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 importRecords) { + String currentItemId = dto.getId(); + if (currentItemId == null) { + return true; + } + for (ExternalDataObject importRecord : importRecords) { + if (currentItemId.equals(importRecord.getId())) { + return true; + } + } + return false; + } + + + /** + * 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) { + String value = itemService.getMetadata(researcher, name); + if (value != null) { + authors.add(value); + } + } + return authors; + } + + @Override + protected boolean isExternalDataObjectPotentiallySuggested(Context context, ExternalDataObject externalDataObject) { + if (StringUtils.equals(externalDataObject.getSource(), primaryProvider.getSourceIdentifier())) { + return true; + } else if (otherProviders != null) { + return otherProviders.stream() + .anyMatch(x -> StringUtils.equals(externalDataObject.getSource(), x.getSourceIdentifier())); + } else { + return false; + } + } + +} diff --git a/dspace-api/src/main/java/org/dspace/app/suggestion/openaire/PublicationLoaderCliScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/app/suggestion/openaire/PublicationLoaderCliScriptConfiguration.java new file mode 100644 index 0000000000..f5289fd99a --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/suggestion/openaire/PublicationLoaderCliScriptConfiguration.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.suggestion.openaire; + +import org.apache.commons.cli.Options; + +/** + * Extension of {@link PublicationLoaderScriptConfiguration} for CLI. + * + * @author Alessandro Martelli (alessandro.martelli at 4science.it) + */ +public class PublicationLoaderCliScriptConfiguration + extends PublicationLoaderScriptConfiguration { + + @Override + public Options getOptions() { + Options options = super.getOptions(); + options.addOption("h", "help", false, "help"); + options.getOption("h").setType(boolean.class); + super.options = options; + return options; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/app/suggestion/openaire/PublicationLoaderRunnable.java b/dspace-api/src/main/java/org/dspace/app/suggestion/openaire/PublicationLoaderRunnable.java new file mode 100644 index 0000000000..76e8213cd7 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/suggestion/openaire/PublicationLoaderRunnable.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.suggestion.openaire; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.apache.commons.cli.ParseException; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.dspace.content.Item; +import org.dspace.core.Context; +import org.dspace.discovery.DiscoverQuery; +import org.dspace.discovery.SearchService; +import org.dspace.discovery.SearchServiceException; +import org.dspace.discovery.SearchUtils; +import org.dspace.discovery.utils.DiscoverQueryBuilder; +import org.dspace.discovery.utils.parameter.QueryBuilderSearchFilter; +import org.dspace.scripts.DSpaceRunnable; +import org.dspace.sort.SortOption; +import org.dspace.utils.DSpace; + +/** + * Runner responsible to import metadata about authors from OpenAIRE to Solr. + * This runner works in two ways: + * If -s parameter with a valid UUID is received, then the specific researcher + * with this UUID will be used. + * Invocation without any parameter results in massive import, processing all + * authors registered in DSpace. + * + * @author Alessandro Martelli (alessandro.martelli at 4science.it) + */ +public class PublicationLoaderRunnable + extends DSpaceRunnable> { + + private static final Logger LOGGER = LogManager.getLogger(); + + private PublicationLoader oairePublicationLoader = null; + + protected Context context; + + protected String profile; + + @Override + @SuppressWarnings({ "rawtypes", "unchecked" }) + public PublicationLoaderScriptConfiguration getScriptConfiguration() { + PublicationLoaderScriptConfiguration configuration = new DSpace().getServiceManager() + .getServiceByName("import-openaire-suggestions", PublicationLoaderScriptConfiguration.class); + return configuration; + } + + @Override + public void setup() throws ParseException { + + oairePublicationLoader = new DSpace().getServiceManager().getServiceByName( + "OpenairePublicationLoader", PublicationLoader.class); + + profile = commandLine.getOptionValue("s"); + if (profile == null) { + LOGGER.info("No argument for -s, process all profiles"); + } else { + LOGGER.info("Process eperson item with UUID {}", profile); + } + } + + @Override + public void internalRun() throws Exception { + + context = new Context(); + + Iterator researchers = getResearchers(profile); + while (researchers.hasNext()) { + Item researcher = researchers.next(); + oairePublicationLoader.importAuthorRecords(context, researcher); + } + + } + + /** + * Get the Item(s) which map a researcher from Solr. If the uuid is specified, + * the researcher with this UUID will be chosen. If the uuid doesn't match any + * researcher, the method returns an empty array list. If uuid is null, all + * research will be return. + * + * @param profileUUID uuid of the researcher. If null, all researcher will be + * returned. + * @return the researcher with specified UUID or all researchers + */ + @SuppressWarnings("rawtypes") + private Iterator getResearchers(String profileUUID) { + SearchService searchService = new DSpace().getSingletonService(SearchService.class); + DiscoverQueryBuilder queryBuilder = SearchUtils.getQueryBuilder(); + List filters = new ArrayList<>(); + String query = "*:*"; + if (profileUUID != null) { + query = "search.resourceid:" + profileUUID; + } + try { + DiscoverQuery discoverQuery = queryBuilder.buildQuery(context, null, + SearchUtils.getDiscoveryConfigurationByName("person"), + query, filters, + "Item", 10, Long.getLong("0"), null, SortOption.DESCENDING); + return searchService.iteratorSearch(context, null, discoverQuery); + } catch (SearchServiceException e) { + LOGGER.error("Unable to read researcher on solr", e); + } + return null; + } +} diff --git a/dspace-api/src/main/java/org/dspace/app/suggestion/openaire/PublicationLoaderRunnableCli.java b/dspace-api/src/main/java/org/dspace/app/suggestion/openaire/PublicationLoaderRunnableCli.java new file mode 100644 index 0000000000..6c59b725d5 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/suggestion/openaire/PublicationLoaderRunnableCli.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.suggestion.openaire; + +import org.apache.commons.cli.HelpFormatter; +import org.apache.commons.cli.ParseException; +import org.dspace.utils.DSpace; + +public class PublicationLoaderRunnableCli extends PublicationLoaderRunnable { + + @Override + @SuppressWarnings({ "rawtypes", "unchecked" }) + public PublicationLoaderCliScriptConfiguration getScriptConfiguration() { + PublicationLoaderCliScriptConfiguration configuration = new DSpace().getServiceManager() + .getServiceByName("import-openaire-suggestions", PublicationLoaderCliScriptConfiguration.class); + return configuration; + } + + @Override + public void setup() throws ParseException { + super.setup(); + + // in case of CLI we show the help prompt + if (commandLine.hasOption('h')) { + HelpFormatter formatter = new HelpFormatter(); + formatter.printHelp("Import Researchers Suggestions", getScriptConfiguration().getOptions()); + System.exit(0); + } + } + +} diff --git a/dspace-api/src/main/java/org/dspace/app/suggestion/openaire/PublicationLoaderScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/app/suggestion/openaire/PublicationLoaderScriptConfiguration.java new file mode 100644 index 0000000000..8bef7de40d --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/suggestion/openaire/PublicationLoaderScriptConfiguration.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.suggestion.openaire; + +import org.apache.commons.cli.Options; +import org.dspace.scripts.configuration.ScriptConfiguration; + +public class PublicationLoaderScriptConfiguration + extends ScriptConfiguration { + + private Class dspaceRunnableClass; + + @Override + public Class getDspaceRunnableClass() { + return dspaceRunnableClass; + } + + /** + * Generic setter for the dspaceRunnableClass + * @param dspaceRunnableClass The dspaceRunnableClass to be set on this PublicationLoaderScriptConfiguration + */ + @Override + public void setDspaceRunnableClass(Class dspaceRunnableClass) { + this.dspaceRunnableClass = dspaceRunnableClass; + } + + /* + @Override + public boolean isAllowedToExecute(Context context) { + try { + return authorizeService.isAdmin(context); + } catch (SQLException e) { + throw new RuntimeException("SQLException occurred when checking if the current user is an admin", e); + } + } + */ + + @Override + public Options getOptions() { + if (options == null) { + Options options = new Options(); + + options.addOption("s", "single-researcher", true, "Single researcher UUID"); + options.getOption("s").setType(String.class); + + super.options = options; + } + return options; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/app/util/AbstractDSpaceWebapp.java b/dspace-api/src/main/java/org/dspace/app/util/AbstractDSpaceWebapp.java index c0cb5d226c..48df3dbc12 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/AbstractDSpaceWebapp.java +++ b/dspace-api/src/main/java/org/dspace/app/util/AbstractDSpaceWebapp.java @@ -12,13 +12,13 @@ import java.sql.SQLException; import java.sql.Timestamp; import java.util.Date; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.app.util.factory.UtilServiceFactory; import org.dspace.app.util.service.WebAppService; import org.dspace.core.Context; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * Represent a DSpace application while it is running. This helps us report @@ -29,11 +29,10 @@ import org.slf4j.LoggerFactory; */ abstract public class AbstractDSpaceWebapp implements DSpaceWebappMXBean { - private static final Logger log = LoggerFactory.getLogger(AbstractDSpaceWebapp.class); + private static final Logger log = LogManager.getLogger(); protected final WebAppService webAppService = UtilServiceFactory.getInstance().getWebAppService(); - protected String kind; protected Date started; diff --git a/dspace-api/src/main/java/org/dspace/app/util/AuthorizeUtil.java b/dspace-api/src/main/java/org/dspace/app/util/AuthorizeUtil.java index efd813d29b..ed59f4b24f 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/AuthorizeUtil.java +++ b/dspace-api/src/main/java/org/dspace/app/util/AuthorizeUtil.java @@ -9,8 +9,8 @@ package org.dspace.app.util; import java.sql.SQLException; import java.util.List; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.apache.logging.log4j.Logger; import org.dspace.authenticate.factory.AuthenticateServiceFactory; import org.dspace.authorize.AuthorizeConfiguration; @@ -624,16 +624,27 @@ public class AuthorizeUtil { throws SQLException { if (DSpaceServicesFactory.getInstance().getConfigurationService() .getBooleanProperty("user.registration", true)) { - // This allowSetPassword is currently the only mthod that would return true only when it's + // This allowSetPassword is currently the only method that would return true only when it's // actually expected to be returning true. // For example the LDAP canSelfRegister will return true due to auto-register, while that // does not imply a new user can register explicitly - return AuthenticateServiceFactory.getInstance().getAuthenticationService() - .allowSetPassword(context, request, null); + return authorizePasswordChange(context, request); } return false; } + /** + * This method will return a boolean indicating whether the current user is allowed to reset the password + * or not + * + * @return A boolean indicating whether the current user can reset its password or not + * @throws SQLException If something goes wrong + */ + public static boolean authorizeForgotPassword() { + return DSpaceServicesFactory.getInstance().getConfigurationService() + .getBooleanProperty("user.forgot-password", true); + } + /** * This method will return a boolean indicating whether it's allowed to update the password for the EPerson * with the given email and canLogin property @@ -647,8 +658,7 @@ public class AuthorizeUtil { if (eperson != null && eperson.canLogIn()) { HttpServletRequest request = new DSpace().getRequestService().getCurrentRequest() .getHttpServletRequest(); - return AuthenticateServiceFactory.getInstance().getAuthenticationService() - .allowSetPassword(context, request, null); + return authorizePasswordChange(context, request); } } catch (SQLException e) { log.error("Something went wrong trying to retrieve EPerson for email: " + email, e); @@ -656,6 +666,19 @@ public class AuthorizeUtil { return false; } + /** + * Checks if the current configuration has at least one password based authentication method + * + * @param context Dspace Context + * @param request Current Request + * @return True if the password change is enabled + * @throws SQLException + */ + protected static boolean authorizePasswordChange(Context context, HttpServletRequest request) throws SQLException { + return AuthenticateServiceFactory.getInstance().getAuthenticationService() + .allowSetPassword(context, request, null); + } + /** * This method checks if the community Admin can manage accounts * diff --git a/dspace-api/src/main/java/org/dspace/app/util/DCInput.java b/dspace-api/src/main/java/org/dspace/app/util/DCInput.java index 11f9aadd86..dd88390cb8 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/DCInput.java +++ b/dspace-api/src/main/java/org/dspace/app/util/DCInput.java @@ -13,13 +13,13 @@ import java.util.Map; import java.util.Optional; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; -import javax.annotation.Nullable; +import jakarta.annotation.Nullable; import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.content.MetadataSchemaEnum; import org.dspace.core.Utils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * Class representing a line in an input form. @@ -28,7 +28,7 @@ import org.slf4j.LoggerFactory; */ public class DCInput { - private static final Logger log = LoggerFactory.getLogger(DCInput.class); + private static final Logger log = LogManager.getLogger(); /** * the DC element name @@ -183,7 +183,7 @@ public class DCInput { } //check if the input have a language tag - language = Boolean.valueOf(fieldMap.get("language")); + language = Boolean.parseBoolean(fieldMap.get("language")); valueLanguageList = new ArrayList<>(); if (language) { String languageNameTmp = fieldMap.get("value-pairs-name"); @@ -219,7 +219,7 @@ public class DCInput { || "yes".equalsIgnoreCase(closedVocabularyStr); // parsing of the element (using the colon as split separator) - typeBind = new ArrayList(); + typeBind = new ArrayList<>(); String typeBindDef = fieldMap.get("type-bind"); if (typeBindDef != null && typeBindDef.trim().length() > 0) { String[] types = typeBindDef.split(","); @@ -523,7 +523,7 @@ public class DCInput { * @return true when there is no type restriction or typeName is allowed */ public boolean isAllowedFor(String typeName) { - if (typeBind.size() == 0) { + if (typeBind.isEmpty()) { return true; } diff --git a/dspace-api/src/main/java/org/dspace/app/util/DSpaceContextListener.java b/dspace-api/src/main/java/org/dspace/app/util/DSpaceContextListener.java index b0289ec4a4..51d78ccaba 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/DSpaceContextListener.java +++ b/dspace-api/src/main/java/org/dspace/app/util/DSpaceContextListener.java @@ -13,9 +13,9 @@ import java.net.URLConnection; import java.sql.Driver; import java.sql.DriverManager; import java.util.Enumeration; -import javax.servlet.ServletContextEvent; -import javax.servlet.ServletContextListener; +import jakarta.servlet.ServletContextEvent; +import jakarta.servlet.ServletContextListener; import org.apache.logging.log4j.Logger; /** diff --git a/dspace-api/src/main/java/org/dspace/app/util/DSpaceWebappListener.java b/dspace-api/src/main/java/org/dspace/app/util/DSpaceWebappListener.java index c2817169b2..32c4ff9c1c 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/DSpaceWebappListener.java +++ b/dspace-api/src/main/java/org/dspace/app/util/DSpaceWebappListener.java @@ -8,8 +8,9 @@ package org.dspace.app.util; import java.lang.reflect.InvocationTargetException; -import javax.servlet.ServletContextEvent; -import javax.servlet.ServletContextListener; + +import jakarta.servlet.ServletContextEvent; +import jakarta.servlet.ServletContextListener; /** * Class that registers the web application upon startup of the application. diff --git a/dspace-api/src/main/java/org/dspace/app/util/IndexVersion.java b/dspace-api/src/main/java/org/dspace/app/util/IndexVersion.java index 7bdaa95b5c..8634954795 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/IndexVersion.java +++ b/dspace-api/src/main/java/org/dspace/app/util/IndexVersion.java @@ -62,7 +62,7 @@ public class IndexVersion { // First argument is the Index path. Determine its version String indexVersion = getIndexVersion(argv[0]); - // Second argumet is an optional version number to compare to + // Second argument is an optional version number to compare to String compareToVersion = argv.length > 1 ? argv[1] : null; // If indexVersion comes back as null, then it is not a valid index directory. diff --git a/dspace-api/src/main/java/org/dspace/app/util/InitializeEntities.java b/dspace-api/src/main/java/org/dspace/app/util/InitializeEntities.java index 0a072a9819..e8c5d93181 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/InitializeEntities.java +++ b/dspace-api/src/main/java/org/dspace/app/util/InitializeEntities.java @@ -74,7 +74,7 @@ public class InitializeEntities { private static void checkHelpEntered(Options options, CommandLine line) { if (line.hasOption("h")) { HelpFormatter formatter = new HelpFormatter(); - formatter.printHelp("Intialize Entities", options); + formatter.printHelp("Initialize Entities", options); System.exit(0); } } diff --git a/dspace-api/src/main/java/org/dspace/app/util/RegexPatternUtils.java b/dspace-api/src/main/java/org/dspace/app/util/RegexPatternUtils.java index 578e57fb09..17d94027af 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/RegexPatternUtils.java +++ b/dspace-api/src/main/java/org/dspace/app/util/RegexPatternUtils.java @@ -36,7 +36,7 @@ public class RegexPatternUtils { * Computes a pattern starting from a regex definition with flags that * uses the standard format: /{regex}/{flags} (ECMAScript format). * This method can transform an ECMAScript regex into a java {@code Pattern} object - * wich can be used to validate strings. + * which can be used to validate strings. *
* If regex is null, empty or blank a null {@code Pattern} will be retrieved * If it's a valid regex, then a non-null {@code Pattern} will be retrieved, diff --git a/dspace-api/src/main/java/org/dspace/app/util/SubmissionConfigReader.java b/dspace-api/src/main/java/org/dspace/app/util/SubmissionConfigReader.java index 70c5092602..0f8f745680 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/SubmissionConfigReader.java +++ b/dspace-api/src/main/java/org/dspace/app/util/SubmissionConfigReader.java @@ -22,6 +22,7 @@ import javax.xml.parsers.FactoryConfigurationError; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.dspace.content.Collection; +import org.dspace.content.Community; import org.dspace.content.DSpaceObject; import org.dspace.content.Item; import org.dspace.content.factory.ContentServiceFactory; @@ -92,10 +93,11 @@ public class SubmissionConfigReader { private Map collectionToSubmissionConfig = null; /** - * Reference to the global submission step definitions defined in the - * "step-definitions" section + * Hashmap which stores which submission process configuration is used by + * which community, computed from the item submission config file + * (specifically, the 'submission-map' tag) */ - private Map> stepDefns = null; + private Map communityToSubmissionConfig = null; /** * Hashmap which stores which submission process configuration is used by @@ -104,6 +106,12 @@ public class SubmissionConfigReader { */ private Map entityTypeToSubmissionConfig = null; + /** + * Reference to the global submission step definitions defined in the + * "step-definitions" section + */ + private Map> stepDefns = null; + /** * Reference to the item submission definitions defined in the * "submission-definitions" section @@ -135,6 +143,7 @@ public class SubmissionConfigReader { public void reload() throws SubmissionConfigReaderException { collectionToSubmissionConfig = null; + communityToSubmissionConfig = null; entityTypeToSubmissionConfig = null; stepDefns = null; submitDefns = null; @@ -149,11 +158,12 @@ public class SubmissionConfigReader { *
  • Hashmap of Collection to Submission definition mappings - * defines which Submission process a particular collection uses *
  • Hashmap of all Submission definitions. List of all valid - * Submision Processes by name. + * Submission Processes by name. * */ private void buildInputs(String fileName) throws SubmissionConfigReaderException { collectionToSubmissionConfig = new HashMap(); + communityToSubmissionConfig = new HashMap(); entityTypeToSubmissionConfig = new HashMap(); submitDefns = new LinkedHashMap>>(); @@ -233,15 +243,30 @@ public class SubmissionConfigReader { if (submitName != null) { return getSubmissionConfigByName(submitName); } - } - // get the name of the submission process based on the entity type of this collections - if (!entityTypeToSubmissionConfig.isEmpty()) { - String entityType = collectionService.getMetadataFirstValue(col, "dspace", "entity", "type", Item.ANY); - submitName = entityTypeToSubmissionConfig - .get(entityType); - if (submitName != null) { - return getSubmissionConfigByName(submitName); + // get the name of the submission process based on the entity type of this collections + if (!entityTypeToSubmissionConfig.isEmpty()) { + String entityType = collectionService.getMetadataFirstValue(col, "dspace", "entity", "type", Item.ANY); + submitName = entityTypeToSubmissionConfig + .get(entityType); + if (submitName != null) { + return getSubmissionConfigByName(submitName); + } + } + + if (!communityToSubmissionConfig.isEmpty()) { + try { + List communities = col.getCommunities(); + for (Community com : communities) { + submitName = getSubmissionConfigByCommunity(com); + if (submitName != null) { + return getSubmissionConfigByName(submitName); + } + } + } catch (SQLException sqle) { + throw new IllegalStateException("Error occurred while getting item submission configured " + + "by community", sqle); + } } } @@ -255,6 +280,30 @@ public class SubmissionConfigReader { return getSubmissionConfigByName(submitName); } + /** + * Recursive function to return the Item Submission process config + * used for a community or the closest community parent, or null + * if none is defined + * + * @param com community for which search Submission process config + * @return the SubmissionConfig representing the item submission config + */ + private String getSubmissionConfigByCommunity(Community com) { + String submitName = communityToSubmissionConfig + .get(com.getHandle()); + if (submitName != null) { + return submitName; + } + List communities = com.getParentCommunities(); + for (Community parentCom : communities) { + submitName = getSubmissionConfigByCommunity(parentCom); + if (submitName != null) { + return submitName; + } + } + return null; + } + /** * Returns the Item Submission process config * @@ -309,7 +358,7 @@ public class SubmissionConfigReader { throws SubmissionConfigReaderException { // We should already have the step definitions loaded if (stepDefns != null) { - // retreive step info + // retrieve step info Map stepInfo = stepDefns.get(stepID); if (stepInfo != null) { @@ -380,13 +429,14 @@ public class SubmissionConfigReader { Node nd = nl.item(i); if (nd.getNodeName().equals("name-map")) { String id = getAttribute(nd, "collection-handle"); + String communityId = getAttribute(nd, "community-handle"); String entityType = getAttribute(nd, "collection-entity-type"); String value = getAttribute(nd, "submission-name"); String content = getValue(nd); - if (id == null && entityType == null) { + if (id == null && communityId == null && entityType == null) { throw new SAXException( - "name-map element is missing collection-handle or collection-entity-type attribute " + - "in 'item-submission.xml'"); + "name-map element is missing collection-handle or community-handle or collection-entity-type " + + "attribute in 'item-submission.xml'"); } if (value == null) { throw new SAXException( @@ -398,7 +448,8 @@ public class SubmissionConfigReader { } if (id != null) { collectionToSubmissionConfig.put(id, value); - + } else if (communityId != null) { + communityToSubmissionConfig.put(communityId, value); } else { entityTypeToSubmissionConfig.put(entityType, value); } diff --git a/dspace-api/src/main/java/org/dspace/app/util/SubmissionStepConfig.java b/dspace-api/src/main/java/org/dspace/app/util/SubmissionStepConfig.java index 28d39d911b..db45d42e49 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/SubmissionStepConfig.java +++ b/dspace-api/src/main/java/org/dspace/app/util/SubmissionStepConfig.java @@ -13,7 +13,7 @@ import java.util.Map; import org.apache.commons.lang3.BooleanUtils; import org.dspace.content.InProgressSubmission; import org.dspace.content.WorkspaceItem; -import org.hibernate.proxy.HibernateProxyHelper; +import org.dspace.core.HibernateProxyHelper; /** * Class representing configuration for a single step within an Item Submission diff --git a/dspace-api/src/main/java/org/dspace/app/util/SyndicationFeed.java b/dspace-api/src/main/java/org/dspace/app/util/SyndicationFeed.java index d37e6d802f..b4ba69ffa2 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/SyndicationFeed.java +++ b/dspace-api/src/main/java/org/dspace/app/util/SyndicationFeed.java @@ -13,7 +13,6 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Map; -import javax.servlet.http.HttpServletRequest; import com.rometools.modules.itunes.EntryInformation; import com.rometools.modules.itunes.EntryInformationImpl; @@ -35,6 +34,7 @@ import com.rometools.rome.feed.synd.SyndPerson; import com.rometools.rome.feed.synd.SyndPersonImpl; import com.rometools.rome.io.FeedException; import com.rometools.rome.io.SyndFeedOutput; +import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; @@ -523,7 +523,7 @@ public class SyndicationFeed { */ protected String resolveURL(HttpServletRequest request, DSpaceObject dso) { // If no object given then just link to the whole repository, - // since no offical handle exists so we have to use local resolution. + // since no official handle exists so we have to use local resolution. if (dso == null) { if (baseURL == null) { if (request == null) { diff --git a/dspace-api/src/main/java/org/dspace/app/util/Util.java b/dspace-api/src/main/java/org/dspace/app/util/Util.java index 3bc828d6c4..3a0c368880 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/Util.java +++ b/dspace-api/src/main/java/org/dspace/app/util/Util.java @@ -20,8 +20,8 @@ import java.util.Locale; import java.util.Properties; import java.util.Set; import java.util.UUID; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.collections4.ListUtils; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; @@ -179,7 +179,7 @@ public class Util { * @return the file size as a String */ public static String formatFileSize(double in) { - // Work out the size of the file, and format appropriatly + // Work out the size of the file, and format appropriately // FIXME: When full i18n support is available, use the user's Locale // rather than the default Locale. NumberFormat nf = NumberFormat.getNumberInstance(Locale.getDefault()); @@ -238,7 +238,7 @@ public class Util { } catch (Exception e) { // at least log this error to make debugging easier // do not silently return null only. - log.warn("Unable to recoginze UUID from String \"" + log.warn("Unable to recognize UUID from String \"" + val + "\". Will return null.", e); // Problem with parameter return null; diff --git a/dspace-api/src/main/java/org/dspace/app/util/WebApp.java b/dspace-api/src/main/java/org/dspace/app/util/WebApp.java index 2f42c1459f..c2fc133f41 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/WebApp.java +++ b/dspace-api/src/main/java/org/dspace/app/util/WebApp.java @@ -8,16 +8,16 @@ package org.dspace.app.util; import java.util.Date; -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.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.dspace.core.Context; import org.dspace.core.ReloadableEntity; diff --git a/dspace-api/src/main/java/org/dspace/app/util/service/OpenSearchService.java b/dspace-api/src/main/java/org/dspace/app/util/service/OpenSearchService.java index 03f41e535c..cf62bca30e 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/service/OpenSearchService.java +++ b/dspace-api/src/main/java/org/dspace/app/util/service/OpenSearchService.java @@ -85,7 +85,7 @@ public interface OpenSearchService { * @param start - start result index * @param pageSize - page size * @param scope - search scope, null or the community/collection - * @param results the retreived DSpace objects satisfying search + * @param results the retrieved DSpace objects satisfying search * @param labels labels to apply - format specific * @return formatted search results * @throws IOException if IO error @@ -105,7 +105,7 @@ public interface OpenSearchService { * @param start - start result index * @param pageSize - page size * @param scope - search scope, null or the community/collection - * @param results the retreived DSpace objects satisfying search + * @param results the retrieved DSpace objects satisfying search * @param labels labels to apply - format specific * @return formatted search results * @throws IOException if IO error diff --git a/dspace-api/src/main/java/org/dspace/authenticate/AuthenticationMethod.java b/dspace-api/src/main/java/org/dspace/authenticate/AuthenticationMethod.java index 500ee04a97..d316cb636f 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/AuthenticationMethod.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/AuthenticationMethod.java @@ -9,9 +9,9 @@ package org.dspace.authenticate; import java.sql.SQLException; import java.util.List; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.dspace.core.Context; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; diff --git a/dspace-api/src/main/java/org/dspace/authenticate/AuthenticationServiceImpl.java b/dspace-api/src/main/java/org/dspace/authenticate/AuthenticationServiceImpl.java index 1d67da37ec..2b07f73c48 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/AuthenticationServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/AuthenticationServiceImpl.java @@ -13,8 +13,10 @@ import java.util.Arrays; import java.util.Date; import java.util.Iterator; import java.util.List; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.authenticate.service.AuthenticationService; import org.dspace.authorize.AuthorizeException; import org.dspace.core.Context; @@ -22,8 +24,6 @@ import org.dspace.core.factory.CoreServiceFactory; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; import org.dspace.eperson.service.EPersonService; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; /** @@ -49,15 +49,14 @@ import org.springframework.beans.factory.annotation.Autowired; * specified first (in the configuration) thus getting highest priority. * * @author Larry Stone - * @version $Revision$ * @see AuthenticationMethod */ public class AuthenticationServiceImpl implements AuthenticationService { /** - * SLF4J logging category + * Logging category */ - private final Logger log = (Logger) LoggerFactory.getLogger(AuthenticationServiceImpl.class); + private final Logger log = LogManager.getLogger(); @Autowired(required = true) protected EPersonService ePersonService; @@ -121,6 +120,7 @@ public class AuthenticationServiceImpl implements AuthenticationService { return bestRet; } + @Override public void updateLastActiveDate(Context context) { EPerson me = context.getCurrentUser(); if (me != null) { diff --git a/dspace-api/src/main/java/org/dspace/authenticate/IPAuthentication.java b/dspace-api/src/main/java/org/dspace/authenticate/IPAuthentication.java index 0c2be211a5..db71ec1c2f 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/IPAuthentication.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/IPAuthentication.java @@ -14,9 +14,9 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.apache.logging.log4j.Logger; import org.dspace.core.Context; import org.dspace.core.LogHelper; diff --git a/dspace-api/src/main/java/org/dspace/authenticate/LDAPAuthentication.java b/dspace-api/src/main/java/org/dspace/authenticate/LDAPAuthentication.java index 585eaf9cd8..40b8f48078 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/LDAPAuthentication.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/LDAPAuthentication.java @@ -17,6 +17,7 @@ import java.util.Collections; import java.util.Hashtable; import java.util.Iterator; import java.util.List; +import java.util.Optional; import javax.naming.NamingEnumeration; import javax.naming.NamingException; import javax.naming.directory.Attribute; @@ -29,9 +30,9 @@ import javax.naming.ldap.InitialLdapContext; import javax.naming.ldap.LdapContext; import javax.naming.ldap.StartTlsRequest; import javax.naming.ldap.StartTlsResponse; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.dspace.authenticate.factory.AuthenticateServiceFactory; @@ -68,12 +69,8 @@ import org.dspace.services.factory.DSpaceServicesFactory; * @author Ivan Masár * @author Michael Plate */ -public class LDAPAuthentication - implements AuthenticationMethod { +public class LDAPAuthentication implements AuthenticationMethod { - /** - * log4j category - */ private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(LDAPAuthentication.class); @@ -130,7 +127,7 @@ public class LDAPAuthentication return false; } - /* + /** * This is an explicit method. */ @Override @@ -138,7 +135,7 @@ public class LDAPAuthentication return false; } - /* + /** * Add authenticated users to the group defined in dspace.cfg by * the login.specialgroup key. */ @@ -177,7 +174,7 @@ public class LDAPAuthentication return Collections.EMPTY_LIST; } - /* + /** * Authenticate the given credentials. * This is the heart of the authentication method: test the * credentials for authenticity, and if accepted, attempt to match @@ -187,7 +184,7 @@ public class LDAPAuthentication * @param context * DSpace context, will be modified (ePerson set) upon success. * - * @param username + * @param netid * Username (or email address) when method is explicit. Use null for * implicit method. * @@ -250,7 +247,7 @@ public class LDAPAuthentication } // Check a DN was found - if ((dn == null) || (dn.trim().equals(""))) { + if (StringUtils.isBlank(dn)) { log.info(LogHelper .getHeader(context, "failed_login", "no DN found for user " + netid)); return BAD_CREDENTIALS; @@ -269,6 +266,18 @@ public class LDAPAuthentication context.setCurrentUser(eperson); request.setAttribute(LDAP_AUTHENTICATED, true); + // update eperson's attributes + context.turnOffAuthorisationSystem(); + setEpersonAttributes(context, eperson, ldap, Optional.empty()); + try { + ePersonService.update(context, eperson); + context.dispatchEvents(); + } catch (AuthorizeException e) { + log.warn("update of eperson " + eperson.getID() + " failed", e); + } finally { + context.restoreAuthSystemState(); + } + // assign user to groups based on ldap dn assignGroups(dn, ldap.ldapGroup, context); @@ -313,14 +322,13 @@ public class LDAPAuthentication log.info(LogHelper.getHeader(context, "type=ldap-login", "type=ldap_but_already_email")); context.turnOffAuthorisationSystem(); - eperson.setNetid(netid.toLowerCase()); + setEpersonAttributes(context, eperson, ldap, Optional.of(netid)); ePersonService.update(context, eperson); context.dispatchEvents(); context.restoreAuthSystemState(); context.setCurrentUser(eperson); request.setAttribute(LDAP_AUTHENTICATED, true); - // assign user to groups based on ldap dn assignGroups(dn, ldap.ldapGroup, context); @@ -331,20 +339,7 @@ public class LDAPAuthentication try { context.turnOffAuthorisationSystem(); eperson = ePersonService.create(context); - if (StringUtils.isNotEmpty(email)) { - eperson.setEmail(email); - } - if (StringUtils.isNotEmpty(ldap.ldapGivenName)) { - eperson.setFirstName(context, ldap.ldapGivenName); - } - if (StringUtils.isNotEmpty(ldap.ldapSurname)) { - eperson.setLastName(context, ldap.ldapSurname); - } - if (StringUtils.isNotEmpty(ldap.ldapPhone)) { - ePersonService.setMetadataSingleValue(context, eperson, - MD_PHONE, ldap.ldapPhone, null); - } - eperson.setNetid(netid.toLowerCase()); + setEpersonAttributes(context, eperson, ldap, Optional.of(netid)); eperson.setCanLogIn(true); authenticationService.initEPerson(context, request, eperson); ePersonService.update(context, eperson); @@ -382,6 +377,29 @@ public class LDAPAuthentication return BAD_ARGS; } + /** + * Update eperson's attributes + */ + private void setEpersonAttributes(Context context, EPerson eperson, SpeakerToLDAP ldap, Optional netid) + throws SQLException { + + if (StringUtils.isNotEmpty(ldap.ldapEmail)) { + eperson.setEmail(ldap.ldapEmail); + } + if (StringUtils.isNotEmpty(ldap.ldapGivenName)) { + eperson.setFirstName(context, ldap.ldapGivenName); + } + if (StringUtils.isNotEmpty(ldap.ldapSurname)) { + eperson.setLastName(context, ldap.ldapSurname); + } + if (StringUtils.isNotEmpty(ldap.ldapPhone)) { + ePersonService.setMetadataSingleValue(context, eperson, MD_PHONE, ldap.ldapPhone, null); + } + if (netid.isPresent()) { + eperson.setNetid(netid.get().toLowerCase()); + } + } + /** * Internal class to manage LDAP query and results, mainly * because there are multiple values to return. @@ -503,6 +521,7 @@ public class LDAPAuthentication } else { searchName = ldap_provider_url + ldap_search_context; } + @SuppressWarnings("BanJNDI") NamingEnumeration answer = ctx.search( searchName, "(&({0}={1}))", new Object[] {ldap_id_field, @@ -553,7 +572,7 @@ public class LDAPAuthentication att = atts.get(attlist[4]); if (att != null) { // loop through all groups returned by LDAP - ldapGroup = new ArrayList(); + ldapGroup = new ArrayList<>(); for (NamingEnumeration val = att.getAll(); val.hasMoreElements(); ) { ldapGroup.add((String) val.next()); } @@ -633,7 +652,8 @@ public class LDAPAuthentication ctx.addToEnvironment(javax.naming.Context.AUTHORITATIVE, "true"); ctx.addToEnvironment(javax.naming.Context.REFERRAL, "follow"); // dummy operation to check if authentication has succeeded - ctx.getAttributes(""); + @SuppressWarnings("BanJNDI") + Attributes trash = ctx.getAttributes(""); } else if (!useTLS) { // Authenticate env.put(javax.naming.Context.SECURITY_AUTHENTICATION, "Simple"); @@ -671,7 +691,7 @@ public class LDAPAuthentication } } - /* + /** * Returns the URL of an external login page which is not applicable for this authn method. * * Note: Prior to DSpace 7, this method return the page of login servlet. @@ -699,7 +719,7 @@ public class LDAPAuthentication return "ldap"; } - /* + /** * Add authenticated users to the group defined in dspace.cfg by * the authentication-ldap.login.groupmap.* key. * diff --git a/dspace-api/src/main/java/org/dspace/authenticate/OidcAuthentication.java b/dspace-api/src/main/java/org/dspace/authenticate/OidcAuthentication.java index 5d4635d48e..c7ac1ff557 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/OidcAuthentication.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/OidcAuthentication.java @@ -9,9 +9,9 @@ package org.dspace.authenticate; import java.sql.SQLException; import java.util.List; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.dspace.core.Context; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; diff --git a/dspace-api/src/main/java/org/dspace/authenticate/OidcAuthenticationBean.java b/dspace-api/src/main/java/org/dspace/authenticate/OidcAuthenticationBean.java index 8a4ac190c8..44c9fb7dc8 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/OidcAuthenticationBean.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/OidcAuthenticationBean.java @@ -20,10 +20,12 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.authenticate.oidc.OidcClient; import org.dspace.authenticate.oidc.model.OidcTokenResponseDTO; import org.dspace.core.Context; @@ -31,8 +33,6 @@ import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; import org.dspace.eperson.service.EPersonService; import org.dspace.services.ConfigurationService; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; /** @@ -51,7 +51,7 @@ public class OidcAuthenticationBean implements AuthenticationMethod { private final static String LOGIN_PAGE_URL_FORMAT = "%s?client_id=%s&response_type=code&scope=%s&redirect_uri=%s"; - private static final Logger LOGGER = LoggerFactory.getLogger(OidcAuthenticationBean.class); + private static final Logger LOGGER = LogManager.getLogger(); private static final String OIDC_AUTHENTICATED = "oidc.authenticated"; @@ -174,7 +174,7 @@ public class OidcAuthenticationBean implements AuthenticationMethod { final Entry entry = iterator.next(); if (isBlank(entry.getValue())) { - LOGGER.error(" * {} is missing", entry.getKey()); + LOGGER.error(" * {} is missing", entry::getKey); } } return ""; @@ -183,7 +183,7 @@ public class OidcAuthenticationBean implements AuthenticationMethod { try { return format(LOGIN_PAGE_URL_FORMAT, authorizeUrl, clientId, scopes, encode(redirectUri, "UTF-8")); } catch (UnsupportedEncodingException e) { - LOGGER.error(e.getMessage(), e); + LOGGER.error(e::getMessage, e); return ""; } @@ -235,7 +235,7 @@ public class OidcAuthenticationBean implements AuthenticationMethod { try { return oidcClient.getAccessToken(code); } catch (Exception ex) { - LOGGER.error("An error occurs retriving the OIDC access_token", ex); + LOGGER.error("An error occurs retrieving the OIDC access_token", ex); return null; } } @@ -244,7 +244,7 @@ public class OidcAuthenticationBean implements AuthenticationMethod { try { return oidcClient.getUserInfo(accessToken); } catch (Exception ex) { - LOGGER.error("An error occurs retriving the OIDC user info", ex); + LOGGER.error("An error occurs retrieving the OIDC user info", ex); return Map.of(); } } diff --git a/dspace-api/src/main/java/org/dspace/authenticate/OrcidAuthentication.java b/dspace-api/src/main/java/org/dspace/authenticate/OrcidAuthentication.java index 3e9ff6638a..932d963307 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/OrcidAuthentication.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/OrcidAuthentication.java @@ -10,9 +10,9 @@ package org.dspace.authenticate; import java.sql.SQLException; import java.util.Iterator; import java.util.List; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.dspace.authenticate.factory.AuthenticateServiceFactory; import org.dspace.core.Context; import org.dspace.eperson.EPerson; diff --git a/dspace-api/src/main/java/org/dspace/authenticate/OrcidAuthenticationBean.java b/dspace-api/src/main/java/org/dspace/authenticate/OrcidAuthenticationBean.java index a11bbfc867..ee30338a8f 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/OrcidAuthenticationBean.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/OrcidAuthenticationBean.java @@ -18,11 +18,13 @@ import java.sql.SQLException; import java.util.Collections; import java.util.List; import java.util.Optional; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.authorize.AuthorizeException; import org.dspace.core.Context; import org.dspace.eperson.EPerson; @@ -39,8 +41,6 @@ import org.dspace.profile.service.ResearcherProfileService; import org.dspace.services.ConfigurationService; import org.orcid.jaxb.model.v3.release.record.Email; import org.orcid.jaxb.model.v3.release.record.Person; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; /** @@ -53,7 +53,7 @@ public class OrcidAuthenticationBean implements AuthenticationMethod { public static final String ORCID_AUTH_ATTRIBUTE = "orcid-authentication"; - private final static Logger LOGGER = LoggerFactory.getLogger(OrcidAuthenticationBean.class); + private final static Logger LOGGER = LogManager.getLogger(); private final static String LOGIN_PAGE_URL_FORMAT = "%s?client_id=%s&response_type=code&scope=%s&redirect_uri=%s"; @@ -282,7 +282,8 @@ public class OrcidAuthenticationBean implements AuthenticationMethod { try { return orcidClient.getPerson(token.getAccessToken(), token.getOrcid()); } catch (Exception ex) { - LOGGER.error("An error occurs retriving the ORCID record with id " + token.getOrcid(), ex); + LOGGER.error("An error occurs retrieving the ORCID record with id {}", + token.getOrcid(), ex); return null; } } @@ -319,7 +320,7 @@ public class OrcidAuthenticationBean implements AuthenticationMethod { try { return orcidClient.getAccessToken(code); } catch (Exception ex) { - LOGGER.error("An error occurs retriving the ORCID access_token", ex); + LOGGER.error("An error occurs retrieving the ORCID access_token", ex); return null; } } diff --git a/dspace-api/src/main/java/org/dspace/authenticate/PasswordAuthentication.java b/dspace-api/src/main/java/org/dspace/authenticate/PasswordAuthentication.java index 6d1ca862d3..f66d0730e9 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/PasswordAuthentication.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/PasswordAuthentication.java @@ -11,9 +11,9 @@ import java.sql.SQLException; import java.util.Arrays; import java.util.Collections; import java.util.List; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -82,7 +82,7 @@ public class PasswordAuthentication // No conditions set, so must be able to self register return true; } else { - // Itterate through all domains + // Iterate through all domains String check; email = email.trim().toLowerCase(); for (int i = 0; i < domains.length; i++) { diff --git a/dspace-api/src/main/java/org/dspace/authenticate/ShibAuthentication.java b/dspace-api/src/main/java/org/dspace/authenticate/ShibAuthentication.java index 791634a7dc..2f117e6944 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/ShibAuthentication.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/ShibAuthentication.java @@ -20,9 +20,9 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -287,7 +287,7 @@ public class ShibAuthentication implements AuthenticationMethod { @Override public List getSpecialGroups(Context context, HttpServletRequest request) { try { - // User has not successfuly authenticated via shibboleth. + // User has not successfully authenticated via shibboleth. if (request == null || context.getCurrentUser() == null) { return Collections.EMPTY_LIST; @@ -309,7 +309,7 @@ public class ShibAuthentication implements AuthenticationMethod { if (ignoreScope && ignoreValue) { throw new IllegalStateException( "Both config parameters for ignoring an roll attributes scope and value are turned on, this is " + - "not a permissable configuration. (Note: ignore-scope defaults to true) The configuration " + + "not a permissible configuration. (Note: ignore-scope defaults to true) The configuration " + "parameters are: 'authentication.shib.role-header.ignore-scope' and 'authentication.shib" + ".role-header.ignore-value'"); } @@ -391,7 +391,7 @@ public class ShibAuthentication implements AuthenticationMethod { return new ArrayList<>(groups); } catch (Throwable t) { - log.error("Unable to validate any sepcial groups this user may belong too because of an exception.", t); + log.error("Unable to validate any special groups this user may belong too because of an exception.", t); return Collections.EMPTY_LIST; } } @@ -546,7 +546,7 @@ public class ShibAuthentication implements AuthenticationMethod { /** * Identify an existing EPerson based upon the shibboleth attributes provided on - * the request object. There are three cases where this can occurr, each as + * the request object. There are three cases where this can occur, each as * a fallback for the previous method. * * 1) NetID from Shibboleth Header (best) @@ -671,7 +671,7 @@ public class ShibAuthentication implements AuthenticationMethod { if (!foundNetID && !foundEmail && !foundRemoteUser) { log.error( "Shibboleth authentication was not able to find a NetId, Email, or Tomcat Remote user for which to " + - "indentify a user from."); + "identify a user from."); } @@ -931,7 +931,7 @@ public class ShibAuthentication implements AuthenticationMethod { "compatibility mode."); return SUCCESS; } else { - // Passsword failure + // Password failure log.error( "Shibboleth-based password authentication failed for user " + username + " because a bad password was" + " supplied."); @@ -944,7 +944,7 @@ public class ShibAuthentication implements AuthenticationMethod { /** * Initialize Shibboleth Authentication. * - * During initalization the mapping of additional eperson metadata will be loaded from the DSpace.cfg + * During initialization the mapping of additional eperson metadata will be loaded from the DSpace.cfg * and cached. While loading the metadata mapping this method will check the EPerson object to see * if it supports the metadata field. If the field is not supported and autocreate is turned on then * the field will be automatically created. @@ -985,7 +985,7 @@ public class ShibAuthentication implements AuthenticationMethod { String[] metadataParts = metadataString.split("=>"); if (metadataParts.length != 2) { - log.error("Unable to parse metadat mapping string: '" + metadataString + "'"); + log.error("Unable to parse metadata mapping string: '" + metadataString + "'"); continue; } diff --git a/dspace-api/src/main/java/org/dspace/authenticate/X509Authentication.java b/dspace-api/src/main/java/org/dspace/authenticate/X509Authentication.java index 12dc5feda5..55843c7107 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/X509Authentication.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/X509Authentication.java @@ -25,10 +25,10 @@ import java.util.Collections; import java.util.Enumeration; import java.util.List; import java.util.StringTokenizer; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; import org.apache.commons.lang3.ArrayUtils; import org.apache.logging.log4j.Logger; import org.dspace.authenticate.factory.AuthenticateServiceFactory; @@ -505,7 +505,7 @@ public class X509Authentication implements AuthenticationMethod { X509Certificate[] certs = null; if (request != null) { certs = (X509Certificate[]) request - .getAttribute("javax.servlet.request.X509Certificate"); + .getAttribute("jakarta.servlet.request.X509Certificate"); } if ((certs == null) || (certs.length == 0)) { diff --git a/dspace-api/src/main/java/org/dspace/authenticate/oidc/impl/OidcClientImpl.java b/dspace-api/src/main/java/org/dspace/authenticate/oidc/impl/OidcClientImpl.java index ddab01e8cb..68fffd3fb2 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/oidc/impl/OidcClientImpl.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/oidc/impl/OidcClientImpl.java @@ -14,10 +14,10 @@ import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; import java.util.Map; -import javax.annotation.PostConstruct; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.annotation.PostConstruct; import org.apache.commons.io.IOUtils; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; diff --git a/dspace-api/src/main/java/org/dspace/authenticate/service/AuthenticationService.java b/dspace-api/src/main/java/org/dspace/authenticate/service/AuthenticationService.java index e955302ec3..45ad8932da 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/service/AuthenticationService.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/service/AuthenticationService.java @@ -10,8 +10,8 @@ package org.dspace.authenticate.service; import java.sql.SQLException; import java.util.Iterator; import java.util.List; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.authenticate.AuthenticationMethod; import org.dspace.core.Context; import org.dspace.eperson.EPerson; diff --git a/dspace-api/src/main/java/org/dspace/authority/AuthoritySolrServiceImpl.java b/dspace-api/src/main/java/org/dspace/authority/AuthoritySolrServiceImpl.java index ca5b4a11b5..2ab2bbf902 100644 --- a/dspace-api/src/main/java/org/dspace/authority/AuthoritySolrServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/authority/AuthoritySolrServiceImpl.java @@ -11,9 +11,9 @@ import java.io.IOException; import java.net.MalformedURLException; import java.util.ArrayList; import java.util.List; -import javax.inject.Inject; -import javax.inject.Named; +import jakarta.inject.Inject; +import jakarta.inject.Named; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.solr.client.solrj.SolrClient; diff --git a/dspace-api/src/main/java/org/dspace/authority/AuthorityValue.java b/dspace-api/src/main/java/org/dspace/authority/AuthorityValue.java index 10a608bb76..6ca0292fdb 100644 --- a/dspace-api/src/main/java/org/dspace/authority/AuthorityValue.java +++ b/dspace-api/src/main/java/org/dspace/authority/AuthorityValue.java @@ -9,6 +9,10 @@ package org.dspace.authority; import java.sql.SQLException; import java.text.DateFormat; +import java.time.DateTimeException; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; @@ -16,6 +20,7 @@ import java.util.List; import java.util.Map; import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.solr.common.SolrDocument; import org.apache.solr.common.SolrInputDocument; @@ -25,9 +30,6 @@ import org.dspace.content.MetadataValue; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.core.Context; import org.dspace.util.SolrUtils; -import org.joda.time.DateTime; -import org.joda.time.format.DateTimeFormatter; -import org.joda.time.format.ISODateTimeFormat; /** * @author Antoine Snyers (antoine at atmire.com) @@ -192,7 +194,7 @@ public class AuthorityValue { } /** - * Information that can be used the choice ui + * Information that can be used the choice ui. * * @return map */ @@ -200,42 +202,51 @@ public class AuthorityValue { return new HashMap<>(); } - - public List getDateFormatters() { - List list = new ArrayList<>(); - list.add(ISODateTimeFormat.dateTime()); - list.add(ISODateTimeFormat.dateTimeNoMillis()); + /** + * Build a list of ISO date formatters to parse various forms. + * + *

    Note: any formatter which does not parse a zone or + * offset must have a default zone set. See {@link stringToDate}. + * + * @return the formatters. + */ + static private List getDateFormatters() { + List list = new ArrayList<>(); + list.add(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss[.SSS]X")); + list.add(java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME + .withZone(ZoneId.systemDefault().normalized())); return list; } - public Date stringToDate(String date) { + /** + * Convert a date string to internal form, trying several parsers. + * + * @param date serialized date to be converted. + * @return converted date, or null if no parser accepted the input. + */ + static public Date stringToDate(String date) { Date result = null; if (StringUtils.isNotBlank(date)) { - List dateFormatters = getDateFormatters(); - boolean converted = false; - int formatter = 0; - while (!converted) { + for (DateTimeFormatter formatter : getDateFormatters()) { try { - DateTimeFormatter dateTimeFormatter = dateFormatters.get(formatter); - DateTime dateTime = dateTimeFormatter.parseDateTime(date); - result = dateTime.toDate(); - converted = true; - } catch (IllegalArgumentException e) { - formatter++; - if (formatter > dateFormatters.size()) { - converted = true; - } - log.error("Could not find a valid date format for: \"" + date + "\"", e); + ZonedDateTime dateTime = ZonedDateTime.parse(date, formatter); + result = Date.from(dateTime.toInstant()); + break; + } catch (DateTimeException e) { + log.debug("Input '{}' did not match {}", date, formatter); } } } + if (null == result) { + log.error("Could not find a valid date format for: \"{}\"", date); + } return result; } /** * log4j logger */ - private static Logger log = org.apache.logging.log4j.LogManager.getLogger(AuthorityValue.class); + private static Logger log = LogManager.getLogger(); @Override public String toString() { @@ -272,6 +283,10 @@ public class AuthorityValue { return new AuthorityValue(); } + /** + * Get the type of authority which created this value. + * @return type name. + */ public String getAuthorityType() { return "internal"; } diff --git a/dspace-api/src/main/java/org/dspace/authority/indexer/DSpaceAuthorityIndexer.java b/dspace-api/src/main/java/org/dspace/authority/indexer/DSpaceAuthorityIndexer.java index 20baef8f9c..b42bd130b7 100644 --- a/dspace-api/src/main/java/org/dspace/authority/indexer/DSpaceAuthorityIndexer.java +++ b/dspace-api/src/main/java/org/dspace/authority/indexer/DSpaceAuthorityIndexer.java @@ -80,7 +80,15 @@ public class DSpaceAuthorityIndexer implements AuthorityIndexerInterface, Initia throws SQLException, AuthorizeException { List values = new ArrayList<>(); for (String metadataField : metadataFields) { - List metadataValues = itemService.getMetadataByMetadataString(item, metadataField); + + String[] fieldParts = metadataField.split("\\."); + String schema = (fieldParts.length > 0 ? fieldParts[0] : null); + String element = (fieldParts.length > 1 ? fieldParts[1] : null); + String qualifier = (fieldParts.length > 2 ? fieldParts[2] : null); + + // Get metadata values without virtual metadata + List metadataValues = itemService.getMetadata(item, schema, element, qualifier, Item.ANY, + false); for (MetadataValue metadataValue : metadataValues) { String content = metadataValue.getValue(); String authorityKey = metadataValue.getAuthority(); diff --git a/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java b/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java index a99d83764a..932cd71744 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java @@ -693,7 +693,7 @@ public class AuthorizeServiceImpl implements AuthorizeService { if (!duplicates.isEmpty()) { policy = duplicates.get(0); } - } else { + } else if (group != null) { // if an identical policy (same Action and same Group) is already in place modify it... policyTemp = findByTypeGroupAction(context, dso, group, action); } diff --git a/dspace-api/src/main/java/org/dspace/authorize/PolicySet.java b/dspace-api/src/main/java/org/dspace/authorize/PolicySet.java index e22b8e9df0..23806b35dd 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/PolicySet.java +++ b/dspace-api/src/main/java/org/dspace/authorize/PolicySet.java @@ -137,7 +137,7 @@ public class PolicySet { * otherwise add to existing policies * @param clearOnly if non-null, only process bitstreams whose names contain filter * @param name policy name - * @param description policy descrption + * @param description policy description * @param startDate policy start date * @param endDate policy end date * @throws SQLException if database error diff --git a/dspace-api/src/main/java/org/dspace/authorize/RegexPasswordValidator.java b/dspace-api/src/main/java/org/dspace/authorize/RegexPasswordValidator.java index d12c3ba919..2cefec35b3 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/RegexPasswordValidator.java +++ b/dspace-api/src/main/java/org/dspace/authorize/RegexPasswordValidator.java @@ -17,7 +17,7 @@ import org.springframework.beans.factory.annotation.Autowired; /** * Implementation of {@link PasswordValidatorService} that verifies if the given - * passowrd matches the configured pattern. + * password matches the configured pattern. * * @author Luca Giamminonni (luca.giamminonni at 4science.it) */ diff --git a/dspace-api/src/main/java/org/dspace/authorize/ResourcePolicy.java b/dspace-api/src/main/java/org/dspace/authorize/ResourcePolicy.java index c781400bae..68a59d46ae 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/ResourcePolicy.java +++ b/dspace-api/src/main/java/org/dspace/authorize/ResourcePolicy.java @@ -9,29 +9,28 @@ package org.dspace.authorize; import java.util.Date; import java.util.Objects; -import javax.persistence.CascadeType; -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.Lob; -import javax.persistence.ManyToOne; -import javax.persistence.SequenceGenerator; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.CascadeType; +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.apache.solr.common.StringUtils; import org.dspace.content.DSpaceObject; import org.dspace.core.Context; +import org.dspace.core.HibernateProxyHelper; import org.dspace.core.ReloadableEntity; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; -import org.hibernate.annotations.Type; -import org.hibernate.proxy.HibernateProxyHelper; +import org.hibernate.Length; /** * Database entity representation of the ResourcePolicy table @@ -99,9 +98,7 @@ public class ResourcePolicy implements ReloadableEntity { @Column(name = "rptype", length = 30) private String rptype; - @Lob - @Type(type = "org.hibernate.type.TextType") - @Column(name = "rpdescription") + @Column(name = "rpdescription", length = Length.LONG32) private String rpdescription; /** diff --git a/dspace-api/src/main/java/org/dspace/authorize/ResourcePolicyServiceImpl.java b/dspace-api/src/main/java/org/dspace/authorize/ResourcePolicyServiceImpl.java index 7b93b91237..86998a2196 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/ResourcePolicyServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/authorize/ResourcePolicyServiceImpl.java @@ -417,7 +417,7 @@ public class ResourcePolicyServiceImpl implements ResourcePolicyService { ResourcePolicy resourcePolicy = resourcePolicyDAO.findOneById(context, id); Group group = resourcePolicy.getGroup(); - if (resourcePolicy.getEPerson() != null && resourcePolicy.getEPerson().getID() == eperson.getID()) { + if (resourcePolicy.getEPerson() != null && resourcePolicy.getEPerson().getID().equals(eperson.getID())) { isMy = true; } else if (group != null && groupService.isMember(context, eperson, group)) { isMy = true; diff --git a/dspace-api/src/main/java/org/dspace/authorize/dao/impl/ResourcePolicyDAOImpl.java b/dspace-api/src/main/java/org/dspace/authorize/dao/impl/ResourcePolicyDAOImpl.java index 26b6bb1d73..3b09f9cf30 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/dao/impl/ResourcePolicyDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/authorize/dao/impl/ResourcePolicyDAOImpl.java @@ -8,16 +8,19 @@ package org.dspace.authorize.dao.impl; import java.sql.SQLException; +import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.UUID; -import javax.persistence.Query; -import javax.persistence.criteria.CriteriaBuilder; -import javax.persistence.criteria.CriteriaQuery; -import javax.persistence.criteria.Join; -import javax.persistence.criteria.Order; -import javax.persistence.criteria.Root; +import jakarta.persistence.Query; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Join; +import jakarta.persistence.criteria.Order; +import jakarta.persistence.criteria.Predicate; +import jakarta.persistence.criteria.Root; +import org.apache.commons.collections.CollectionUtils; import org.dspace.authorize.ResourcePolicy; import org.dspace.authorize.ResourcePolicy_; import org.dspace.authorize.dao.ResourcePolicyDAO; @@ -153,16 +156,30 @@ public class ResourcePolicyDAOImpl extends AbstractHibernateDAO public List findByEPersonGroupTypeIdAction(Context context, EPerson e, List groups, int action, int type_id) throws SQLException { + // If groups and eperson are empty, return immediately + if (CollectionUtils.isEmpty(groups) && e == null) { + return Collections.emptyList(); + } + CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, ResourcePolicy.class); Root resourcePolicyRoot = criteriaQuery.from(ResourcePolicy.class); criteriaQuery.select(resourcePolicyRoot); + + // Determine which predicate to use to match EPerson or Group(s) based on which were specified in params + Predicate compareEpersonOrGroups = + (CollectionUtils.isNotEmpty(groups) && e != null) ? + // Both are non-empty, so check both via an OR clause + criteriaBuilder.or(criteriaBuilder.equal(resourcePolicyRoot.get(ResourcePolicy_.eperson), e), + resourcePolicyRoot.get(ResourcePolicy_.epersonGroup).in(groups)) : + // Otherwise only check one based on which is non-empty + (e != null ? criteriaBuilder.equal(resourcePolicyRoot.get(ResourcePolicy_.eperson), e) : + resourcePolicyRoot.get(ResourcePolicy_.epersonGroup).in(groups)); + criteriaQuery.where( criteriaBuilder.and(criteriaBuilder.equal(resourcePolicyRoot.get(ResourcePolicy_.resourceTypeId), type_id), criteriaBuilder.equal(resourcePolicyRoot.get(ResourcePolicy_.actionId), action), - criteriaBuilder - .or(criteriaBuilder.equal(resourcePolicyRoot.get(ResourcePolicy_.eperson), e), - (resourcePolicyRoot.get(ResourcePolicy_.epersonGroup).in(groups))) + compareEpersonOrGroups ) ); return list(context, criteriaQuery, false, ResourcePolicy.class, 1, -1); @@ -286,8 +303,8 @@ public class ResourcePolicyDAOImpl extends AbstractHibernateDAO @Override public int countByEPerson(Context context, EPerson ePerson) throws SQLException { Query query = createQuery(context, - "SELECT count(*) FROM " + ResourcePolicy.class.getSimpleName() + " WHERE eperson_id = (:epersonUuid) "); - query.setParameter("epersonUuid", ePerson.getID()); + "SELECT count(*) FROM " + ResourcePolicy.class.getSimpleName() + " WHERE eperson = :eperson "); + query.setParameter("eperson", ePerson); return count(query); } @@ -307,9 +324,9 @@ public class ResourcePolicyDAOImpl extends AbstractHibernateDAO @Override public int countByEPersonAndResourceUuid(Context context, EPerson eperson, UUID resourceUuid) throws SQLException { Query query = createQuery(context, "SELECT count(*) FROM " + ResourcePolicy.class.getSimpleName() - + " WHERE eperson_id = (:epersonUuid) AND dspace_object = (:resourceUuid) "); + + " WHERE eperson = :eperson AND dSpaceObject.id = :resourceUuid "); query.setParameter("resourceUuid", resourceUuid); - query.setParameter("epersonUuid", eperson.getID()); + query.setParameter("eperson", eperson); return count(query); } @@ -329,7 +346,7 @@ public class ResourcePolicyDAOImpl extends AbstractHibernateDAO @Override public int countByResouceUuidAndActionId(Context context, UUID resourceUuid, int actionId) throws SQLException { Query query = createQuery(context, "SELECT count(*) FROM " + ResourcePolicy.class.getSimpleName() - + " WHERE dspace_object = (:resourceUuid) AND action_id = (:actionId) "); + + " WHERE dSpaceObject.id = :resourceUuid AND actionId = :actionId "); query.setParameter("resourceUuid", resourceUuid); query.setParameter("actionId", actionId); return count(query); @@ -349,7 +366,7 @@ public class ResourcePolicyDAOImpl extends AbstractHibernateDAO @Override public int countByResourceUuid(Context context, UUID resourceUuid) throws SQLException { Query query = createQuery(context, "SELECT count(*) FROM " + ResourcePolicy.class.getSimpleName() - + " WHERE dspace_object = (:resourceUuid) "); + + " WHERE dSpaceObject.id = :resourceUuid "); query.setParameter("resourceUuid", resourceUuid); return count(query); } @@ -367,8 +384,8 @@ public class ResourcePolicyDAOImpl extends AbstractHibernateDAO @Override public int countResourcePolicyByGroup(Context context, Group group) throws SQLException { Query query = createQuery(context, "SELECT count(*) " + "FROM " + ResourcePolicy.class.getSimpleName() - + " WHERE epersongroup_id = (:groupUuid) "); - query.setParameter("groupUuid", group.getID()); + + " WHERE epersonGroup = :group "); + query.setParameter("group", group); return count(query); } @@ -388,9 +405,9 @@ public class ResourcePolicyDAOImpl extends AbstractHibernateDAO @Override public int countByGroupAndResourceUuid(Context context, Group group, UUID resourceUuid) throws SQLException { Query query = createQuery(context, "SELECT count(*) FROM " + ResourcePolicy.class.getSimpleName() - + " WHERE dspace_object = (:resourceUuid) AND epersongroup_id = (:groupUuid) "); + + " WHERE dSpaceObject.id = :resourceUuid AND epersonGroup = :group "); query.setParameter("resourceUuid", resourceUuid); - query.setParameter("groupUuid", group.getID()); + query.setParameter("group", group); return count(query); } diff --git a/dspace-api/src/main/java/org/dspace/authorize/service/AuthorizeService.java b/dspace-api/src/main/java/org/dspace/authorize/service/AuthorizeService.java index e0a94833d7..9fb2e6d4a1 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/service/AuthorizeService.java +++ b/dspace-api/src/main/java/org/dspace/authorize/service/AuthorizeService.java @@ -133,7 +133,7 @@ public interface AuthorizeService { public boolean authorizeActionBoolean(Context c, DSpaceObject o, int a, boolean useInheritance) throws SQLException; /** - * same authorize with a specif eperson (not the current user), returns boolean for those who don't want to deal + * same authorize with a specific eperson (not the current user), returns boolean for those who don't want to deal * with * catching exceptions. * @@ -235,7 +235,7 @@ public interface AuthorizeService { * @param o DSpaceObject to add policy to * @param actionID ID of action from org.dspace.core.Constants * @param e eperson who can perform the action - * @param type policy type, deafult types are declared in the ResourcePolicy class + * @param type policy type, default types are declared in the ResourcePolicy class * @throws SQLException if database error * @throws AuthorizeException if current user in context is not authorized to add policies */ @@ -261,7 +261,7 @@ public interface AuthorizeService { * @param o object to add policy for * @param actionID ID of action from org.dspace.core.Constants * @param g group to add policy for - * @param type policy type, deafult types are declared in the ResourcePolicy class + * @param type policy type, default types are declared in the ResourcePolicy class * @throws SQLException if there's a database problem * @throws AuthorizeException if the current user is not authorized to add this policy */ diff --git a/dspace-api/src/main/java/org/dspace/browse/BrowseIndex.java b/dspace-api/src/main/java/org/dspace/browse/BrowseIndex.java index 6c38c8dd66..0eddfcac91 100644 --- a/dspace-api/src/main/java/org/dspace/browse/BrowseIndex.java +++ b/dspace-api/src/main/java/org/dspace/browse/BrowseIndex.java @@ -195,7 +195,7 @@ public class BrowseIndex { } } - // for backward compatability we ignore the keywords + // for backward compatibility we ignore the keywords // single and full here if (!sortName.equalsIgnoreCase("single") && !sortName.equalsIgnoreCase("full") @@ -597,7 +597,7 @@ public class BrowseIndex { /** * Is the browse index of display type single? * - * @return true if singe, false if not + * @return true if single, false if not */ public boolean isMetadataIndex() { return displayType != null && displayType.startsWith("metadata"); diff --git a/dspace-api/src/main/java/org/dspace/browse/CrossLinks.java b/dspace-api/src/main/java/org/dspace/browse/CrossLinks.java index ec4cb199ea..f78070d4f6 100644 --- a/dspace-api/src/main/java/org/dspace/browse/CrossLinks.java +++ b/dspace-api/src/main/java/org/dspace/browse/CrossLinks.java @@ -100,7 +100,7 @@ public class CrossLinks { // Resolve wildcards properly, eg. dc.subject.other matches a configuration for dc.subject.* for (String key : links.keySet()) { if (null != key && key.endsWith(".*")) { - // A substring of length-1, also substracting the wildcard should work as a "startsWith" + // A substring of length-1, also subtracting the wildcard should work as a "startsWith" // check for the field eg. dc.subject.* -> dc.subject is the start of dc.subject.other if (null != metadata && metadata.startsWith(key.substring(0, key.length() - 1 - ".*".length()))) { return links.get(key); diff --git a/dspace-api/src/main/java/org/dspace/checker/ChecksumHistory.java b/dspace-api/src/main/java/org/dspace/checker/ChecksumHistory.java index 63779cda02..521bf546a4 100644 --- a/dspace-api/src/main/java/org/dspace/checker/ChecksumHistory.java +++ b/dspace-api/src/main/java/org/dspace/checker/ChecksumHistory.java @@ -8,19 +8,19 @@ package org.dspace.checker; 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.core.Context; import org.dspace.core.ReloadableEntity; diff --git a/dspace-api/src/main/java/org/dspace/checker/ChecksumResult.java b/dspace-api/src/main/java/org/dspace/checker/ChecksumResult.java index 57fcdb8e36..a634880777 100644 --- a/dspace-api/src/main/java/org/dspace/checker/ChecksumResult.java +++ b/dspace-api/src/main/java/org/dspace/checker/ChecksumResult.java @@ -8,12 +8,13 @@ package org.dspace.checker; import java.io.Serializable; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.Id; -import javax.persistence.Table; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.Id; +import jakarta.persistence.Table; /** * Database entity representation of the checksum_results table diff --git a/dspace-api/src/main/java/org/dspace/checker/DailyReportEmailer.java b/dspace-api/src/main/java/org/dspace/checker/DailyReportEmailer.java index b291232e8b..50ef4baa98 100644 --- a/dspace-api/src/main/java/org/dspace/checker/DailyReportEmailer.java +++ b/dspace-api/src/main/java/org/dspace/checker/DailyReportEmailer.java @@ -13,8 +13,8 @@ import java.io.IOException; import java.sql.SQLException; import java.util.Date; import java.util.GregorianCalendar; -import javax.mail.MessagingException; +import jakarta.mail.MessagingException; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.DefaultParser; @@ -63,7 +63,7 @@ public class DailyReportEmailer { * @throws MessagingException if message cannot be sent. */ public void sendReport(File attachment, int numberOfBitstreams) - throws IOException, javax.mail.MessagingException { + throws IOException, jakarta.mail.MessagingException { if (numberOfBitstreams > 0) { ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); diff --git a/dspace-api/src/main/java/org/dspace/checker/LimitedCountDispatcher.java b/dspace-api/src/main/java/org/dspace/checker/LimitedCountDispatcher.java index 93ce634a05..36fcefd47b 100644 --- a/dspace-api/src/main/java/org/dspace/checker/LimitedCountDispatcher.java +++ b/dspace-api/src/main/java/org/dspace/checker/LimitedCountDispatcher.java @@ -60,7 +60,7 @@ public class LimitedCountDispatcher implements BitstreamDispatcher { } /** - * Retreives the next bitstream to be checked. + * Retrieves the next bitstream to be checked. * * @return the bitstream * @throws SQLException if database error diff --git a/dspace-api/src/main/java/org/dspace/checker/MostRecentChecksum.java b/dspace-api/src/main/java/org/dspace/checker/MostRecentChecksum.java index eff8a8be1c..5cb1851e7c 100644 --- a/dspace-api/src/main/java/org/dspace/checker/MostRecentChecksum.java +++ b/dspace-api/src/main/java/org/dspace/checker/MostRecentChecksum.java @@ -9,16 +9,16 @@ package org.dspace.checker; import java.io.Serializable; import java.util.Date; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Id; -import javax.persistence.JoinColumn; -import javax.persistence.OneToOne; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; -import javax.persistence.Transient; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.OneToOne; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; +import jakarta.persistence.Transient; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.dspace.content.Bitstream; diff --git a/dspace-api/src/main/java/org/dspace/checker/dao/impl/ChecksumHistoryDAOImpl.java b/dspace-api/src/main/java/org/dspace/checker/dao/impl/ChecksumHistoryDAOImpl.java index 328d4a717e..44c594d0eb 100644 --- a/dspace-api/src/main/java/org/dspace/checker/dao/impl/ChecksumHistoryDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/checker/dao/impl/ChecksumHistoryDAOImpl.java @@ -9,9 +9,9 @@ package org.dspace.checker.dao.impl; import java.sql.SQLException; import java.util.Date; -import javax.persistence.Query; -import javax.persistence.TemporalType; +import jakarta.persistence.Query; +import jakarta.persistence.TemporalType; import org.dspace.checker.ChecksumHistory; import org.dspace.checker.ChecksumResultCode; import org.dspace.checker.dao.ChecksumHistoryDAO; diff --git a/dspace-api/src/main/java/org/dspace/checker/dao/impl/ChecksumResultDAOImpl.java b/dspace-api/src/main/java/org/dspace/checker/dao/impl/ChecksumResultDAOImpl.java index 7552c6d5bb..ac882e971d 100644 --- a/dspace-api/src/main/java/org/dspace/checker/dao/impl/ChecksumResultDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/checker/dao/impl/ChecksumResultDAOImpl.java @@ -8,10 +8,10 @@ package org.dspace.checker.dao.impl; import java.sql.SQLException; -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.checker.ChecksumResult; import org.dspace.checker.ChecksumResultCode; import org.dspace.checker.ChecksumResult_; diff --git a/dspace-api/src/main/java/org/dspace/checker/dao/impl/MostRecentChecksumDAOImpl.java b/dspace-api/src/main/java/org/dspace/checker/dao/impl/MostRecentChecksumDAOImpl.java index a31e02cbab..669621aeeb 100644 --- a/dspace-api/src/main/java/org/dspace/checker/dao/impl/MostRecentChecksumDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/checker/dao/impl/MostRecentChecksumDAOImpl.java @@ -11,14 +11,14 @@ import java.sql.SQLException; import java.util.Date; import java.util.LinkedList; import java.util.List; -import javax.persistence.Query; -import javax.persistence.criteria.CriteriaBuilder; -import javax.persistence.criteria.CriteriaQuery; -import javax.persistence.criteria.Join; -import javax.persistence.criteria.Order; -import javax.persistence.criteria.Root; -import javax.persistence.criteria.Subquery; +import jakarta.persistence.Query; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Join; +import jakarta.persistence.criteria.Order; +import jakarta.persistence.criteria.Root; +import jakarta.persistence.criteria.Subquery; import org.dspace.checker.ChecksumHistory; import org.dspace.checker.ChecksumHistory_; import org.dspace.checker.ChecksumResult; diff --git a/dspace-api/src/main/java/org/dspace/coarnotify/NotifyConfigurationService.java b/dspace-api/src/main/java/org/dspace/coarnotify/NotifyConfigurationService.java new file mode 100644 index 0000000000..c9d65186b6 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/coarnotify/NotifyConfigurationService.java @@ -0,0 +1,34 @@ +/** + * 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.coarnotify; + +import java.util.List; +import java.util.Map; + +/** + * Simple bean to manage different COAR Notify configuration + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + */ +public class NotifyConfigurationService { + + /** + * Mapping the submission step process identifier with the configuration + * (see configuration at coar-notify.xml) + */ + private Map> patterns; + + public Map> getPatterns() { + return patterns; + } + + public void setPatterns(Map> patterns) { + this.patterns = patterns; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/coarnotify/NotifyPattern.java b/dspace-api/src/main/java/org/dspace/coarnotify/NotifyPattern.java new file mode 100644 index 0000000000..d678aa0523 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/coarnotify/NotifyPattern.java @@ -0,0 +1,44 @@ +/** + * 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.coarnotify; + +/** + * A collection of configured patterns to be met when adding COAR Notify services. + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + */ +public class NotifyPattern { + + private String pattern; + private boolean multipleRequest; + + public NotifyPattern() { + + } + + public NotifyPattern(String pattern, boolean multipleRequest) { + this.pattern = pattern; + this.multipleRequest = multipleRequest; + } + + public String getPattern() { + return pattern; + } + + public void setPattern(String pattern) { + this.pattern = pattern; + } + + public boolean isMultipleRequest() { + return multipleRequest; + } + + public void setMultipleRequest(boolean multipleRequest) { + this.multipleRequest = multipleRequest; + } +} diff --git a/dspace-api/src/main/java/org/dspace/coarnotify/NotifySubmissionConfiguration.java b/dspace-api/src/main/java/org/dspace/coarnotify/NotifySubmissionConfiguration.java new file mode 100644 index 0000000000..ee23ee3465 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/coarnotify/NotifySubmissionConfiguration.java @@ -0,0 +1,65 @@ +/** + * 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.coarnotify; + +import java.util.List; + +/** + * this class represents the Configuration of Submission COAR Notify + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + */ +public class NotifySubmissionConfiguration { + + /** + * the map key of configured bean of COARNotifyConfigurationService + * in coar-notify.xml + */ + private String id; + + /** + * the map values of configured bean of COARNotifyConfigurationService + * in coar-notify.xml + */ + private List patterns; + + public NotifySubmissionConfiguration() { + + } + + public NotifySubmissionConfiguration(String id, List patterns) { + super(); + this.id = id; + this.patterns = patterns; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + /** + * Gets the list of configured COAR Notify Patterns + * + * @return the list of configured COAR Notify Patterns + */ + public List getPatterns() { + return patterns; + } + + /** + * Sets the list of configured COAR Notify Patterns + * @param patterns + */ + public void setPatterns(final List patterns) { + this.patterns = patterns; + } +} diff --git a/dspace-api/src/main/java/org/dspace/coarnotify/SubmissionNotifyServiceImpl.java b/dspace-api/src/main/java/org/dspace/coarnotify/SubmissionNotifyServiceImpl.java new file mode 100644 index 0000000000..afb771529f --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/coarnotify/SubmissionNotifyServiceImpl.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.coarnotify; + +import java.util.ArrayList; +import java.util.List; + +import org.dspace.coarnotify.service.SubmissionNotifyService; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Service implementation of {@link SubmissionNotifyService} + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + */ +public class SubmissionNotifyServiceImpl implements SubmissionNotifyService { + + @Autowired(required = true) + private NotifyConfigurationService coarNotifyConfigurationService; + + protected SubmissionNotifyServiceImpl() { + + } + + @Override + public NotifySubmissionConfiguration findOne(String id) { + List patterns = + coarNotifyConfigurationService.getPatterns().get(id); + + if (patterns == null) { + return null; + } + + return new NotifySubmissionConfiguration(id, patterns); + } + + @Override + public List findAll() { + List coarNotifies = new ArrayList<>(); + + coarNotifyConfigurationService.getPatterns().forEach((id, patterns) -> + coarNotifies.add(new NotifySubmissionConfiguration(id, patterns) + )); + + return coarNotifies; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/coarnotify/service/SubmissionNotifyService.java b/dspace-api/src/main/java/org/dspace/coarnotify/service/SubmissionNotifyService.java new file mode 100644 index 0000000000..43f3ea1733 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/coarnotify/service/SubmissionNotifyService.java @@ -0,0 +1,39 @@ +/** + * 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.coarnotify.service; + +import java.util.List; + +import org.dspace.coarnotify.NotifySubmissionConfiguration; + +/** + * Service interface class for the Creative Submission COAR Notify. + * The implementation of this class is responsible for all business logic calls for the Creative Submission COAR Notify + * and is autowired by spring + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + */ +public interface SubmissionNotifyService { + + /** + * Find the COARE Notify corresponding to the provided ID + * found in the configuration + * + * @param id - the ID of the COAR Notify to be found + * @return the corresponding COAR Notify if found or null when not found + */ + public NotifySubmissionConfiguration findOne(String id); + + /** + * Find all configured COAR Notifies + * + * @return all configured COAR Notifies + */ + public List findAll(); + +} diff --git a/dspace-api/src/main/java/org/dspace/content/Bitstream.java b/dspace-api/src/main/java/org/dspace/content/Bitstream.java index 5485735a28..3fdb6316b2 100644 --- a/dspace-api/src/main/java/org/dspace/content/Bitstream.java +++ b/dspace-api/src/main/java/org/dspace/content/Bitstream.java @@ -11,21 +11,21 @@ import java.io.InputStream; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.JoinColumn; -import javax.persistence.ManyToMany; -import javax.persistence.ManyToOne; -import javax.persistence.OneToOne; -import javax.persistence.Table; -import javax.persistence.Transient; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToMany; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToOne; +import jakarta.persistence.Table; +import jakarta.persistence.Transient; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.BitstreamService; import org.dspace.core.Constants; import org.dspace.core.Context; -import org.hibernate.proxy.HibernateProxyHelper; +import org.dspace.core.HibernateProxyHelper; /** * Class representing bitstreams stored in the DSpace system. diff --git a/dspace-api/src/main/java/org/dspace/content/BitstreamFormat.java b/dspace-api/src/main/java/org/dspace/content/BitstreamFormat.java index 6d64ee3073..4dacea0952 100644 --- a/dspace-api/src/main/java/org/dspace/content/BitstreamFormat.java +++ b/dspace-api/src/main/java/org/dspace/content/BitstreamFormat.java @@ -11,27 +11,28 @@ import java.io.Serializable; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; -import javax.persistence.CollectionTable; -import javax.persistence.Column; -import javax.persistence.ElementCollection; -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.SequenceGenerator; -import javax.persistence.Table; -import javax.persistence.Transient; +import jakarta.persistence.CollectionTable; +import jakarta.persistence.Column; +import jakarta.persistence.ElementCollection; +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.SequenceGenerator; +import jakarta.persistence.Table; +import jakarta.persistence.Transient; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.BitstreamFormatService; import org.dspace.core.Context; +import org.dspace.core.HibernateProxyHelper; import org.dspace.core.ReloadableEntity; import org.hibernate.annotations.Cascade; import org.hibernate.annotations.CollectionId; -import org.hibernate.annotations.Type; -import org.hibernate.proxy.HibernateProxyHelper; +import org.hibernate.annotations.CollectionIdJavaType; +import org.hibernate.type.descriptor.java.IntegerJavaType; /** * Class representing a particular bitstream format. @@ -55,8 +56,6 @@ public class BitstreamFormat implements Serializable, ReloadableEntity @Column(name = "short_description", length = 128, unique = true) private String shortDescription; - // @Column(name="description") -// @Lob //Generates a TEXT or LONGTEXT data type @Column(name = "description", columnDefinition = "text") private String description; @@ -73,10 +72,10 @@ public class BitstreamFormat implements Serializable, ReloadableEntity @ElementCollection(fetch = FetchType.EAGER) @CollectionTable(name = "fileextension", joinColumns = @JoinColumn(name = "bitstream_format_id")) @CollectionId( - columns = @Column(name = "file_extension_id"), - type = @Type(type = "integer"), + column = @Column(name = "file_extension_id"), generator = "fileextension_seq" ) + @CollectionIdJavaType(IntegerJavaType.class) @SequenceGenerator(name = "fileextension_seq", sequenceName = "fileextension_seq", allocationSize = 1) @Column(name = "extension") @Cascade( {org.hibernate.annotations.CascadeType.ALL, org.hibernate.annotations.CascadeType.DELETE_ORPHAN}) diff --git a/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java index e23e5ce2c8..bd56ad4651 100644 --- a/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java @@ -14,8 +14,8 @@ import java.util.Iterator; import java.util.List; import java.util.UUID; import java.util.regex.Pattern; -import javax.annotation.Nullable; +import jakarta.annotation.Nullable; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; diff --git a/dspace-api/src/main/java/org/dspace/content/Bundle.java b/dspace-api/src/main/java/org/dspace/content/Bundle.java index e5cbdb6ff2..b619d5cd06 100644 --- a/dspace-api/src/main/java/org/dspace/content/Bundle.java +++ b/dspace-api/src/main/java/org/dspace/content/Bundle.java @@ -10,22 +10,22 @@ package org.dspace.content; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.JoinColumn; -import javax.persistence.JoinTable; -import javax.persistence.ManyToMany; -import javax.persistence.OneToOne; -import javax.persistence.OrderColumn; -import javax.persistence.Table; -import javax.persistence.Transient; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.JoinTable; +import jakarta.persistence.ManyToMany; +import jakarta.persistence.OneToOne; +import jakarta.persistence.OrderColumn; +import jakarta.persistence.Table; +import jakarta.persistence.Transient; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.BundleService; import org.dspace.core.Constants; import org.dspace.core.Context; -import org.hibernate.proxy.HibernateProxyHelper; +import org.dspace.core.HibernateProxyHelper; /** * Class representing bundles of bitstreams stored in the DSpace system @@ -131,7 +131,7 @@ public class Bundle extends DSpaceObject implements DSpaceObjectLegacySupport { /** * Get a copy of the bitstream list of this bundle - * Note that this is a copy and if you wish to manipulate the bistream list, you should use + * Note that this is a copy and if you wish to manipulate the bitstream list, you should use * {@ref Bundle.addBitstream}, {@ref Bundle.removeBitstream} or {@ref Bundle.clearBitstreams} * * @return the bitstreams diff --git a/dspace-api/src/main/java/org/dspace/content/CacheableDSpaceObject.java b/dspace-api/src/main/java/org/dspace/content/CacheableDSpaceObject.java new file mode 100644 index 0000000000..fa6ec7676a --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/CacheableDSpaceObject.java @@ -0,0 +1,20 @@ +/** + * 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.content; + +import jakarta.persistence.Cacheable; +import org.hibernate.annotations.CacheConcurrencyStrategy; + +/** + * Abstract class for DSpaceObjects which are safe to cache in Hibernate's second level cache. + * See hibernate-ehcache-config.xml for caching configurations for each DSpaceObject which extends this class. + */ +@Cacheable +@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE, include = "non-lazy") +public abstract class CacheableDSpaceObject extends DSpaceObject { +} diff --git a/dspace-api/src/main/java/org/dspace/content/Collection.java b/dspace-api/src/main/java/org/dspace/content/Collection.java index 8900ac21b7..8c27c5a4ae 100644 --- a/dspace-api/src/main/java/org/dspace/content/Collection.java +++ b/dspace-api/src/main/java/org/dspace/content/Collection.java @@ -15,28 +15,26 @@ import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.UUID; -import javax.annotation.Nonnull; -import javax.persistence.Cacheable; -import javax.persistence.CascadeType; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.JoinColumn; -import javax.persistence.JoinTable; -import javax.persistence.ManyToMany; -import javax.persistence.OneToOne; -import javax.persistence.Table; -import javax.persistence.Transient; +import jakarta.annotation.Nonnull; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.JoinTable; +import jakarta.persistence.ManyToMany; +import jakarta.persistence.OneToOne; +import jakarta.persistence.Table; +import jakarta.persistence.Transient; import org.dspace.authorize.AuthorizeException; import org.dspace.content.comparator.NameAscendingComparator; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.CollectionService; import org.dspace.core.Constants; import org.dspace.core.Context; +import org.dspace.core.HibernateProxyHelper; import org.dspace.eperson.Group; -import org.hibernate.annotations.CacheConcurrencyStrategy; -import org.hibernate.proxy.HibernateProxyHelper; /** * Class representing a collection. @@ -53,9 +51,7 @@ import org.hibernate.proxy.HibernateProxyHelper; */ @Entity @Table(name = "collection") -@Cacheable -@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE, include = "non-lazy") -public class Collection extends DSpaceObject implements DSpaceObjectLegacySupport { +public class Collection extends CacheableDSpaceObject implements DSpaceObjectLegacySupport { @Column(name = "collection_id", insertable = false, updatable = false) private Integer legacyId; @@ -159,7 +155,7 @@ public class Collection extends DSpaceObject implements DSpaceObjectLegacySuppor /** * Set the default group of submitters * - * Package protected in order to preven unauthorized calls to this method + * Package protected in order to prevent unauthorized calls to this method * * @param submitters the group of submitters */ @@ -233,7 +229,7 @@ public class Collection extends DSpaceObject implements DSpaceObjectLegacySuppor * @throws SQLException if database error */ public void setLicense(Context context, String license) throws SQLException { - getCollectionService().setMetadataSingleValue(context, this, MD_LICENSE, Item.ANY, license); + getCollectionService().setMetadataSingleValue(context, this, MD_LICENSE, null, license); } /** diff --git a/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java index 5fe8ca54d9..b800ce21a9 100644 --- a/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java @@ -1028,6 +1028,61 @@ public class CollectionServiceImpl extends DSpaceObjectServiceImpl i return resp; } + @Override + public Collection retrieveCollectionWithSubmitByEntityType(Context context, Item item, + String entityType) throws SQLException { + Collection ownCollection = item.getOwningCollection(); + return retrieveWithSubmitCollectionByEntityType(context, ownCollection.getCommunities(), entityType); + } + + private Collection retrieveWithSubmitCollectionByEntityType(Context context, List communities, + String entityType) { + + for (Community community : communities) { + Collection collection = retrieveCollectionWithSubmitByCommunityAndEntityType(context, community, + entityType); + if (collection != null) { + return collection; + } + } + + for (Community community : communities) { + List parentCommunities = community.getParentCommunities(); + Collection collection = retrieveWithSubmitCollectionByEntityType(context, parentCommunities, entityType); + if (collection != null) { + return collection; + } + } + + return retrieveCollectionWithSubmitByCommunityAndEntityType(context, null, entityType); + } + + @Override + public Collection retrieveCollectionWithSubmitByCommunityAndEntityType(Context context, Community community, + String entityType) { + context.turnOffAuthorisationSystem(); + List collections; + try { + collections = findCollectionsWithSubmit(null, context, community, entityType, 0, 1); + } catch (SQLException | SearchServiceException e) { + throw new RuntimeException(e); + } + context.restoreAuthSystemState(); + if (collections != null && collections.size() > 0) { + return collections.get(0); + } + if (community != null) { + for (Community subCommunity : community.getSubcommunities()) { + Collection collection = retrieveCollectionWithSubmitByCommunityAndEntityType(context, + subCommunity, entityType); + if (collection != null) { + return collection; + } + } + } + return null; + } + @Override public List findCollectionsWithSubmit(String q, Context context, Community community, String entityType, int offset, int limit) throws SQLException, SearchServiceException { diff --git a/dspace-api/src/main/java/org/dspace/content/Community.java b/dspace-api/src/main/java/org/dspace/content/Community.java index 4503b24cfa..7f362d2f16 100644 --- a/dspace-api/src/main/java/org/dspace/content/Community.java +++ b/dspace-api/src/main/java/org/dspace/content/Community.java @@ -12,27 +12,26 @@ import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.UUID; -import javax.persistence.Cacheable; -import javax.persistence.CascadeType; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.JoinColumn; -import javax.persistence.JoinTable; -import javax.persistence.ManyToMany; -import javax.persistence.OneToOne; -import javax.persistence.Table; -import javax.persistence.Transient; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.JoinTable; +import jakarta.persistence.ManyToMany; +import jakarta.persistence.OneToOne; +import jakarta.persistence.Table; +import jakarta.persistence.Transient; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.dspace.content.comparator.NameAscendingComparator; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.CommunityService; import org.dspace.core.Constants; import org.dspace.core.Context; +import org.dspace.core.HibernateProxyHelper; import org.dspace.eperson.Group; -import org.hibernate.annotations.CacheConcurrencyStrategy; -import org.hibernate.proxy.HibernateProxyHelper; + /** * Class representing a community @@ -45,9 +44,7 @@ import org.hibernate.proxy.HibernateProxyHelper; */ @Entity @Table(name = "community") -@Cacheable -@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE, include = "non-lazy") -public class Community extends DSpaceObject implements DSpaceObjectLegacySupport { +public class Community extends CacheableDSpaceObject implements DSpaceObjectLegacySupport { @Column(name = "community_id", insertable = false, updatable = false) private Integer legacyId; diff --git a/dspace-api/src/main/java/org/dspace/content/DCDate.java b/dspace-api/src/main/java/org/dspace/content/DCDate.java index d58aff7b1e..163e21cdc2 100644 --- a/dspace-api/src/main/java/org/dspace/content/DCDate.java +++ b/dspace-api/src/main/java/org/dspace/content/DCDate.java @@ -80,6 +80,9 @@ public class DCDate { // just year, "2009" private final SimpleDateFormat yearIso = new SimpleDateFormat("yyyy"); + // Additional iso-like format which contains milliseconds + private final SimpleDateFormat fullIsoWithMs = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'.000'"); + private static Map dfsLocaleMap = new HashMap(); /** @@ -193,6 +196,9 @@ public class DCDate { if (date == null) { date = tryParse(fullIso4, fromDC); } + if (date == null) { + date = tryParse(fullIsoWithMs, fromDC); + } if (date == null) { // Seems there is no time component to the date. date = tryParse(dateIso, fromDC); @@ -244,6 +250,7 @@ public class DCDate { dateIso.setTimeZone(utcZone); yearMonthIso.setTimeZone(utcZone); yearIso.setTimeZone(utcZone); + fullIsoWithMs.setTimeZone(utcZone); } // Attempt to parse, swallowing errors; return null for failure. diff --git a/dspace-api/src/main/java/org/dspace/content/DCPersonName.java b/dspace-api/src/main/java/org/dspace/content/DCPersonName.java index cb9b5346ff..e51882f82e 100644 --- a/dspace-api/src/main/java/org/dspace/content/DCPersonName.java +++ b/dspace-api/src/main/java/org/dspace/content/DCPersonName.java @@ -44,7 +44,7 @@ public class DCPersonName { * @param rawValue the value entry from the database */ public DCPersonName(String rawValue) { - // Null by default (representing noone) + // Null by default (representing no one) lastName = null; firstNames = null; diff --git a/dspace-api/src/main/java/org/dspace/content/DSpaceObject.java b/dspace-api/src/main/java/org/dspace/content/DSpaceObject.java index 59217a109f..b659035dc6 100644 --- a/dspace-api/src/main/java/org/dspace/content/DSpaceObject.java +++ b/dspace-api/src/main/java/org/dspace/content/DSpaceObject.java @@ -11,19 +11,19 @@ import java.io.Serializable; import java.util.ArrayList; import java.util.List; import java.util.UUID; -import javax.persistence.CascadeType; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.GeneratedValue; -import javax.persistence.Id; -import javax.persistence.Inheritance; -import javax.persistence.InheritanceType; -import javax.persistence.OneToMany; -import javax.persistence.OrderBy; -import javax.persistence.Table; -import javax.persistence.Transient; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.Inheritance; +import jakarta.persistence.InheritanceType; +import jakarta.persistence.OneToMany; +import jakarta.persistence.OrderBy; +import jakarta.persistence.Table; +import jakarta.persistence.Transient; import org.apache.commons.collections4.CollectionUtils; import org.dspace.authorize.ResourcePolicy; import org.dspace.core.ReloadableEntity; diff --git a/dspace-api/src/main/java/org/dspace/content/DSpaceObjectServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/DSpaceObjectServiceImpl.java index 4e3fa42162..eee858fbc3 100644 --- a/dspace-api/src/main/java/org/dspace/content/DSpaceObjectServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/DSpaceObjectServiceImpl.java @@ -323,7 +323,7 @@ public abstract class DSpaceObjectServiceImpl implements } } metadataValue.setValue(String.valueOf(dcvalue)); - //An update here isn't needed, this is persited upon the merge of the owning object + //An update here isn't needed, this is persisted upon the merge of the owning object // metadataValueService.update(context, metadataValue); dso.addDetails(metadataField.toString()); } @@ -656,6 +656,7 @@ public abstract class DSpaceObjectServiceImpl implements // E.g. for an Author relationship, // the place should be updated using the same principle as dc.contributor.author. StringUtils.startsWith(metadataValue.getAuthority(), Constants.VIRTUAL_AUTHORITY_PREFIX) + && metadataValue instanceof RelationshipMetadataValue && ((RelationshipMetadataValue) metadataValue).isUseForPlace() ) { int mvPlace = getMetadataValuePlace(fieldToLastPlace, metadataValue); diff --git a/dspace-api/src/main/java/org/dspace/content/DuplicateDetectionServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/DuplicateDetectionServiceImpl.java new file mode 100644 index 0000000000..9f52b7b63a --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/DuplicateDetectionServiceImpl.java @@ -0,0 +1,362 @@ +/** + * 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.content; + +import static java.util.Comparator.comparing; +import static java.util.Comparator.naturalOrder; + +import java.sql.SQLException; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.Optional; + +import org.apache.commons.lang3.StringUtils; +import org.apache.velocity.exception.ResourceNotFoundException; +import org.dspace.app.itemupdate.MetadataUtilities; +import org.dspace.authorize.AuthorizeException; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.content.service.DuplicateDetectionService; +import org.dspace.content.service.ItemService; +import org.dspace.content.service.MetadataFieldService; +import org.dspace.content.service.MetadataValueService; +import org.dspace.content.service.WorkspaceItemService; +import org.dspace.content.virtual.PotentialDuplicate; +import org.dspace.core.Constants; +import org.dspace.core.Context; +import org.dspace.discovery.DiscoverQuery; +import org.dspace.discovery.DiscoverResult; +import org.dspace.discovery.IndexableObject; +import org.dspace.discovery.SearchService; +import org.dspace.discovery.SearchServiceException; +import org.dspace.discovery.SearchUtils; +import org.dspace.discovery.indexobject.IndexableItem; +import org.dspace.discovery.indexobject.IndexableWorkflowItem; +import org.dspace.discovery.indexobject.IndexableWorkspaceItem; +import org.dspace.eperson.service.GroupService; +import org.dspace.services.ConfigurationService; +import org.dspace.versioning.VersionHistory; +import org.dspace.versioning.service.VersionHistoryService; +import org.dspace.workflow.WorkflowItem; +import org.dspace.xmlworkflow.storedcomponents.service.XmlWorkflowItemService; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Default implementation of DuplicateDetectionService. + * Duplicate Detection Service handles get, search and validation operations for duplicate detection. + * + * @author Kim Shepherd + */ +public class DuplicateDetectionServiceImpl implements DuplicateDetectionService { + + @Autowired + ConfigurationService configurationService; + @Autowired + VersionHistoryService versionHistoryService; + @Autowired + AuthorizeService authorizeService; + @Autowired + GroupService groupService; + @Autowired + MetadataFieldService metadataFieldService; + @Autowired + MetadataValueService metadataValueService; + @Autowired + XmlWorkflowItemService workflowItemService; + @Autowired + WorkspaceItemService workspaceItemService; + @Autowired + ItemService itemService; + + /** + * Get a list of PotentialDuplicate objects (wrappers with some metadata included for previewing) that + * are identified as potential duplicates of the given item + * + * @param context DSpace context + * @param item Item to check + * @return List of potential duplicates (empty if none found) + * @throws SearchServiceException if an error occurs performing the discovery search + */ + @Override + public List getPotentialDuplicates(Context context, Item item) + throws SearchServiceException { + // Instantiate a new list of potential duplicates + List potentialDuplicates = new LinkedList<>(); + + // Immediately return an empty if this feature is not configured + if (!configurationService.getBooleanProperty("duplicate.enable", false)) { + return potentialDuplicates; + } + + // Search duplicates of this item and get discovery search result + DiscoverResult discoverResult = searchDuplicates(context, item); + + // If the search result is valid, iterate results and validate / transform + if (discoverResult != null) { + for (IndexableObject result : discoverResult.getIndexableObjects()) { + if (result != null) { + try { + // Validate this result and check permissions to read the item + Optional potentialDuplicateOptional = + validateDuplicateResult(context, result, item); + if (potentialDuplicateOptional.isPresent()) { + // Add the potential duplicate to the list + potentialDuplicates.add(potentialDuplicateOptional.get()); + } + } catch (SQLException e) { + log.error("SQL Error obtaining duplicate result: " + e.getMessage()); + } catch (AuthorizeException e) { + log.error("Authorize Error obtaining duplicate result: " + e.getMessage()); + } + } + } + } + + // Return the list of potential duplicates + return potentialDuplicates; + } + + + + /** + * Validate an indexable object (returned by discovery search) to ensure it is permissible, readable and valid + * and can be added to a list of results. + * An Optional is returned, if it is empty then it was invalid or did not pass validation. + * + * @param context The DSpace context + * @param indexableObject The discovery search result + * @param original The original item (to compare IDs, submitters, etc) + * @return An Optional potential duplicate + * @throws SQLException + * @throws AuthorizeException + */ + @Override + public Optional validateDuplicateResult(Context context, IndexableObject indexableObject, + Item original) + throws SQLException, + AuthorizeException { + + Item resultItem = null; + PotentialDuplicate potentialDuplicate = null; + WorkspaceItem workspaceItem = null; + WorkflowItem workflowItem = null; + + // Inspect the indexable object, and extract the DSpace item depending on + // what submission / archived state it is in + if (indexableObject instanceof IndexableWorkspaceItem) { + workspaceItem = ((IndexableWorkspaceItem) indexableObject).getIndexedObject(); + // Only process workspace items that belong to the submitter + if (workspaceItem != null && workspaceItem.getSubmitter() != null + && workspaceItem.getSubmitter().equals(context.getCurrentUser())) { + resultItem = workspaceItem.getItem(); + } + } + if (indexableObject instanceof IndexableWorkflowItem) { + workflowItem = ((IndexableWorkflowItem) indexableObject).getIndexedObject(); + if (workflowItem != null) { + resultItem = workflowItem.getItem(); + } + } + if (indexableObject instanceof IndexableItem) { + resultItem = ((IndexableItem) indexableObject).getIndexedObject(); + // Attempt resolution of workflow or workspace items, tested later + workflowItem = workflowItemService.findByItem(context, resultItem); + workspaceItem = workspaceItemService.findByItem(context, resultItem); + } + + // Result item must not be null, a template item, or actually identical to the original + if (resultItem == null) { + log.warn("skipping null item in duplicate search results"); + return Optional.empty(); + } else if (resultItem.getTemplateItemOf() != null) { + log.info("skipping template item in duplicate search results, item={}", resultItem.getID()); + return Optional.empty(); + } else if (resultItem.getID().equals(original.getID())) { + log.info("skipping a duplicate search result for the original item", resultItem.getID()); + return Optional.empty(); + } + + // If our item and the duplicate candidate share the same versionHistory, they are two different + // versions of the same item. + VersionHistory versionHistory = versionHistoryService.findByItem(context, original); + VersionHistory candiateVersionHistory = versionHistoryService.findByItem(context, resultItem); + // if the versionHistory is null, either versioning is switched off or the item doesn't have + // multiple versions + if (versionHistory != null && versionHistory.equals(candiateVersionHistory)) { + log.warn("skipping item that is just another version of this item"); + return Optional.empty(); + } + + // Construct new potential duplicate object + potentialDuplicate = new PotentialDuplicate(resultItem); + + // Get configured list of metadata fields to copy + List fields = new ArrayList<>(Arrays.asList( + configurationService.getArrayProperty("duplicate.preview.metadata.field", new String[]{}))); + + // Get item metadata and if it's configured for mapping, copy it across to the potential duplicate object + List metadata = resultItem.getCachedMetadata(); + + // Prepare a map of metadata to set on the potential duplicate object + for (MetadataValue metadatum : metadata) { + String fieldName = metadatum.getMetadataField().toString('.'); + if (fields.contains(fieldName)) { + potentialDuplicate.getMetadataValueList().add(metadatum); + } + } + + // Only if the current user is also the submitter of the item will we add this information + if (workspaceItem != null && workspaceItem.getSubmitter() != null + && workspaceItem.getSubmitter().equals(context.getCurrentUser())) { + potentialDuplicate.setWorkspaceItemId(workspaceItem.getID()); + return Optional.of(potentialDuplicate); + } + + // More authorisation checks + if (workflowItem != null) { + Collection c = workflowItem.getCollection(); + if (groupService.isMember(context, context.getCurrentUser(), c.getWorkflowStep1(context)) || + groupService.isMember(context, context.getCurrentUser(), c.getWorkflowStep2(context)) || + groupService.isMember(context, context.getCurrentUser(), c.getWorkflowStep3(context))) { + // Current user is a member of one of the workflow role groups + potentialDuplicate.setWorkflowItemId(workflowItem.getID()); + return Optional.of(potentialDuplicate); + } + } else if (resultItem.isArchived() && !resultItem.isWithdrawn() && resultItem.isDiscoverable()) { + // Not a workspace or workflow item, but is it archived, not withdrawn, and discoverable? + // Is it readable by the current user? + if (authorizeService.authorizeActionBoolean(context, resultItem, Constants.READ)) { + return Optional.of(potentialDuplicate); + } + } else if (authorizeService.isAdmin(context, resultItem)) { + // Admins can always read, return immediately + return Optional.of(potentialDuplicate); + } else { + log.info("Potential duplicate result is not readable by the current user, skipping item={}", + potentialDuplicate.getUuid()); + } + + // By default, return an empty result + return Optional.empty(); + } + + /** + * Search discovery for potential duplicates of a given item. The search uses levenshtein distance (configurable) + * and a single-term "comparison value" constructed out of the item title + * + * @param context DSpace context + * @param item The item to check + * @return DiscoverResult as a result of performing search. Null if invalid. + * + * @throws SearchServiceException if an error was encountered during the discovery search itself. + */ + @Override + public DiscoverResult searchDuplicates(Context context, Item item) throws SearchServiceException { + + // If the item is null or otherwise invalid (template, etc) then throw an appropriate error + if (item == null) { + throw new ResourceNotFoundException("Duplicate search error: item is null"); + } + if (item.getTemplateItemOf() != null) { + throw new IllegalArgumentException("Cannot get duplicates for template item"); + } + + // Build normalised comparison value + String comparisonValue = buildComparisonValue(context, item); + + // Construct query + if (StringUtils.isNotBlank(comparisonValue)) { + // Get search service + SearchService searchService = SearchUtils.getSearchService(); + + // Escape reserved solr characters + comparisonValue = searchService.escapeQueryChars(comparisonValue); + + // Construct discovery query based on comparison value + DiscoverQuery discoverQuery = new DiscoverQuery(); + discoverQuery.setQuery("(" + configurationService.getProperty("duplicate.comparison.solr.field", + "deduplication_keyword") + ":" + comparisonValue + "~" + + configurationService.getIntProperty("duplicate.comparison.distance", 0) + ")"); + // Add filter queries for the resource type + discoverQuery.addFilterQueries("(search.resourcetype:Item OR " + + "search.resourcetype:WorkspaceItem OR " + + "search.resourcetype:XmlWorkflowItem OR search.resourcetype:WorkflowItem)"); + // Skip this item itself so it isn't a false positive + discoverQuery.addFilterQueries("-search.resourceid:" + item.getID()); + + // Perform search and populate list with results, update total count integer + return searchService.search(context, discoverQuery); + } else { + log.warn("empty item comparison value, ignoring for duplicate search"); + } + + // Return null by default + return null; + + } + + /** + * Build a comparison value string made up of values of configured fields, used when indexing and querying + * items for deduplication + * @param context DSpace context + * @param item The DSpace item + * @return a constructed, normalised string + */ + @Override + public String buildComparisonValue(Context context, Item item) { + // Get configured fields to use for comparison values + String[] comparisonFields = configurationService.getArrayProperty("duplicate.comparison.metadata.field", + new String[]{"dc.title"}); + // Get all values, in order, for these fields + StringBuilder comparisonValueBuilder = new StringBuilder(); + String comparisonValue = null; + for (String field : comparisonFields) { + try { + // Get field components + String[] fieldParts = MetadataUtilities.parseCompoundForm(field); + // Get all values of this field + List metadataValues = itemService.getMetadata(item, + fieldParts[0], fieldParts[1], (fieldParts.length > 2 ? fieldParts[2] : null), Item.ANY); + // Sort metadata values by text value, so their 'position' in db doesn't matter for dedupe purposes + metadataValues.sort(comparing(MetadataValue::getValue, naturalOrder())); + for (MetadataValue metadataValue : metadataValues) { + // Add each found value to the string builder (null values interpreted as empty) + if (metadataValue != null) { + comparisonValueBuilder.append(metadataValue.getValue()); + } + } + } catch (ParseException e) { + // Log error and continue processing + log.error("Error parsing configured field for deduplication comparison: item={}, field={}", + item.getID(), field); + } catch (NullPointerException e) { + log.error("Null pointer encountered, probably during metadata value sort, when deduping:" + + "item={}, field={}", item.getID(), field); + } + } + + // Build string + comparisonValue = comparisonValueBuilder.toString(); + + // Normalise according to configuration + if (!StringUtils.isBlank(comparisonValue)) { + if (configurationService.getBooleanProperty("duplicate.comparison.normalise.lowercase")) { + comparisonValue = comparisonValue.toLowerCase(context.getCurrentLocale()); + } + if (configurationService.getBooleanProperty("duplicate.comparison.normalise.whitespace")) { + comparisonValue = comparisonValue.replaceAll("\\s+", ""); + } + } + + // Return comparison value + return comparisonValue; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/content/EntityServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/EntityServiceImpl.java index 2f34129f2e..e831786678 100644 --- a/dspace-api/src/main/java/org/dspace/content/EntityServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/EntityServiceImpl.java @@ -8,6 +8,7 @@ package org.dspace.content; import java.sql.SQLException; +import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.UUID; @@ -51,12 +52,7 @@ public class EntityServiceImpl implements EntityService { @Override public EntityType getType(Context context, Entity entity) throws SQLException { Item item = entity.getItem(); - List list = itemService.getMetadata(item, "dspace", "entity", "type", Item.ANY, false); - if (!list.isEmpty()) { - return entityTypeService.findByEntityType(context, list.get(0).getValue()); - } else { - return null; - } + return itemService.getEntityType(context, item); } @Override @@ -64,7 +60,7 @@ public class EntityServiceImpl implements EntityService { List fullList = entity.getRelationships(); List listToReturn = new LinkedList<>(); for (Relationship relationship : fullList) { - if (relationship.getLeftItem().getID() == entity.getItem().getID()) { + if (relationship.getLeftItem().getID().equals(entity.getItem().getID())) { listToReturn.add(relationship); } } @@ -76,7 +72,7 @@ public class EntityServiceImpl implements EntityService { List fullList = entity.getRelationships(); List listToReturn = new LinkedList<>(); for (Relationship relationship : fullList) { - if (relationship.getRightItem().getID() == entity.getItem().getID()) { + if (relationship.getRightItem().getID().equals(entity.getItem().getID())) { listToReturn.add(relationship); } } @@ -103,7 +99,12 @@ public class EntityServiceImpl implements EntityService { @Override public List getAllRelationshipTypes(Context context, Entity entity, Integer limit, Integer offset) throws SQLException { - return relationshipTypeService.findByEntityType(context, this.getType(context, entity), limit, offset); + EntityType entityType = this.getType(context, entity); + if (entityType != null) { + return relationshipTypeService.findByEntityType(context, entityType, limit, offset); + } else { + return Collections.emptyList(); + } } @Override @@ -115,7 +116,12 @@ public class EntityServiceImpl implements EntityService { @Override public List getLeftRelationshipTypes(Context context, Entity entity, boolean isLeft, Integer limit, Integer offset) throws SQLException { - return relationshipTypeService.findByEntityType(context, this.getType(context, entity), isLeft, limit, offset); + EntityType entityType = this.getType(context, entity); + if (entityType != null) { + return relationshipTypeService.findByEntityType(context, entityType, isLeft, limit, offset); + } else { + return Collections.emptyList(); + } } @Override @@ -128,7 +134,12 @@ public class EntityServiceImpl implements EntityService { public List getRightRelationshipTypes(Context context, Entity entity, boolean isLeft, Integer limit, Integer offset) throws SQLException { - return relationshipTypeService.findByEntityType(context, this.getType(context, entity), isLeft, limit, offset); + EntityType entityType = this.getType(context, entity); + if (entityType != null) { + return relationshipTypeService.findByEntityType(context, entityType, isLeft, limit, offset); + } else { + return Collections.emptyList(); + } } @Override diff --git a/dspace-api/src/main/java/org/dspace/content/EntityType.java b/dspace-api/src/main/java/org/dspace/content/EntityType.java index 20ab758a0b..720e0c492c 100644 --- a/dspace-api/src/main/java/org/dspace/content/EntityType.java +++ b/dspace-api/src/main/java/org/dspace/content/EntityType.java @@ -8,14 +8,14 @@ package org.dspace.content; import java.util.Objects; -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 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 org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.dspace.core.ReloadableEntity; diff --git a/dspace-api/src/main/java/org/dspace/content/FeedbackServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/FeedbackServiceImpl.java index 7e34af132b..13f149f69f 100644 --- a/dspace-api/src/main/java/org/dspace/content/FeedbackServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/FeedbackServiceImpl.java @@ -9,9 +9,9 @@ package org.dspace.content; import java.io.IOException; import java.util.Date; import java.util.Objects; -import javax.mail.MessagingException; -import javax.servlet.http.HttpServletRequest; +import jakarta.mail.MessagingException; +import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.lang.StringUtils; import org.dspace.content.service.FeedbackService; import org.dspace.core.Context; diff --git a/dspace-api/src/main/java/org/dspace/content/InstallItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/InstallItemServiceImpl.java index 1aadbea162..23f30615d0 100644 --- a/dspace-api/src/main/java/org/dspace/content/InstallItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/InstallItemServiceImpl.java @@ -28,6 +28,7 @@ import org.dspace.event.Event; import org.dspace.identifier.Identifier; import org.dspace.identifier.IdentifierException; import org.dspace.identifier.service.IdentifierService; +import org.dspace.services.ConfigurationService; import org.dspace.supervision.SupervisionOrder; import org.dspace.supervision.service.SupervisionOrderService; import org.springframework.beans.factory.annotation.Autowired; @@ -56,6 +57,9 @@ public class InstallItemServiceImpl implements InstallItemService { Logger log = LogManager.getLogger(InstallItemServiceImpl.class); + @Autowired + protected ConfigurationService configurationService; + protected InstallItemServiceImpl() { } @@ -150,7 +154,6 @@ public class InstallItemServiceImpl implements InstallItemService { return finishItem(c, item, is); } - protected void populateMetadata(Context c, Item item) throws SQLException, AuthorizeException { // create accession date @@ -158,15 +161,6 @@ public class InstallItemServiceImpl implements InstallItemService { itemService.addMetadata(c, item, MetadataSchemaEnum.DC.getName(), "date", "accessioned", null, now.toString()); - // add date available if not under embargo, otherwise it will - // be set when the embargo is lifted. - // this will flush out fatal embargo metadata - // problems before we set inArchive. - if (embargoService.getEmbargoTermsAsDate(c, item) == null) { - itemService.addMetadata(c, item, MetadataSchemaEnum.DC.getName(), - "date", "available", null, now.toString()); - } - // If issue date is set as "today" (literal string), then set it to current date // In the below loop, we temporarily clear all issued dates and re-add, one-by-one, // replacing "today" with today's date. @@ -280,14 +274,20 @@ public class InstallItemServiceImpl implements InstallItemService { // Create provenance description StringBuffer provmessage = new StringBuffer(); - if (item.getSubmitter() != null) { + //behavior to generate provenance message, if set true, personal data (e.g. email) of submitter will be hidden + //default value false, personal data of submitter will be shown in provenance message + String isProvenancePrivacyActiveProperty = + configurationService.getProperty("metadata.privacy.dc.description.provenance", "false"); + boolean isProvenancePrivacyActive = Boolean.parseBoolean(isProvenancePrivacyActiveProperty); + + if (item.getSubmitter() != null && !isProvenancePrivacyActive) { provmessage.append("Submitted by ").append(item.getSubmitter().getFullName()) - .append(" (").append(item.getSubmitter().getEmail()).append(") on ") - .append(now.toString()); + .append(" (").append(item.getSubmitter().getEmail()).append(") on ") + .append(now.toString()); } else { // else, null submitter - provmessage.append("Submitted by unknown (probably automated) on") - .append(now.toString()); + provmessage.append("Submitted by unknown (probably automated or submitter hidden) on ") + .append(now.toString()); } provmessage.append("\n"); diff --git a/dspace-api/src/main/java/org/dspace/content/Item.java b/dspace-api/src/main/java/org/dspace/content/Item.java index 547ff490b8..5422528f84 100644 --- a/dspace-api/src/main/java/org/dspace/content/Item.java +++ b/dspace-api/src/main/java/org/dspace/content/Item.java @@ -14,27 +14,28 @@ import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.UUID; -import javax.persistence.CascadeType; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.JoinColumn; -import javax.persistence.JoinTable; -import javax.persistence.ManyToMany; -import javax.persistence.ManyToOne; -import javax.persistence.OneToOne; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; -import javax.persistence.Transient; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.JoinTable; +import jakarta.persistence.ManyToMany; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToOne; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; +import jakarta.persistence.Transient; import org.dspace.content.comparator.NameAscendingComparator; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.ItemService; import org.dspace.core.Constants; import org.dspace.core.Context; +import org.dspace.core.HibernateProxyHelper; import org.dspace.eperson.EPerson; -import org.hibernate.proxy.HibernateProxyHelper; + /** * Class representing an item in DSpace. diff --git a/dspace-api/src/main/java/org/dspace/content/ItemFilterService.java b/dspace-api/src/main/java/org/dspace/content/ItemFilterService.java new file mode 100644 index 0000000000..8b664a9726 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/ItemFilterService.java @@ -0,0 +1,33 @@ +/** + * 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.content; + +import java.util.List; + +import org.dspace.app.ldn.ItemFilter; + +/** + * Service interface class for the Item Filter Object + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + */ +public interface ItemFilterService { + + /** + * @param id the bean name of item filter + * @return one logical item filter by id + * defined in item-filter.xml + */ + public ItemFilter findOne(String id); + + /** + * @return all logical item filters + * defined in item-filter.xml + */ + public List findAll(); +} diff --git a/dspace-api/src/main/java/org/dspace/content/ItemFilterServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/ItemFilterServiceImpl.java new file mode 100644 index 0000000000..bdb23d6566 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/ItemFilterServiceImpl.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.content; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.dspace.app.ldn.ItemFilter; +import org.dspace.content.logic.LogicalStatement; +import org.dspace.kernel.ServiceManager; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Service implementation for {@link ItemFilterService} + * + * @author Mohamd Eskander (mohamed.eskander at 4science.com) + */ +public class ItemFilterServiceImpl implements ItemFilterService { + + @Autowired + private ServiceManager serviceManager; + + @Override + public ItemFilter findOne(String id) { + return findAll() + .stream() + .filter(itemFilter -> + itemFilter.getId().equals(id)) + .findFirst() + .orElse(null); + } + + @Override + public List findAll() { + Map ldnFilters = + serviceManager.getServiceByName("ldnItemFilters", Map.class); + return ldnFilters.keySet() + .stream() + .sorted() + .map(ItemFilter::new) + .collect(Collectors.toList()); + } + + public ServiceManager getServiceManager() { + return serviceManager; + } + + public void setServiceManager(ServiceManager serviceManager) { + this.serviceManager = serviceManager; + } + +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java index ac6c0d43d7..9a97851937 100644 --- a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java @@ -50,6 +50,7 @@ import org.dspace.content.service.MetadataSchemaService; import org.dspace.content.service.RelationshipService; import org.dspace.content.service.WorkspaceItemService; import org.dspace.content.virtual.VirtualMetadataPopulator; +import org.dspace.contentreport.QueryPredicate; import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.core.LogHelper; @@ -66,6 +67,7 @@ import org.dspace.event.Event; import org.dspace.harvest.HarvestedItem; import org.dspace.harvest.service.HarvestedItemService; import org.dspace.identifier.DOI; +import org.dspace.identifier.DOIIdentifierProvider; import org.dspace.identifier.IdentifierException; import org.dspace.identifier.service.DOIService; import org.dspace.identifier.service.IdentifierService; @@ -78,6 +80,7 @@ import org.dspace.orcid.service.OrcidQueueService; import org.dspace.orcid.service.OrcidSynchronizationService; import org.dspace.orcid.service.OrcidTokenService; import org.dspace.profile.service.ResearcherProfileService; +import org.dspace.qaevent.dao.QAEventsDAO; import org.dspace.services.ConfigurationService; import org.dspace.versioning.service.VersioningService; import org.dspace.workflow.WorkflowItemService; @@ -96,7 +99,7 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl implements It /** * log4j category */ - private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(Item.class); + private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(); @Autowired(required = true) protected ItemDAO itemDAO; @@ -171,8 +174,10 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl implements It @Autowired(required = true) protected SubscribeService subscribeService; + @Autowired + private QAEventsDAO qaEventsDao; + protected ItemServiceImpl() { - super(); } @Override @@ -272,9 +277,8 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl implements It + template.getID())); return template; - } else { - return collection.getTemplateItem(); } + return collection.getTemplateItem(); } @Override @@ -848,6 +852,7 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl implements It DOI doi = doiService.findDOIByDSpaceObject(context, item); if (doi != null) { doi.setDSpaceObject(null); + doi.setStatus(DOIIdentifierProvider.TO_BE_DELETED); } // remove version attached to the item @@ -869,6 +874,11 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl implements It orcidToken.setProfileItem(null); } + List qaEvents = qaEventsDao.findByItem(context, item); + for (QAEventProcessed qaEvent : qaEvents) { + qaEventsDao.delete(context, qaEvent); + } + //Only clear collections after we have removed everything else from the item item.clearCollections(); item.setOwningCollection(null); @@ -997,7 +1007,7 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl implements It throws SQLException, AuthorizeException { // Bundles should inherit from DEFAULT_ITEM_READ so that if the item is readable, the files // can be listed (even if they are themselves not readable as per DEFAULT_BITSTREAM_READ or other - // policies or embargos applied + // policies or embargoes applied List defaultCollectionBundlePolicies = authorizeService .getPoliciesActionFilter(context, collection, Constants.DEFAULT_ITEM_READ); // Bitstreams should inherit from DEFAULT_BITSTREAM_READ @@ -1231,9 +1241,8 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl implements It if (item.getOwningCollection() == null) { if (!isInProgressSubmission(context, item)) { return true; - } else { - return false; } + return false; } return collectionService.canEditBoolean(context, item.getOwningCollection(), false); @@ -1325,8 +1334,8 @@ prevent the generation of resource policy entry values with null dspace_object a if (!authorizeService .isAnIdenticalPolicyAlreadyInPlace(context, dso, defaultPolicy.getGroup(), Constants.READ, defaultPolicy.getID()) && - (!appendMode && this.isNotAlreadyACustomRPOfThisTypeOnDSO(context, dso) || - appendMode && this.shouldBeAppended(context, dso, defaultPolicy))) { + (!appendMode && isNotAlreadyACustomRPOfThisTypeOnDSO(context, dso) || + appendMode && shouldBeAppended(context, dso, defaultPolicy))) { ResourcePolicy newPolicy = resourcePolicyService.clone(context, defaultPolicy); newPolicy.setdSpaceObject(dso); newPolicy.setAction(Constants.READ); @@ -1425,9 +1434,8 @@ prevent the generation of resource policy entry values with null dspace_object a if (Item.ANY.equals(value)) { return itemDAO.findByMetadataField(context, mdf, null, true); - } else { - return itemDAO.findByMetadataField(context, mdf, value, true); } + return itemDAO.findByMetadataField(context, mdf, value, true); } @Override @@ -1471,19 +1479,24 @@ prevent the generation of resource policy entry values with null dspace_object a if (Item.ANY.equals(value)) { return itemDAO.findByMetadataField(context, mdf, null, true); - } else { - return itemDAO.findByMetadataField(context, mdf, value, true); } + return itemDAO.findByMetadataField(context, mdf, value, true); } @Override - public Iterator findByMetadataQuery(Context context, List> listFieldList, - List query_op, List query_val, List collectionUuids, - String regexClause, int offset, int limit) - throws SQLException, AuthorizeException, IOException { - return itemDAO - .findByMetadataQuery(context, listFieldList, query_op, query_val, collectionUuids, regexClause, offset, - limit); + public List findByMetadataQuery(Context context, List queryPredicates, + List collectionUuids, long offset, int limit) + throws SQLException { + return itemDAO.findByMetadataQuery(context, queryPredicates, collectionUuids, "value ~ ?", + offset, limit); + } + + + @Override + public long countForMetadataQuery(Context context, List queryPredicates, + List collectionUuids) + throws SQLException { + return itemDAO.countForMetadataQuery(context, queryPredicates, collectionUuids, "value ~ ?"); } @Override @@ -1549,20 +1562,19 @@ prevent the generation of resource policy entry values with null dspace_object a Collection ownCollection = item.getOwningCollection(); if (ownCollection != null) { return ownCollection; - } else { - InProgressSubmission inprogress = ContentServiceFactory.getInstance().getWorkspaceItemService() - .findByItem(context, - item); - if (inprogress == null) { - inprogress = WorkflowServiceFactory.getInstance().getWorkflowItemService().findByItem(context, item); - } - - if (inprogress != null) { - return inprogress.getCollection(); - } - // is a template item? - return item.getTemplateItemOf(); } + InProgressSubmission inprogress = ContentServiceFactory.getInstance().getWorkspaceItemService() + .findByItem(context, + item); + if (inprogress == null) { + inprogress = WorkflowServiceFactory.getInstance().getWorkflowItemService().findByItem(context, item); + } + + if (inprogress != null) { + return inprogress.getCollection(); + } + // is a template item? + return item.getTemplateItemOf(); } @Override @@ -1662,9 +1674,8 @@ prevent the generation of resource policy entry values with null dspace_object a try { if (StringUtils.isNumeric(id)) { return findByLegacyId(context, Integer.parseInt(id)); - } else { - return find(context, UUID.fromString(id)); } + return find(context, UUID.fromString(id)); } catch (IllegalArgumentException e) { // Not a valid legacy ID or valid UUID return null; @@ -1783,12 +1794,14 @@ prevent the generation of resource policy entry values with null dspace_object a */ @Override protected void moveSingleMetadataValue(Context context, Item dso, int place, MetadataValue rr) { + // If this is a (virtual) metadata value representing a relationship, + // then we must also update the corresponding Relationship with the new place if (rr instanceof RelationshipMetadataValue) { try { //Retrieve the applicable relationship Relationship rs = relationshipService.find(context, ((RelationshipMetadataValue) rr).getRelationshipId()); - if (rs.getLeftItem() == dso) { + if (rs.getLeftItem().equals(dso)) { rs.setLeftPlace(place); } else { rs.setRightPlace(place); @@ -1798,10 +1811,10 @@ prevent the generation of resource policy entry values with null dspace_object a //should not occur, otherwise metadata can't be updated either log.error("An error occurred while moving " + rr.getAuthority() + " for item " + dso.getID(), e); } - } else { - //just move the metadata - rr.setPlace(place); } + + // Update the MetadataValue object with the new place setting + rr.setPlace(place); } @Override diff --git a/dspace-api/src/main/java/org/dspace/content/MetadataField.java b/dspace-api/src/main/java/org/dspace/content/MetadataField.java index 8b76701199..cbe90a3744 100644 --- a/dspace-api/src/main/java/org/dspace/content/MetadataField.java +++ b/dspace-api/src/main/java/org/dspace/content/MetadataField.java @@ -7,23 +7,22 @@ */ package org.dspace.content; -import javax.persistence.Cacheable; -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 jakarta.persistence.Cacheable; +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 org.dspace.core.Context; +import org.dspace.core.HibernateProxyHelper; import org.dspace.core.ReloadableEntity; import org.hibernate.annotations.Cache; import org.hibernate.annotations.CacheConcurrencyStrategy; -import org.hibernate.proxy.HibernateProxyHelper; /** @@ -58,8 +57,6 @@ public class MetadataField implements ReloadableEntity { @Column(name = "qualifier", length = 64) private String qualifier = null; - // @Column(name = "scope_note") -// @Lob @Column(name = "scope_note", columnDefinition = "text") private String scopeNote; diff --git a/dspace-api/src/main/java/org/dspace/content/MetadataFieldName.java b/dspace-api/src/main/java/org/dspace/content/MetadataFieldName.java index 8d7f4b0277..32c08dab2b 100644 --- a/dspace-api/src/main/java/org/dspace/content/MetadataFieldName.java +++ b/dspace-api/src/main/java/org/dspace/content/MetadataFieldName.java @@ -8,7 +8,8 @@ package org.dspace.content; import java.util.Arrays; -import javax.annotation.Nonnull; + +import jakarta.annotation.Nonnull; /** * Simple immutable holder for the name of a metadata field. diff --git a/dspace-api/src/main/java/org/dspace/content/MetadataSchema.java b/dspace-api/src/main/java/org/dspace/content/MetadataSchema.java index f60e5e1604..b9a4665e66 100644 --- a/dspace-api/src/main/java/org/dspace/content/MetadataSchema.java +++ b/dspace-api/src/main/java/org/dspace/content/MetadataSchema.java @@ -7,19 +7,18 @@ */ package org.dspace.content; -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 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 org.dspace.core.Context; +import org.dspace.core.HibernateProxyHelper; import org.dspace.core.ReloadableEntity; import org.hibernate.annotations.CacheConcurrencyStrategy; -import org.hibernate.proxy.HibernateProxyHelper; /** * Class representing a schema in DSpace. diff --git a/dspace-api/src/main/java/org/dspace/content/MetadataValue.java b/dspace-api/src/main/java/org/dspace/content/MetadataValue.java index 31479e6206..dc45579f4e 100644 --- a/dspace-api/src/main/java/org/dspace/content/MetadataValue.java +++ b/dspace-api/src/main/java/org/dspace/content/MetadataValue.java @@ -7,24 +7,23 @@ */ package org.dspace.content; -import javax.persistence.CascadeType; -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.Lob; -import javax.persistence.ManyToOne; -import javax.persistence.SequenceGenerator; -import javax.persistence.Table; -import javax.persistence.Transient; - +import jakarta.persistence.CascadeType; +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.Transient; +import org.apache.commons.lang3.StringUtils; import org.dspace.core.Context; +import org.dspace.core.HibernateProxyHelper; import org.dspace.core.ReloadableEntity; -import org.hibernate.annotations.Type; -import org.hibernate.proxy.HibernateProxyHelper; +import org.hibernate.Length; /** * Database access class representing a Dublin Core metadata value. @@ -59,9 +58,7 @@ public class MetadataValue implements ReloadableEntity { /** * The value of the field */ - @Lob - @Type(type = "org.hibernate.type.TextType") - @Column(name = "text_value") + @Column(name = "text_value", length = Length.LONG32) private String value; /** @@ -143,6 +140,9 @@ public class MetadataValue implements ReloadableEntity { * @param language new language */ public void setLanguage(String language) { + if (StringUtils.equals(language, Item.ANY)) { + language = null; + } this.language = language; } diff --git a/dspace-api/src/main/java/org/dspace/content/MetadataValueServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/MetadataValueServiceImpl.java index 0c34c04f30..97f0c2ccf4 100644 --- a/dspace-api/src/main/java/org/dspace/content/MetadataValueServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/MetadataValueServiceImpl.java @@ -52,7 +52,7 @@ public class MetadataValueServiceImpl implements MetadataValueService { metadataValue.setMetadataField(metadataField); metadataValue.setDSpaceObject(dso); dso.addMetadata(metadataValue); -//An update here isn't needed, this is persited upon the merge of the owning object +//An update here isn't needed, this is persisted upon the merge of the owning object // metadataValueDAO.save(context, metadataValue); metadataValue = metadataValueDAO.create(context, metadataValue); log.info(LogHelper.getHeader(context, "add_metadatavalue", diff --git a/dspace-api/src/main/java/org/dspace/content/PredefinedUUIDGenerator.java b/dspace-api/src/main/java/org/dspace/content/PredefinedUUIDGenerator.java index aa4a8ea542..15d302ec61 100644 --- a/dspace-api/src/main/java/org/dspace/content/PredefinedUUIDGenerator.java +++ b/dspace-api/src/main/java/org/dspace/content/PredefinedUUIDGenerator.java @@ -7,7 +7,6 @@ */ package org.dspace.content; -import java.io.Serializable; import java.util.UUID; import org.hibernate.engine.spi.SharedSessionContractImplementor; @@ -21,7 +20,7 @@ import org.hibernate.id.UUIDGenerator; public class PredefinedUUIDGenerator extends UUIDGenerator { @Override - public Serializable generate(SharedSessionContractImplementor session, Object object) { + public Object generate(SharedSessionContractImplementor session, Object object) { if (object instanceof DSpaceObject) { UUID uuid = ((DSpaceObject) object).getPredefinedUUID(); if (uuid != null) { diff --git a/dspace-api/src/main/java/org/dspace/content/QAEvent.java b/dspace-api/src/main/java/org/dspace/content/QAEvent.java new file mode 100644 index 0000000000..0f2608ef70 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/QAEvent.java @@ -0,0 +1,220 @@ +/** + * 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.content; + +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Date; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import org.dspace.qaevent.service.dto.CorrectionTypeMessageDTO; +import org.dspace.qaevent.service.dto.NotifyMessageDTO; +import org.dspace.qaevent.service.dto.OpenaireMessageDTO; +import org.dspace.qaevent.service.dto.QAMessageDTO; +import org.dspace.util.RawJsonDeserializer; + +/** + * This class represent the Quality Assurance broker data as loaded in our solr + * qaevent core + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + */ +public class QAEvent { + public static final char[] HEX_DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', + 'f' }; + public static final String ACCEPTED = "accepted"; + public static final String REJECTED = "rejected"; + public static final String DISCARDED = "discarded"; + + public static final String OPENAIRE_SOURCE = "openaire"; + public static final String DSPACE_USERS_SOURCE = "DSpaceUsers"; + public static final String COAR_NOTIFY_SOURCE = "coar-notify"; + + private String source; + + private String eventId; + /** + * contains the targeted dspace object, + * ie: oai:www.openstarts.units.it:123456789/1120 contains the handle + * of the DSpace pbject in its final part 123456789/1120 + * */ + private String originalId; + + /** + * evaluated with the targeted dspace object id + * + * */ + private String target; + + private String related; + + private String title; + + private String topic; + + private double trust; + + @JsonDeserialize(using = RawJsonDeserializer.class) + private String message; + + private Date lastUpdate; + + private String status = "PENDING"; + + public QAEvent() {} + + public QAEvent(String source, String originalId, String target, String title, + String topic, double trust, String message, Date lastUpdate) { + super(); + this.source = source; + this.originalId = originalId; + this.target = target; + this.title = title; + this.topic = topic; + this.trust = trust; + this.message = message; + this.lastUpdate = lastUpdate; + try { + computedEventId(); + } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) { + throw new IllegalStateException(e); + } + } + + public String getOriginalId() { + return originalId; + } + + public void setOriginalId(String originalId) { + this.originalId = originalId; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getTopic() { + return topic; + } + + public void setTopic(String topic) { + this.topic = topic; + } + + public double getTrust() { + return trust; + } + + public void setTrust(double trust) { + this.trust = trust; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getEventId() { + if (eventId == null) { + try { + computedEventId(); + } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } + return eventId; + } + + public void setEventId(String eventId) { + this.eventId = eventId; + } + + public String getTarget() { + return target; + } + + public void setTarget(String target) { + this.target = target; + } + + public Date getLastUpdate() { + return lastUpdate; + } + + public void setLastUpdate(Date lastUpdate) { + this.lastUpdate = lastUpdate; + } + + public void setRelated(String related) { + this.related = related; + } + + public String getRelated() { + return related; + } + + public void setStatus(String status) { + this.status = status; + } + + public String getStatus() { + return status; + } + + public String getSource() { + return source != null ? source : OPENAIRE_SOURCE; + } + + public void setSource(String source) { + this.source = source; + } + + /* + * DTO constructed via Jackson use empty constructor. In this case, the eventId + * must be compute on the get method. This method create a signature based on + * the event fields and store it in the eventid attribute. + */ + private void computedEventId() throws NoSuchAlgorithmException, UnsupportedEncodingException { + MessageDigest digester = MessageDigest.getInstance("MD5"); + String dataToString = "source=" + source + ",originalId=" + originalId + ", title=" + title + ", topic=" + + topic + ", trust=" + trust + ", message=" + message; + digester.update(dataToString.getBytes("UTF-8")); + byte[] signature = digester.digest(); + char[] arr = new char[signature.length << 1]; + for (int i = 0; i < signature.length; i++) { + int b = signature[i]; + int idx = i << 1; + arr[idx] = HEX_DIGITS[(b >> 4) & 0xf]; + arr[idx + 1] = HEX_DIGITS[b & 0xf]; + } + eventId = new String(arr); + + } + + public Class getMessageDtoClass() { + switch (getSource()) { + case OPENAIRE_SOURCE: + return OpenaireMessageDTO.class; + case COAR_NOTIFY_SOURCE: + return NotifyMessageDTO.class; + case DSPACE_USERS_SOURCE: + return CorrectionTypeMessageDTO.class; + default: + throw new IllegalArgumentException("Unknown event's source: " + getSource()); + } + } + +} diff --git a/dspace-api/src/main/java/org/dspace/content/QAEventProcessed.java b/dspace-api/src/main/java/org/dspace/content/QAEventProcessed.java new file mode 100644 index 0000000000..fcca5b4570 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/QAEventProcessed.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.content; + +import java.io.Serializable; +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.eperson.EPerson; + +/** + * This class represent the stored information about processed notification + * broker events + * + */ +@Entity +@Table(name = "qaevent_processed") +public class QAEventProcessed implements Serializable { + + private static final long serialVersionUID = 3427340199132007814L; + + @Id + @Column(name = "qaevent_id") + private String eventId; + + @Temporal(TemporalType.TIMESTAMP) + @Column(name = "qaevent_timestamp") + private Date eventTimestamp; + + @JoinColumn(name = "eperson_uuid") + @ManyToOne + private EPerson eperson; + + @JoinColumn(name = "item_uuid") + @ManyToOne + private Item item; + + public String getEventId() { + return eventId; + } + + public void setEventId(String eventId) { + this.eventId = eventId; + } + + public Date getEventTimestamp() { + return eventTimestamp; + } + + public void setEventTimestamp(Date eventTimestamp) { + this.eventTimestamp = eventTimestamp; + } + + public EPerson getEperson() { + return eperson; + } + + public void setEperson(EPerson eperson) { + this.eperson = eperson; + } + + public Item getItem() { + return item; + } + + public void setItem(Item item) { + this.item = item; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/content/Relationship.java b/dspace-api/src/main/java/org/dspace/content/Relationship.java index 77c418a23d..05e4b00718 100644 --- a/dspace-api/src/main/java/org/dspace/content/Relationship.java +++ b/dspace-api/src/main/java/org/dspace/content/Relationship.java @@ -7,19 +7,20 @@ */ package org.dspace.content; -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 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 org.dspace.core.Context; import org.dspace.core.ReloadableEntity; +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.type.SqlTypes; /** * This class represents a relationship @@ -96,6 +97,7 @@ public class Relationship implements ReloadableEntity { * This column affects what version of an item appears on search pages or the relationship listings of other items. */ @Column(name = "latest_version_status") + @JdbcTypeCode(SqlTypes.INTEGER) private LatestVersionStatus latestVersionStatus = LatestVersionStatus.BOTH; /** diff --git a/dspace-api/src/main/java/org/dspace/content/RelationshipType.java b/dspace-api/src/main/java/org/dspace/content/RelationshipType.java index 5e6941052b..bcabc98cff 100644 --- a/dspace-api/src/main/java/org/dspace/content/RelationshipType.java +++ b/dspace-api/src/main/java/org/dspace/content/RelationshipType.java @@ -7,20 +7,21 @@ */ package org.dspace.content; -import javax.persistence.CascadeType; -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 jakarta.persistence.CascadeType; +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 org.dspace.core.Context; import org.dspace.core.ReloadableEntity; +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.type.SqlTypes; /** * Class representing a RelationshipType @@ -97,7 +98,7 @@ public class RelationshipType implements ReloadableEntity { private Integer rightMinCardinality; /** - * Tha maximum amount of relations for the rightItem that can be present at all times + * The maximum amount of relations for the rightItem that can be present at all times */ @Column(name = "right_max_cardinality") private Integer rightMaxCardinality; @@ -118,6 +119,7 @@ public class RelationshipType implements ReloadableEntity { * The value indicating whether relationships of this type should be ignored on the right/left/neither. */ @Column(name = "tilted") + @JdbcTypeCode(SqlTypes.INTEGER) private Tilted tilted; /** diff --git a/dspace-api/src/main/java/org/dspace/content/Site.java b/dspace-api/src/main/java/org/dspace/content/Site.java index 0bdab6ffe5..904c1d3e6b 100644 --- a/dspace-api/src/main/java/org/dspace/content/Site.java +++ b/dspace-api/src/main/java/org/dspace/content/Site.java @@ -7,28 +7,23 @@ */ package org.dspace.content; -import javax.persistence.Cacheable; -import javax.persistence.Entity; -import javax.persistence.Table; -import javax.persistence.Transient; - +import jakarta.persistence.Entity; +import jakarta.persistence.Table; +import jakarta.persistence.Transient; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.SiteService; import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; -import org.hibernate.annotations.CacheConcurrencyStrategy; /** * Represents the root of the DSpace Archive. * By default, the handle suffix "0" represents the Site, e.g. "1721.1/0" */ @Entity -@Cacheable -@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) @Table(name = "site") -public class Site extends DSpaceObject { +public class Site extends CacheableDSpaceObject { @Transient private transient SiteService siteService; diff --git a/dspace-api/src/main/java/org/dspace/content/WorkspaceItem.java b/dspace-api/src/main/java/org/dspace/content/WorkspaceItem.java index a4c880173b..355e182694 100644 --- a/dspace-api/src/main/java/org/dspace/content/WorkspaceItem.java +++ b/dspace-api/src/main/java/org/dspace/content/WorkspaceItem.java @@ -8,23 +8,23 @@ package org.dspace.content; import java.io.Serializable; -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.OneToOne; -import javax.persistence.SequenceGenerator; -import javax.persistence.Table; +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.OneToOne; +import jakarta.persistence.SequenceGenerator; +import jakarta.persistence.Table; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.dspace.core.Context; +import org.dspace.core.HibernateProxyHelper; import org.dspace.eperson.EPerson; import org.dspace.workflow.WorkflowItem; -import org.hibernate.proxy.HibernateProxyHelper; /** * Class representing an item in the process of being submitted by a user diff --git a/dspace-api/src/main/java/org/dspace/content/authority/Choice.java b/dspace-api/src/main/java/org/dspace/content/authority/Choice.java index 6d73bdb5ea..9f6bff03bc 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/Choice.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/Choice.java @@ -77,7 +77,7 @@ public class Choice { /** * Constructor for common need of Hierarchical authorities that want to - * explicitely set the selectable flag + * explicitly set the selectable flag * * @param authority the authority key * @param value the text value to store in the metadata diff --git a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthority.java b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthority.java index 750e761f3d..fddd85cea3 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthority.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthority.java @@ -131,7 +131,7 @@ public interface ChoiceAuthority extends NameAwarePlugin { * Build the preferred choice associated with the authKey. The default * implementation delegate the creato to the {@link #getLabel(String, String)} * {@link #getValue(String, String)} and {@link #getExtra(String, String)} - * methods but can be directly overriden for better efficiency or special + * methods but can be directly overridden for better efficiency or special * scenario * * @param authKey authority key known to this authority. diff --git a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java index f4d1f02710..f4201c1e28 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java @@ -42,7 +42,7 @@ import org.springframework.beans.factory.annotation.Autowired; * Broker for ChoiceAuthority plugins, and for other information configured * about the choice aspect of authority control for a metadata field. * - * Configuration keys, per metadata field (e.g. "dc.contributer.author") + * Configuration keys, per metadata field (e.g. "dc.contributor.author") * * {@code * # names the ChoiceAuthority plugin called for this field diff --git a/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java b/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java index 902bded33e..17548d808b 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java @@ -30,7 +30,7 @@ import org.dspace.core.SelfNamedPlugin; * configurable submission. * * Configuration: - * This MUST be configured aas a self-named plugin, e.g.: + * This MUST be configured as a self-named plugin, e.g.: * {@code * plugin.selfnamed.org.dspace.content.authority.ChoiceAuthority = \ * org.dspace.content.authority.DCInputAuthority diff --git a/dspace-api/src/main/java/org/dspace/content/authority/SHERPARoMEOJournalTitle.java b/dspace-api/src/main/java/org/dspace/content/authority/SHERPARoMEOJournalTitle.java index da5c743c5b..418000e51e 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/SHERPARoMEOJournalTitle.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/SHERPARoMEOJournalTitle.java @@ -59,7 +59,37 @@ public class SHERPARoMEOJournalTitle implements ChoiceAuthority { @Override public Choices getBestMatch(String text, String locale) { - return getMatches(text, 0, 1, locale); + // punt if there is no query text + if (text == null || text.trim().length() == 0) { + return new Choices(true); + } + int limit = 10; + SHERPAService sherpaService = new DSpace().getSingletonService(SHERPAService.class); + SHERPAResponse sherpaResponse = sherpaService.performRequest("publication", "title", + "equals", text, 0, limit); + Choices result; + if (CollectionUtils.isNotEmpty(sherpaResponse.getJournals())) { + List list = sherpaResponse + .getJournals().stream() + .map(sherpaJournal -> new Choice(sherpaJournal.getIssns().get(0), + sherpaJournal.getTitles().get(0), sherpaJournal.getTitles().get(0))) + .collect(Collectors.toList()); + int total = sherpaResponse.getJournals().size(); + + int confidence; + if (list.isEmpty()) { + confidence = Choices.CF_NOTFOUND; + } else if (list.size() == 1) { + confidence = Choices.CF_UNCERTAIN; + } else { + confidence = Choices.CF_AMBIGUOUS; + } + result = new Choices(list.toArray(new Choice[list.size()]), 0, total, confidence, + total > limit); + } else { + result = new Choices(false); + } + return result; } @Override diff --git a/dspace-api/src/main/java/org/dspace/content/authority/SHERPARoMEOPublisher.java b/dspace-api/src/main/java/org/dspace/content/authority/SHERPARoMEOPublisher.java index 0f93dff498..d1001ce5db 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/SHERPARoMEOPublisher.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/SHERPARoMEOPublisher.java @@ -60,7 +60,38 @@ public class SHERPARoMEOPublisher implements ChoiceAuthority { @Override public Choices getBestMatch(String text, String locale) { - return getMatches(text, 0, 1, locale); + // punt if there is no query text + if (text == null || text.trim().length() == 0) { + return new Choices(true); + } + int limit = 10; + SHERPAService sherpaService = new DSpace().getSingletonService(SHERPAService.class); + SHERPAPublisherResponse sherpaResponse = sherpaService.performPublisherRequest("publisher", "name", + "equals", text, 0, limit); + Choices result; + if (CollectionUtils.isNotEmpty(sherpaResponse.getPublishers())) { + List list = sherpaResponse + .getPublishers().stream() + .map(sherpaPublisher -> + new Choice(sherpaPublisher.getIdentifier(), + sherpaPublisher.getName(), sherpaPublisher.getName())) + .collect(Collectors.toList()); + int total = sherpaResponse.getPublishers().size(); + + int confidence; + if (list.isEmpty()) { + confidence = Choices.CF_NOTFOUND; + } else if (list.size() == 1) { + confidence = Choices.CF_UNCERTAIN; + } else { + confidence = Choices.CF_AMBIGUOUS; + } + result = new Choices(list.toArray(new Choice[list.size()]), 0, total, confidence, + total > limit); + } else { + result = new Choices(false); + } + return result; } @Override diff --git a/dspace-api/src/main/java/org/dspace/content/authority/SolrAuthority.java b/dspace-api/src/main/java/org/dspace/content/authority/SolrAuthority.java index 123626cd09..a8631ba1f0 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/SolrAuthority.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/SolrAuthority.java @@ -76,7 +76,7 @@ public class SolrAuthority implements ChoiceAuthority { Integer.parseInt(locale); locale = null; } catch (NumberFormatException e) { - //Everything is allright + //Everything is alright } if (locale != null && !"".equals(locale)) { localSearchField = searchField + "_" + locale; diff --git a/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java b/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java index 94e5ca57a0..f7325744c4 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java @@ -22,7 +22,7 @@ import org.dspace.content.authority.DSpaceControlledVocabularyIndex; * Broker for ChoiceAuthority plugins, and for other information configured * about the choice aspect of authority control for a metadata field. * - * Configuration keys, per metadata field (e.g. "dc.contributer.author") + * Configuration keys, per metadata field (e.g. "dc.contributor.author") * {@code * # names the ChoiceAuthority plugin called for this field * choices.plugin. = name-of-plugin diff --git a/dspace-api/src/main/java/org/dspace/content/authority/service/MetadataAuthorityService.java b/dspace-api/src/main/java/org/dspace/content/authority/service/MetadataAuthorityService.java index 2ba6791de5..e40bb978d6 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/service/MetadataAuthorityService.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/service/MetadataAuthorityService.java @@ -14,7 +14,7 @@ import org.dspace.content.MetadataField; /** * Broker for metadata authority settings configured for each metadata field. * - * Configuration keys, per metadata field (e.g. "dc.contributer.author") + * Configuration keys, per metadata field (e.g. "dc.contributor.author") * * # is field authority controlled (i.e. store authority, confidence values)? * {@code authority.controlled. = true} diff --git a/dspace-api/src/main/java/org/dspace/content/crosswalk/DIMDisseminationCrosswalk.java b/dspace-api/src/main/java/org/dspace/content/crosswalk/DIMDisseminationCrosswalk.java index 4365d9a485..76738118f6 100644 --- a/dspace-api/src/main/java/org/dspace/content/crosswalk/DIMDisseminationCrosswalk.java +++ b/dspace-api/src/main/java/org/dspace/content/crosswalk/DIMDisseminationCrosswalk.java @@ -36,7 +36,7 @@ import org.jdom2.Namespace; */ public class DIMDisseminationCrosswalk implements DisseminationCrosswalk { - // Non-existant XSD schema + // Non-existent XSD schema public static final String DIM_XSD = "null"; // Namespaces diff --git a/dspace-api/src/main/java/org/dspace/content/crosswalk/IngestionCrosswalk.java b/dspace-api/src/main/java/org/dspace/content/crosswalk/IngestionCrosswalk.java index bb73c83c45..a8d9836972 100644 --- a/dspace-api/src/main/java/org/dspace/content/crosswalk/IngestionCrosswalk.java +++ b/dspace-api/src/main/java/org/dspace/content/crosswalk/IngestionCrosswalk.java @@ -37,7 +37,7 @@ public interface IngestionCrosswalk { * internal representations. This version accepts metadata as a * List of JDOM XML elements. It interprets the * contents of each element and adds the appropriate values to the - * DSpace Object's internal metadata represenation. + * DSpace Object's internal metadata representation. *

    * Note that this method may be called several times for the same target * Item, if the metadata comes as several lists of elements, so it should diff --git a/dspace-api/src/main/java/org/dspace/content/crosswalk/MODSDisseminationCrosswalk.java b/dspace-api/src/main/java/org/dspace/content/crosswalk/MODSDisseminationCrosswalk.java index 1e63be5ba1..6315f73a7c 100644 --- a/dspace-api/src/main/java/org/dspace/content/crosswalk/MODSDisseminationCrosswalk.java +++ b/dspace-api/src/main/java/org/dspace/content/crosswalk/MODSDisseminationCrosswalk.java @@ -202,7 +202,7 @@ public class MODSDisseminationCrosswalk extends SelfNamedPlugin * e.g. dc.contributor.author * * 2. XML fragment is prototype of metadata element, with empty or "%s" - * placeholders for value(s). NOTE: Leave the %s's in becaue + * placeholders for value(s). NOTE: Leave the %s's in because * it's much easier then to see if something is broken. * * 3. XPath expression listing point(s) in the above XML where diff --git a/dspace-api/src/main/java/org/dspace/content/crosswalk/OREIngestionCrosswalk.java b/dspace-api/src/main/java/org/dspace/content/crosswalk/OREIngestionCrosswalk.java index f756aae225..aa9096517b 100644 --- a/dspace-api/src/main/java/org/dspace/content/crosswalk/OREIngestionCrosswalk.java +++ b/dspace-api/src/main/java/org/dspace/content/crosswalk/OREIngestionCrosswalk.java @@ -173,7 +173,7 @@ public class OREIngestionCrosswalk try { // Make sure the url string escapes all the oddball characters String processedURL = encodeForURL(href); - // Generate a requeset for the aggregated resource + // Generate a request for the aggregated resource ARurl = new URL(processedURL); in = ARurl.openStream(); } catch (FileNotFoundException fe) { diff --git a/dspace-api/src/main/java/org/dspace/content/crosswalk/QDCCrosswalk.java b/dspace-api/src/main/java/org/dspace/content/crosswalk/QDCCrosswalk.java index 2fdbaaad00..fa582fd62f 100644 --- a/dspace-api/src/main/java/org/dspace/content/crosswalk/QDCCrosswalk.java +++ b/dspace-api/src/main/java/org/dspace/content/crosswalk/QDCCrosswalk.java @@ -113,7 +113,7 @@ public class QDCCrosswalk extends SelfNamedPlugin private static final Namespace DCTERMS_NS = Namespace.getNamespace("dcterms", "http://purl.org/dc/terms/"); - // sentinal: done init? + // sentinel: done init? private boolean inited = false; // my plugin name diff --git a/dspace-api/src/main/java/org/dspace/content/crosswalk/SubscriptionDsoMetadataForEmailCompose.java b/dspace-api/src/main/java/org/dspace/content/crosswalk/SubscriptionDsoMetadataForEmailCompose.java index ad92018b22..29baa980fc 100644 --- a/dspace-api/src/main/java/org/dspace/content/crosswalk/SubscriptionDsoMetadataForEmailCompose.java +++ b/dspace-api/src/main/java/org/dspace/content/crosswalk/SubscriptionDsoMetadataForEmailCompose.java @@ -47,12 +47,12 @@ public class SubscriptionDsoMetadataForEmailCompose implements StreamDisseminati Item item = (Item) dso; PrintStream printStream = new PrintStream(out); for (String actualMetadata : metadata) { - String[] splitted = actualMetadata.split("\\."); + String[] split = actualMetadata.split("\\."); String qualifier = null; - if (splitted.length == 3) { - qualifier = splitted[2]; + if (split.length == 3) { + qualifier = split[2]; } - var metadataValue = itemService.getMetadataFirstValue(item, splitted[0], splitted[1], qualifier, ANY); + var metadataValue = itemService.getMetadataFirstValue(item, split[0], split[1], qualifier, ANY); printStream.print(metadataValue + " "); } String itemURL = HandleServiceFactory.getInstance() diff --git a/dspace-api/src/main/java/org/dspace/content/crosswalk/XSLTCrosswalk.java b/dspace-api/src/main/java/org/dspace/content/crosswalk/XSLTCrosswalk.java index d4ccebf82e..1d07da0e51 100644 --- a/dspace-api/src/main/java/org/dspace/content/crosswalk/XSLTCrosswalk.java +++ b/dspace-api/src/main/java/org/dspace/content/crosswalk/XSLTCrosswalk.java @@ -18,12 +18,12 @@ import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.stream.StreamSource; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.core.SelfNamedPlugin; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; import org.jdom2.Namespace; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * Configurable XSLT-driven Crosswalk @@ -88,7 +88,7 @@ public abstract class XSLTCrosswalk extends SelfNamedPlugin { /** * log4j category */ - private static final Logger LOG = LoggerFactory.getLogger(XSLTCrosswalk.class); + private static final Logger LOG = LogManager.getLogger(); /** * DSpace XML Namespace in JDOM form. @@ -168,8 +168,8 @@ public abstract class XSLTCrosswalk extends SelfNamedPlugin { transformFile.lastModified() > transformLastModified) { try { LOG.debug( - (transformer == null ? "Loading {} XSLT stylesheet from {}" : "Reloading {} XSLT stylesheet from " + - "{}"), + (transformer == null ? "Loading {} XSLT stylesheet from {}" + : "Reloading {} XSLT stylesheet from {}"), getPluginInstanceName(), transformFile.toString()); Source transformSource diff --git a/dspace-api/src/main/java/org/dspace/content/crosswalk/XSLTDisseminationCrosswalk.java b/dspace-api/src/main/java/org/dspace/content/crosswalk/XSLTDisseminationCrosswalk.java index 26371b46aa..70bb3f1316 100644 --- a/dspace-api/src/main/java/org/dspace/content/crosswalk/XSLTDisseminationCrosswalk.java +++ b/dspace-api/src/main/java/org/dspace/content/crosswalk/XSLTDisseminationCrosswalk.java @@ -23,6 +23,8 @@ import javax.xml.transform.Transformer; import javax.xml.transform.TransformerException; import org.apache.commons.lang3.ArrayUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Collection; import org.dspace.content.Community; @@ -51,8 +53,6 @@ import org.jdom2.output.Format; import org.jdom2.output.XMLOutputter; import org.jdom2.transform.JDOMResult; import org.jdom2.transform.JDOMSource; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * Configurable XSLT-driven dissemination Crosswalk @@ -88,7 +88,7 @@ public class XSLTDisseminationCrosswalk /** * log4j category */ - private static final Logger LOG = LoggerFactory.getLogger(XSLTDisseminationCrosswalk.class); + private static final Logger LOG = LogManager.getLogger(); /** * DSpace context, will be created if XSLTDisseminationCrosswalk had been started by command-line. @@ -140,12 +140,13 @@ public class XSLTDisseminationCrosswalk // right format for value of "schemaLocation" attribute. schemaLocation = configurationService.getProperty(prefix + "schemaLocation"); if (schemaLocation == null) { - LOG.warn("No schemaLocation for crosswalk=" + myAlias + ", key=" + prefix + "schemaLocation"); + LOG.warn("No schemaLocation for crosswalk={}, key={}schemaLocation", myAlias, prefix); } else if (schemaLocation.length() > 0 && schemaLocation.indexOf(' ') < 0) { // sanity check: schemaLocation should have space. - LOG.warn("Possible INVALID schemaLocation (no space found) for crosswalk=" + - myAlias + ", key=" + prefix + "schemaLocation" + - "\n\tCorrect format is \"{namespace} {schema-URL}\""); + LOG.warn("Possible INVALID schemaLocation (no space found) for crosswalk={}," + + " key={}schemaLocation" + + "\n\tCorrect format is \"{namespace} {schema-URL}\"", + myAlias, prefix); } // grovel for namespaces of the form: @@ -172,7 +173,7 @@ public class XSLTDisseminationCrosswalk try { init(); } catch (CrosswalkInternalException e) { - LOG.error(e.toString()); + LOG.error(e::toString); } return (Namespace[]) ArrayUtils.clone(namespaces); } @@ -187,7 +188,7 @@ public class XSLTDisseminationCrosswalk try { init(); } catch (CrosswalkInternalException e) { - LOG.error(e.toString()); + LOG.error(e::toString); } return schemaLocation; } @@ -220,7 +221,7 @@ public class XSLTDisseminationCrosswalk } for (Map.Entry parameter : parameters.entrySet()) { - LOG.debug("Setting parameter {} to {}", parameter.getKey(), parameter.getValue()); + LOG.debug("Setting parameter {} to {}", parameter::getKey, parameter::getValue); xform.setParameter(parameter.getKey(), parameter.getValue()); } @@ -232,7 +233,7 @@ public class XSLTDisseminationCrosswalk root.detach(); return root; } catch (TransformerException e) { - LOG.error("Got error: " + e.toString()); + LOG.error("Got error: ()", e::toString); throw new CrosswalkInternalException("XSL translation failed: " + e.toString(), e); } } @@ -278,13 +279,13 @@ public class XSLTDisseminationCrosswalk .map(Element.class::cast).collect(Collectors.toList()); return elementList; } catch (TransformerException e) { - LOG.error("Got error: " + e.toString()); + LOG.error("Got error: {}", e::toString); throw new CrosswalkInternalException("XSL translation failed: " + e.toString(), e); } } /** - * Determine is this crosswalk can dessiminate the given object. + * Determine is this crosswalk can disseminate the given object. * * @see DisseminationCrosswalk */ @@ -304,7 +305,7 @@ public class XSLTDisseminationCrosswalk try { init(); } catch (CrosswalkInternalException e) { - LOG.error(e.toString()); + LOG.error(e::toString); } return preferList; } @@ -312,7 +313,7 @@ public class XSLTDisseminationCrosswalk /** * Generate an intermediate representation of a DSpace object. * - * @param dso The dspace object to build a representation of. + * @param dso The DSpace object to build a representation of. * @param dcvs list of metadata * @return element */ @@ -480,9 +481,7 @@ public class XSLTDisseminationCrosswalk if (reason == null) { return value; } else { - if (LOG.isDebugEnabled()) { - LOG.debug("Filtering out non-XML characters in string, reason=" + reason); - } + LOG.debug("Filtering out non-XML characters in string, reason={}", reason); StringBuilder result = new StringBuilder(value.length()); for (int i = 0; i < value.length(); ++i) { char c = value.charAt(i); @@ -567,11 +566,11 @@ public class XSLTDisseminationCrosswalk System.err.println("=== Stack Trace ==="); e.printStackTrace(System.err); System.err.println("====================="); - LOG.error("Caught: {}.", e.toString()); - LOG.error(e.getMessage()); + LOG.error("Caught: {}.", e::toString); + LOG.error(e::getMessage); CharArrayWriter traceWriter = new CharArrayWriter(2048); e.printStackTrace(new PrintWriter(traceWriter)); - LOG.error(traceWriter.toString()); + LOG.error(traceWriter::toString); System.exit(1); } @@ -588,11 +587,11 @@ public class XSLTDisseminationCrosswalk System.err.println("=== Stack Trace ==="); e.printStackTrace(System.err); System.err.println("====================="); - LOG.error("Caught: {}.", e.toString()); - LOG.error(e.getMessage()); + LOG.error("Caught: {}.", e::toString); + LOG.error(e::getMessage); CharArrayWriter traceWriter = new CharArrayWriter(2048); e.printStackTrace(new PrintWriter(traceWriter)); - LOG.error(traceWriter.toString()); + LOG.error(traceWriter::toString); System.exit(1); } diff --git a/dspace-api/src/main/java/org/dspace/content/dao/ItemDAO.java b/dspace-api/src/main/java/org/dspace/content/dao/ItemDAO.java index d7cc9c6079..7cba88c0ba 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/ItemDAO.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/ItemDAO.java @@ -17,6 +17,7 @@ import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.Item; import org.dspace.content.MetadataField; +import org.dspace.contentreport.QueryPredicate; import org.dspace.core.Context; import org.dspace.eperson.EPerson; @@ -28,12 +29,11 @@ import org.dspace.eperson.EPerson; * @author kevinvandevelde at atmire.com */ public interface ItemDAO extends DSpaceObjectLegacySupportDAO { - public Iterator findAll(Context context, boolean archived) throws SQLException; + Iterator findAll(Context context, boolean archived) throws SQLException; - public Iterator findAll(Context context, boolean archived, int limit, int offset) throws SQLException; + Iterator findAll(Context context, boolean archived, int limit, int offset) throws SQLException; - @Deprecated - public Iterator findAll(Context context, boolean archived, boolean withdrawn) throws SQLException; + @Deprecated Iterator findAll(Context context, boolean archived, boolean withdrawn) throws SQLException; /** * Find all items that are: @@ -46,7 +46,7 @@ public interface ItemDAO extends DSpaceObjectLegacySupportDAO { * @return iterator over all regular items. * @throws SQLException if database error. */ - public Iterator findAllRegularItems(Context context) throws SQLException; + Iterator findAllRegularItems(Context context) throws SQLException; /** * Find all Items modified since a Date. @@ -56,10 +56,10 @@ public interface ItemDAO extends DSpaceObjectLegacySupportDAO { * @return iterator over items * @throws SQLException if database error */ - public Iterator findByLastModifiedSince(Context context, Date since) + Iterator findByLastModifiedSince(Context context, Date since) throws SQLException; - public Iterator findBySubmitter(Context context, EPerson eperson) throws SQLException; + Iterator findBySubmitter(Context context, EPerson eperson) throws SQLException; /** * Find all the items by a given submitter. The order is @@ -71,23 +71,40 @@ public interface ItemDAO extends DSpaceObjectLegacySupportDAO { * @return an iterator over the items submitted by eperson * @throws SQLException if database error */ - public Iterator findBySubmitter(Context context, EPerson eperson, boolean retrieveAllItems) + Iterator findBySubmitter(Context context, EPerson eperson, boolean retrieveAllItems) throws SQLException; - public Iterator findBySubmitter(Context context, EPerson eperson, MetadataField metadataField, int limit) + Iterator findBySubmitter(Context context, EPerson eperson, MetadataField metadataField, int limit) throws SQLException; - public Iterator findByMetadataField(Context context, MetadataField metadataField, String value, + Iterator findByMetadataField(Context context, MetadataField metadataField, String value, boolean inArchive) throws SQLException; - public Iterator findByMetadataQuery(Context context, List> listFieldList, - List query_op, List query_val, List collectionUuids, - String regexClause, int offset, int limit) throws SQLException; + /** + * Returns all the Items that belong to the specified aollections (if any) + * and match the provided predicates. + * @param context The relevant DSpace context + * @param queryPredicates List of predicates that returned items are required to match + * @param collectionUuids UUIDs of the collections to search. + * If none are provided, the entire repository will be searched. + * @param regexClause Syntactic expression used to query the database using a regular expression + * (e.g.: "value ~ ?") + * @param offset The offset for the query + * @param limit Maximum number of items to return + * @return A list containing the items that match the provided criteria + * @throws SQLException if something goes wrong + */ + List findByMetadataQuery(Context context, List queryPredicates, + List collectionUuids, String regexClause, + long offset, int limit) throws SQLException; - public Iterator findByAuthorityValue(Context context, MetadataField metadataField, String authority, + long countForMetadataQuery(Context context, List queryPredicates, + List collectionUuids, String regexClause) throws SQLException; + + Iterator findByAuthorityValue(Context context, MetadataField metadataField, String authority, boolean inArchive) throws SQLException; - public Iterator findArchivedByCollection(Context context, Collection collection, Integer limit, + Iterator findArchivedByCollection(Context context, Collection collection, Integer limit, Integer offset) throws SQLException; /** @@ -100,7 +117,7 @@ public interface ItemDAO extends DSpaceObjectLegacySupportDAO { * @return An iterator containing the items for which the constraints hold true * @throws SQLException If something goes wrong */ - public Iterator findArchivedByCollectionExcludingOwning(Context context, Collection collection, Integer limit, + Iterator findArchivedByCollectionExcludingOwning(Context context, Collection collection, Integer limit, Integer offset) throws SQLException; /** @@ -111,11 +128,11 @@ public interface ItemDAO extends DSpaceObjectLegacySupportDAO { * @return The total amount of items that fit the constraints * @throws SQLException If something goes wrong */ - public int countArchivedByCollectionExcludingOwning(Context context, Collection collection) throws SQLException; + int countArchivedByCollectionExcludingOwning(Context context, Collection collection) throws SQLException; - public Iterator findAllByCollection(Context context, Collection collection) throws SQLException; + Iterator findAllByCollection(Context context, Collection collection) throws SQLException; - public Iterator findAllByCollection(Context context, Collection collection, Integer limit, Integer offset) + Iterator findAllByCollection(Context context, Collection collection, Integer limit, Integer offset) throws SQLException; /** @@ -159,7 +176,7 @@ public interface ItemDAO extends DSpaceObjectLegacySupportDAO { * @return iterator over items * @throws SQLException if database error */ - public Iterator findAll(Context context, boolean archived, + Iterator findAll(Context context, boolean archived, boolean withdrawn, boolean discoverable, Date lastModified) throws SQLException; diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/BitstreamDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/BitstreamDAOImpl.java index 276ea2b343..25f102f6de 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/impl/BitstreamDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/BitstreamDAOImpl.java @@ -13,16 +13,21 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.UUID; -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.Join; +import jakarta.persistence.criteria.Root; import org.dspace.content.Bitstream; import org.dspace.content.Bitstream_; +import org.dspace.content.Bundle; +import org.dspace.content.Bundle_; import org.dspace.content.Collection; +import org.dspace.content.Collection_; import org.dspace.content.Community; import org.dspace.content.Item; +import org.dspace.content.Item_; import org.dspace.content.dao.BitstreamDAO; import org.dspace.core.AbstractHibernateDSODAO; import org.dspace.core.Constants; @@ -78,14 +83,21 @@ public class BitstreamDAOImpl extends AbstractHibernateDSODAO impleme @Override public Iterator findByCommunity(Context context, Community community) throws SQLException { - Query query = createQuery(context, "select b.id from Bitstream b " + - "join b.bundles bitBundles " + - "join bitBundles.items item " + - "join item.collections itemColl " + - "join itemColl.communities community " + - "WHERE :community IN community"); + // Select UUID of all bitstreams, joining from Bitstream -> Bundle -> Item -> Collection -> Community + // to find all that exist under the given community. + CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); + CriteriaQuery criteriaQuery = criteriaBuilder.createQuery(UUID.class); + Root bitstreamRoot = criteriaQuery.from(Bitstream.class); + criteriaQuery.select(bitstreamRoot.get(Bitstream_.id)); + // Joins from Bitstream -> Bundle -> Item -> Collection + Join joinBundle = bitstreamRoot.join(Bitstream_.bundles); + Join joinItem = joinBundle.join(Bundle_.items); + Join joinCollection = joinItem.join(Item_.collections); + // Where "community" is a member of the list of Communities linked by the collection(s) + criteriaQuery.where(criteriaBuilder.isMember(community, joinCollection.get(Collection_.COMMUNITIES))); - query.setParameter("community", community); + // Transform into a query object to execute + Query query = createQuery(context, criteriaQuery); @SuppressWarnings("unchecked") List uuids = query.getResultList(); return new UUIDIterator(context, uuids, Bitstream.class, this); @@ -93,13 +105,20 @@ public class BitstreamDAOImpl extends AbstractHibernateDSODAO impleme @Override public Iterator findByCollection(Context context, Collection collection) throws SQLException { - Query query = createQuery(context, "select b.id from Bitstream b " + - "join b.bundles bitBundles " + - "join bitBundles.items item " + - "join item.collections c " + - "WHERE :collection IN c"); + // Select UUID of all bitstreams, joining from Bitstream -> Bundle -> Item -> Collection + // to find all that exist under the given collection. + CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); + CriteriaQuery criteriaQuery = criteriaBuilder.createQuery(UUID.class); + Root bitstreamRoot = criteriaQuery.from(Bitstream.class); + criteriaQuery.select(bitstreamRoot.get(Bitstream_.id)); + // Joins from Bitstream -> Bundle -> Item + Join joinBundle = bitstreamRoot.join(Bitstream_.bundles); + Join joinItem = joinBundle.join(Bundle_.items); + // Where "collection" is a member of the list of Collections linked by the item(s) + criteriaQuery.where(criteriaBuilder.isMember(collection, joinItem.get(Item_.collections))); - query.setParameter("collection", collection); + // Transform into a query object to execute + Query query = createQuery(context, criteriaQuery); @SuppressWarnings("unchecked") List uuids = query.getResultList(); return new UUIDIterator(context, uuids, Bitstream.class, this); @@ -107,12 +126,19 @@ public class BitstreamDAOImpl extends AbstractHibernateDSODAO impleme @Override public Iterator findByItem(Context context, Item item) throws SQLException { - Query query = createQuery(context, "select b.id from Bitstream b " + - "join b.bundles bitBundles " + - "join bitBundles.items item " + - "WHERE :item IN item"); + // Select UUID of all bitstreams, joining from Bitstream -> Bundle -> Item + // to find all that exist under the given item. + CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); + CriteriaQuery criteriaQuery = criteriaBuilder.createQuery(UUID.class); + Root bitstreamRoot = criteriaQuery.from(Bitstream.class); + criteriaQuery.select(bitstreamRoot.get(Bitstream_.id)); + // Join from Bitstream -> Bundle + Join joinBundle = bitstreamRoot.join(Bitstream_.bundles); + // Where "item" is a member of the list of Items linked by the bundle(s) + criteriaQuery.where(criteriaBuilder.isMember(item, joinBundle.get(Bundle_.items))); - query.setParameter("item", item); + // Transform into a query object to execute + Query query = createQuery(context, criteriaQuery); @SuppressWarnings("unchecked") List uuids = query.getResultList(); return new UUIDIterator(context, uuids, Bitstream.class, this); diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/BitstreamFormatDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/BitstreamFormatDAOImpl.java index 4d9283bbec..eaf58fbb88 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/impl/BitstreamFormatDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/BitstreamFormatDAOImpl.java @@ -10,11 +10,11 @@ package org.dspace.content.dao.impl; import java.sql.SQLException; import java.util.LinkedList; import java.util.List; -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.content.BitstreamFormat; import org.dspace.content.BitstreamFormat_; import org.dspace.content.dao.BitstreamFormatDAO; @@ -119,7 +119,7 @@ public class BitstreamFormatDAOImpl extends AbstractHibernateDAO orderList = new LinkedList<>(); + List orderList = new LinkedList<>(); orderList.add(criteriaBuilder.desc(bitstreamFormatRoot.get(BitstreamFormat_.supportLevel))); orderList.add(criteriaBuilder.asc(bitstreamFormatRoot.get(BitstreamFormat_.shortDescription))); criteriaQuery.orderBy(orderList); @@ -142,13 +142,10 @@ public class BitstreamFormatDAOImpl extends AbstractHibernateDAO findAll(Context context, Class clazz) throws SQLException { CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); - CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, BitstreamFormat.class); + CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, BitstreamFormat.class); Root bitstreamFormatRoot = criteriaQuery.from(BitstreamFormat.class); criteriaQuery.select(bitstreamFormatRoot); - - List orderList = new LinkedList<>(); - orderList.add(criteriaBuilder.asc(bitstreamFormatRoot.get(BitstreamFormat_.id))); - criteriaQuery.orderBy(orderList); + criteriaQuery.orderBy(criteriaBuilder.asc(bitstreamFormatRoot.get(BitstreamFormat_.id))); return list(context, criteriaQuery, false, BitstreamFormat.class, -1, -1); } diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/CollectionDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/CollectionDAOImpl.java index befa1397a8..841da319f0 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/impl/CollectionDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/CollectionDAOImpl.java @@ -12,13 +12,13 @@ import java.util.AbstractMap; import java.util.ArrayList; import java.util.List; import java.util.Map; -import javax.persistence.Query; -import javax.persistence.criteria.CriteriaBuilder; -import javax.persistence.criteria.CriteriaQuery; -import javax.persistence.criteria.Join; -import javax.persistence.criteria.Predicate; -import javax.persistence.criteria.Root; +import jakarta.persistence.Query; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Join; +import jakarta.persistence.criteria.Predicate; +import jakarta.persistence.criteria.Root; import org.dspace.authorize.ResourcePolicy; import org.dspace.authorize.ResourcePolicy_; import org.dspace.content.Collection; @@ -71,12 +71,12 @@ public class CollectionDAOImpl extends AbstractHibernateDSODAO imple query.append("SELECT c" + " FROM Collection c" + " left join c.metadata title on title.metadataField = :sortField and" + - " title.dSpaceObject = c.id and" + + " title.dSpaceObject = c and" + " title.place = (select min(internal.place) " + "from c.metadata internal " + "where internal.metadataField = :sortField and" + - " internal.dSpaceObject = c.id)" + - " ORDER BY LOWER(title.value)"); + " internal.dSpaceObject = c)" + + " ORDER BY LOWER(CAST(title.value as string))"); Query hibernateQuery = createQuery(context, query.toString()); if (offset != null) { hibernateQuery.setFirstResult(offset); diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/CommunityDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/CommunityDAOImpl.java index 7a37504851..5712b89859 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/impl/CommunityDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/CommunityDAOImpl.java @@ -10,13 +10,13 @@ package org.dspace.content.dao.impl; import java.sql.SQLException; import java.util.LinkedList; import java.util.List; -import javax.persistence.Query; -import javax.persistence.criteria.CriteriaBuilder; -import javax.persistence.criteria.CriteriaQuery; -import javax.persistence.criteria.Join; -import javax.persistence.criteria.Predicate; -import javax.persistence.criteria.Root; +import jakarta.persistence.Query; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Join; +import jakarta.persistence.criteria.Predicate; +import jakarta.persistence.criteria.Root; import org.dspace.authorize.ResourcePolicy; import org.dspace.authorize.ResourcePolicy_; import org.dspace.content.Community; @@ -68,12 +68,12 @@ public class CommunityDAOImpl extends AbstractHibernateDSODAO impleme queryBuilder.append("SELECT c" + " FROM Community c" + " left join c.metadata title on title.metadataField = :sortField and" + - " title.dSpaceObject = c.id and" + + " title.dSpaceObject = c and" + " title.place = (select min(internal.place) " + "from c.metadata internal " + "where internal.metadataField = :sortField and" + - " internal.dSpaceObject = c.id)" + - " ORDER BY LOWER(title.value)"); + " internal.dSpaceObject = c)" + + " ORDER BY LOWER(CAST(title.value as string))"); Query query = createQuery(context, queryBuilder.toString()); if (offset != null) { query.setFirstResult(offset); @@ -108,13 +108,13 @@ public class CommunityDAOImpl extends AbstractHibernateDSODAO impleme queryBuilder.append("SELECT c" + " FROM Community c" + " left join c.metadata title on title.metadataField = :sortField and" + - " title.dSpaceObject = c.id and" + + " title.dSpaceObject = c and" + " title.place = (select min(internal.place) " + "from c.metadata internal " + "where internal.metadataField = :sortField and" + - " internal.dSpaceObject = c.id)" + + " internal.dSpaceObject = c)" + " WHERE c.parentCommunities IS EMPTY " + - " ORDER BY LOWER(title.value)"); + " ORDER BY LOWER(CAST(title.value as string))"); Query query = createQuery(context, queryBuilder.toString()); query.setParameter("sortField", sortField); query.setHint("org.hibernate.cacheable", Boolean.TRUE); diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/EntityTypeDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/EntityTypeDAOImpl.java index 489f4cd066..32af7ed35c 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/impl/EntityTypeDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/EntityTypeDAOImpl.java @@ -10,11 +10,11 @@ package org.dspace.content.dao.impl; import java.sql.SQLException; import java.util.LinkedList; import java.util.List; -import javax.persistence.criteria.CriteriaBuilder; -import javax.persistence.criteria.CriteriaQuery; -import javax.persistence.criteria.Order; -import javax.persistence.criteria.Root; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Order; +import jakarta.persistence.criteria.Root; import org.dspace.content.EntityType; import org.dspace.content.EntityType_; import org.dspace.content.dao.EntityTypeDAO; @@ -59,9 +59,9 @@ public class EntityTypeDAOImpl extends AbstractHibernateDAO implemen @Override public int countEntityTypesByNames(Context context, List names) throws SQLException { CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); - CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, EntityType.class); + CriteriaQuery criteriaQuery = criteriaBuilder.createQuery(Long.class); Root entityTypeRoot = criteriaQuery.from(EntityType.class); - criteriaQuery.select(entityTypeRoot); + criteriaQuery.select(criteriaBuilder.count(entityTypeRoot)); criteriaQuery.where(entityTypeRoot.get(EntityType_.LABEL).in(names)); return count(context, criteriaQuery, criteriaBuilder, entityTypeRoot); } diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/ItemDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/ItemDAOImpl.java index a92e089302..bd04264838 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/impl/ItemDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/ItemDAOImpl.java @@ -8,37 +8,37 @@ package org.dspace.content.dao.impl; import java.sql.SQLException; +import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.UUID; -import javax.persistence.Query; -import javax.persistence.TemporalType; -import javax.persistence.criteria.CriteriaBuilder; -import javax.persistence.criteria.CriteriaQuery; -import javax.persistence.criteria.Root; +import jakarta.persistence.Query; +import jakarta.persistence.TemporalType; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaBuilder.In; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Predicate; +import jakarta.persistence.criteria.Root; +import jakarta.persistence.criteria.Subquery; import org.apache.logging.log4j.Logger; import org.dspace.content.Collection; +import org.dspace.content.DSpaceObject_; import org.dspace.content.Item; import org.dspace.content.Item_; import org.dspace.content.MetadataField; import org.dspace.content.MetadataValue; +import org.dspace.content.MetadataValue_; import org.dspace.content.dao.ItemDAO; +import org.dspace.contentreport.QueryOperator; +import org.dspace.contentreport.QueryPredicate; import org.dspace.core.AbstractHibernateDSODAO; import org.dspace.core.Context; import org.dspace.core.UUIDIterator; import org.dspace.eperson.EPerson; -import org.hibernate.Criteria; -import org.hibernate.criterion.Criterion; -import org.hibernate.criterion.DetachedCriteria; -import org.hibernate.criterion.Order; -import org.hibernate.criterion.Projections; -import org.hibernate.criterion.Property; -import org.hibernate.criterion.Restrictions; -import org.hibernate.criterion.Subqueries; -import org.hibernate.type.StandardBasicTypes; +import org.dspace.util.JpaCriteriaBuilderKit; /** * Hibernate implementation of the Database Access Object interface class for the Item object. @@ -51,7 +51,6 @@ public class ItemDAOImpl extends AbstractHibernateDSODAO implements ItemDA private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(ItemDAOImpl.class); protected ItemDAOImpl() { - super(); } @Override @@ -112,7 +111,7 @@ public class ItemDAOImpl extends AbstractHibernateDSODAO implements ItemDA queryStr.append(" AND discoverable = :discoverable"); if (lastModified != null) { - queryStr.append(" AND last_modified > :last_modified"); + queryStr.append(" AND lastModified > :last_modified"); } queryStr.append(" ORDER BY i.id"); @@ -193,118 +192,102 @@ public class ItemDAOImpl extends AbstractHibernateDSODAO implements ItemDA return new UUIDIterator(context, uuids, Item.class, this); } - enum OP { - equals { - public Criterion buildPredicate(String val, String regexClause) { - return Property.forName("mv.value").eq(val); - } - }, - not_equals { - public Criterion buildPredicate(String val, String regexClause) { - return OP.equals.buildPredicate(val, regexClause); - } - }, - like { - public Criterion buildPredicate(String val, String regexClause) { - return Property.forName("mv.value").like(val); - } - }, - not_like { - public Criterion buildPredicate(String val, String regexClause) { - return OP.like.buildPredicate(val, regexClause); - } - }, - contains { - public Criterion buildPredicate(String val, String regexClause) { - return Property.forName("mv.value").like("%" + val + "%"); - } - }, - doesnt_contain { - public Criterion buildPredicate(String val, String regexClause) { - return OP.contains.buildPredicate(val, regexClause); - } - }, - exists { - public Criterion buildPredicate(String val, String regexClause) { - return Property.forName("mv.value").isNotNull(); - } - }, - doesnt_exist { - public Criterion buildPredicate(String val, String regexClause) { - return OP.exists.buildPredicate(val, regexClause); - } - }, - matches { - public Criterion buildPredicate(String val, String regexClause) { - return Restrictions.sqlRestriction(regexClause, val, StandardBasicTypes.STRING); - } - }, - doesnt_match { - public Criterion buildPredicate(String val, String regexClause) { - return OP.matches.buildPredicate(val, regexClause); - } - - }; - public abstract Criterion buildPredicate(String val, String regexClause); + @Override + public List findByMetadataQuery(Context context, List queryPredicates, + List collectionUuids, String regexClause, long offset, int limit) throws SQLException { + CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); + CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Item.class); + Root itemRoot = criteriaQuery.from(Item.class); + criteriaQuery.select(itemRoot); + List predicates = toPredicates(criteriaBuilder, criteriaQuery, itemRoot, + queryPredicates, collectionUuids, regexClause); + criteriaQuery.where(criteriaBuilder.and(predicates.stream().toArray(Predicate[]::new))); + criteriaQuery.orderBy(criteriaBuilder.asc(itemRoot.get(DSpaceObject_.id))); + try { + return list(context, criteriaQuery, false, Item.class, limit, (int) offset); + } catch (Exception e) { + e.printStackTrace(); + throw e; + } } @Override - @Deprecated - public Iterator findByMetadataQuery(Context context, List> listFieldList, - List query_op, List query_val, List collectionUuids, - String regexClause, int offset, int limit) throws SQLException { + public long countForMetadataQuery(Context context, List queryPredicates, + List collectionUuids, String regexClause) throws SQLException { + // Build the query infrastructure + CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); + CriteriaQuery criteriaQuery = criteriaBuilder.createQuery(Long.class); + // Select + Root itemRoot = criteriaQuery.from(Item.class); + // Apply the selected predicates + List predicates = toPredicates(criteriaBuilder, criteriaQuery, itemRoot, + queryPredicates, collectionUuids, regexClause); + criteriaQuery.where(criteriaBuilder.and(predicates.stream().toArray(Predicate[]::new))); + // Execute the query + return countLong(context, criteriaQuery, criteriaBuilder, itemRoot); + } - Criteria criteria = getHibernateSession(context).createCriteria(Item.class, "item"); - criteria.setFirstResult(offset); - criteria.setMaxResults(limit); + private List toPredicates(CriteriaBuilder criteriaBuilder, CriteriaQuery query, + Root root, List queryPredicates, + List collectionUuids, String regexClause) { + List predicates = new ArrayList<>(); if (!collectionUuids.isEmpty()) { - DetachedCriteria dcollCriteria = DetachedCriteria.forClass(Collection.class, "coll"); - dcollCriteria.setProjection(Projections.property("coll.id")); - dcollCriteria.add(Restrictions.eqProperty("coll.id", "item.owningCollection")); - dcollCriteria.add(Restrictions.in("coll.id", collectionUuids)); - criteria.add(Subqueries.exists(dcollCriteria)); + Subquery scollQuery = query.subquery(Collection.class); + Root collRoot = scollQuery.from(Collection.class); + In inColls = criteriaBuilder.in(collRoot.get(DSpaceObject_.ID)); + collectionUuids.forEach(inColls::value); + scollQuery.select(collRoot.get(DSpaceObject_.ID)) + .where(criteriaBuilder.and( + criteriaBuilder.equal(collRoot.get(DSpaceObject_.ID), + root.get(Item_.OWNING_COLLECTION).get(DSpaceObject_.ID)), + collRoot.get(DSpaceObject_.ID).in(collectionUuids) + )); + predicates.add(criteriaBuilder.exists(scollQuery)); } - int index = Math.min(listFieldList.size(), Math.min(query_op.size(), query_val.size())); - StringBuilder sb = new StringBuilder(); - - for (int i = 0; i < index; i++) { - OP op = OP.valueOf(query_op.get(i)); + for (int i = 0; i < queryPredicates.size(); i++) { + QueryPredicate predicate = queryPredicates.get(i); + QueryOperator op = predicate.getOperator(); if (op == null) { - log.warn("Skipping Invalid Operator: " + query_op.get(i)); + log.warn("Skipping Invalid Operator: null"); continue; } - if (op == OP.matches || op == OP.doesnt_match) { + if (op.getUsesRegex()) { if (regexClause.isEmpty()) { - log.warn("Skipping Unsupported Regex Operator: " + query_op.get(i)); + log.warn("Skipping Unsupported Regex Operator: " + op); continue; } } - DetachedCriteria subcriteria = DetachedCriteria.forClass(MetadataValue.class, "mv"); - subcriteria.add(Property.forName("mv.dSpaceObject").eqProperty("item.id")); - subcriteria.setProjection(Projections.property("mv.dSpaceObject")); + List mvPredicates = new ArrayList<>(); + Subquery mvQuery = query.subquery(MetadataValue.class); + Root mvRoot = mvQuery.from(MetadataValue.class); + mvPredicates.add(criteriaBuilder.equal( + mvRoot.get(MetadataValue_.D_SPACE_OBJECT), root)); - if (!listFieldList.get(i).isEmpty()) { - subcriteria.add(Restrictions.in("metadataField", listFieldList.get(i))); + if (!predicate.getFields().isEmpty()) { + In inFields = criteriaBuilder.in(mvRoot.get(MetadataValue_.METADATA_FIELD)); + predicate.getFields().forEach(inFields::value); + mvPredicates.add(inFields); } - subcriteria.add(op.buildPredicate(query_val.get(i), regexClause)); + JpaCriteriaBuilderKit jpaKit = new JpaCriteriaBuilderKit<>(criteriaBuilder, mvQuery, mvRoot); + mvPredicates.add(op.buildJpaPredicate(predicate.getValue(), regexClause, jpaKit)); - if (op == OP.exists || op == OP.equals || op == OP.like || op == OP.contains || op == OP.matches) { - criteria.add(Subqueries.exists(subcriteria)); + mvQuery.select(mvRoot.get(MetadataValue_.D_SPACE_OBJECT)) + .where(mvPredicates.stream().toArray(Predicate[]::new)); + + if (op.getNegate()) { + predicates.add(criteriaBuilder.not(criteriaBuilder.exists(mvQuery))); } else { - criteria.add(Subqueries.notExists(subcriteria)); + predicates.add(criteriaBuilder.exists(mvQuery)); } } - criteria.addOrder(Order.asc("item.id")); - - log.debug(String.format("Running custom query with %d filters", index)); - - return ((List) criteria.list()).iterator(); + log.debug(String.format("Running custom query with %d filters", queryPredicates.size())); + return predicates; } @Override @@ -325,11 +308,18 @@ public class ItemDAOImpl extends AbstractHibernateDSODAO implements ItemDA @Override public Iterator findArchivedByCollection(Context context, Collection collection, Integer limit, Integer offset) throws SQLException { - Query query = createQuery(context, - "select i.id from Item i join i.collections c " + - "WHERE :collection IN c AND i.inArchive=:in_archive ORDER BY i.id"); - query.setParameter("collection", collection); - query.setParameter("in_archive", true); + // Select UUID of all items which have this "collection" in their list of collections and are in_archive + CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); + CriteriaQuery criteriaQuery = criteriaBuilder.createQuery(UUID.class); + Root itemRoot = criteriaQuery.from(Item.class); + criteriaQuery.select(itemRoot.get(Item_.id)); + criteriaQuery.where(criteriaBuilder.and( + criteriaBuilder.isTrue((itemRoot.get(Item_.inArchive))), + criteriaBuilder.isMember(collection, itemRoot.get(Item_.collections)))); + criteriaQuery.orderBy(criteriaBuilder.asc(itemRoot.get((Item_.id)))); + + // Transform into a query object to execute + Query query = createQuery(context, criteriaQuery); if (offset != null) { query.setFirstResult(offset); } @@ -345,24 +335,23 @@ public class ItemDAOImpl extends AbstractHibernateDSODAO implements ItemDA public Iterator findArchivedByCollectionExcludingOwning(Context context, Collection collection, Integer limit, Integer offset) throws SQLException { CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); - CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Item.class); + CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Item.class); Root itemRoot = criteriaQuery.from(Item.class); criteriaQuery.select(itemRoot); criteriaQuery.where(criteriaBuilder.and( criteriaBuilder.notEqual(itemRoot.get(Item_.owningCollection), collection), criteriaBuilder.isMember(collection, itemRoot.get(Item_.collections)), criteriaBuilder.isTrue(itemRoot.get(Item_.inArchive)))); - criteriaQuery.orderBy(criteriaBuilder.asc(itemRoot.get(Item_.id))); - criteriaQuery.groupBy(itemRoot.get(Item_.id)); + criteriaQuery.orderBy(criteriaBuilder.asc(itemRoot.get(DSpaceObject_.id))); return list(context, criteriaQuery, false, Item.class, limit, offset).iterator(); } @Override public int countArchivedByCollectionExcludingOwning(Context context, Collection collection) throws SQLException { CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); - CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Item.class); + CriteriaQuery criteriaQuery = criteriaBuilder.createQuery(Long.class); Root itemRoot = criteriaQuery.from(Item.class); - criteriaQuery.select(itemRoot); + criteriaQuery.select(criteriaBuilder.count(itemRoot)); criteriaQuery.where(criteriaBuilder.and( criteriaBuilder.notEqual(itemRoot.get(Item_.owningCollection), collection), criteriaBuilder.isMember(collection, itemRoot.get(Item_.collections)), @@ -372,9 +361,16 @@ public class ItemDAOImpl extends AbstractHibernateDSODAO implements ItemDA @Override public Iterator findAllByCollection(Context context, Collection collection) throws SQLException { - Query query = createQuery(context, - "select i.id from Item i join i.collections c WHERE :collection IN c ORDER BY i.id"); - query.setParameter("collection", collection); + // Select UUID of all items which have this "collection" in their list of collections + CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); + CriteriaQuery criteriaQuery = criteriaBuilder.createQuery(UUID.class); + Root itemRoot = criteriaQuery.from(Item.class); + criteriaQuery.select(itemRoot.get(Item_.id)); + criteriaQuery.where(criteriaBuilder.isMember(collection, itemRoot.get(Item_.collections))); + criteriaQuery.orderBy(criteriaBuilder.asc(itemRoot.get((Item_.id)))); + + // Transform into a query object to execute + Query query = createQuery(context, criteriaQuery); @SuppressWarnings("unchecked") List uuids = query.getResultList(); return new UUIDIterator(context, uuids, Item.class, this); @@ -383,10 +379,16 @@ public class ItemDAOImpl extends AbstractHibernateDSODAO implements ItemDA @Override public Iterator findAllByCollection(Context context, Collection collection, Integer limit, Integer offset) throws SQLException { - Query query = createQuery(context, - "select i.id from Item i join i.collections c WHERE :collection IN c ORDER BY i.id"); - query.setParameter("collection", collection); + // Build Query to select UUID of all items which have this "collection" in their list of collections. + CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); + CriteriaQuery criteriaQuery = criteriaBuilder.createQuery(UUID.class); + Root itemRoot = criteriaQuery.from(Item.class); + criteriaQuery.select(itemRoot.get(Item_.id)); + criteriaQuery.where(criteriaBuilder.isMember(collection, itemRoot.get(Item_.collections))); + criteriaQuery.orderBy(criteriaBuilder.asc(itemRoot.get((Item_.id)))); + // Transform into a query object to execute + Query query = createQuery(context, criteriaQuery); if (offset != null) { query.setFirstResult(offset); } @@ -402,16 +404,19 @@ public class ItemDAOImpl extends AbstractHibernateDSODAO implements ItemDA public int countItems(Context context, Collection collection, boolean includeArchived, boolean includeWithdrawn, boolean discoverable) throws SQLException { - Query query = createQuery(context, - "select count(i) from Item i join i.collections c " + - "WHERE :collection IN c AND i.inArchive=:in_archive AND i.withdrawn=:withdrawn " + - "AND discoverable=:discoverable"); - query.setParameter("collection", collection); - query.setParameter("in_archive", includeArchived); - query.setParameter("withdrawn", includeWithdrawn); - query.setParameter("discoverable", discoverable); - - return count(query); + // Build query to select all Items have this "collection" in their list of collections + // AND also have the inArchive or isWithdrawn set as specified + CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); + CriteriaQuery criteriaQuery = criteriaBuilder.createQuery(Long.class); + Root itemRoot = criteriaQuery.from(Item.class); + criteriaQuery.select(criteriaBuilder.count(itemRoot)); + criteriaQuery.where(criteriaBuilder.and( + criteriaBuilder.equal(itemRoot.get(Item_.inArchive), includeArchived), + criteriaBuilder.equal(itemRoot.get(Item_.withdrawn), includeWithdrawn), + criteriaBuilder.equal(itemRoot.get(Item_.discoverable), discoverable), + criteriaBuilder.isMember(collection, itemRoot.get(Item_.collections)))); + // Execute and return count + return count(context, criteriaQuery, criteriaBuilder, itemRoot); } @Override @@ -436,7 +441,7 @@ public class ItemDAOImpl extends AbstractHibernateDSODAO implements ItemDA public Iterator findByLastModifiedSince(Context context, Date since) throws SQLException { Query query = createQuery(context, - "SELECT i.id FROM Item i WHERE last_modified > :last_modified ORDER BY id"); + "SELECT i.id FROM Item i WHERE lastModified > :last_modified ORDER BY id"); query.setParameter("last_modified", since, TemporalType.TIMESTAMP); @SuppressWarnings("unchecked") List uuids = query.getResultList(); diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/MetadataFieldDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/MetadataFieldDAOImpl.java index e64aaa4dd2..ddbde361d5 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/impl/MetadataFieldDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/MetadataFieldDAOImpl.java @@ -12,12 +12,12 @@ import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; -import javax.persistence.Query; -import javax.persistence.criteria.CriteriaBuilder; -import javax.persistence.criteria.CriteriaQuery; -import javax.persistence.criteria.Join; -import javax.persistence.criteria.Root; +import jakarta.persistence.Query; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Join; +import jakarta.persistence.criteria.Root; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.dspace.content.MetadataField; @@ -158,7 +158,7 @@ public class MetadataFieldDAOImpl extends AbstractHibernateDAO im Join join = metadataFieldRoot.join("metadataSchema"); criteriaQuery.select(metadataFieldRoot); - List orderList = new LinkedList<>(); + List orderList = new LinkedList<>(); orderList.add(criteriaBuilder.asc(join.get(MetadataSchema_.name))); orderList.add(criteriaBuilder.asc(metadataFieldRoot.get(MetadataField_.element))); orderList.add(criteriaBuilder.asc(metadataFieldRoot.get(MetadataField_.qualifier))); diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/MetadataSchemaDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/MetadataSchemaDAOImpl.java index 71eb487b83..4630bed90b 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/impl/MetadataSchemaDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/MetadataSchemaDAOImpl.java @@ -10,11 +10,11 @@ package org.dspace.content.dao.impl; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; -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.content.MetadataSchema; import org.dspace.content.MetadataSchema_; import org.dspace.content.dao.MetadataSchemaDAO; @@ -63,7 +63,7 @@ public class MetadataSchemaDAOImpl extends AbstractHibernateDAO Root metadataSchemaRoot = criteriaQuery.from(MetadataSchema.class); criteriaQuery.select(metadataSchemaRoot); - List orderList = new ArrayList<>(); + List orderList = new ArrayList<>(); orderList.add(criteriaBuilder.asc(metadataSchemaRoot.get(MetadataSchema_.id))); criteriaQuery.orderBy(orderList); diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/MetadataValueDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/MetadataValueDAOImpl.java index f37ced9ab7..dc624c98c6 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/impl/MetadataValueDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/MetadataValueDAOImpl.java @@ -10,12 +10,12 @@ package org.dspace.content.dao.impl; import java.sql.SQLException; import java.util.Iterator; import java.util.List; -import javax.persistence.Query; -import javax.persistence.criteria.CriteriaBuilder; -import javax.persistence.criteria.CriteriaQuery; -import javax.persistence.criteria.Join; -import javax.persistence.criteria.Root; +import jakarta.persistence.Query; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Join; +import jakarta.persistence.criteria.Root; import org.dspace.content.MetadataField; import org.dspace.content.MetadataField_; import org.dspace.content.MetadataValue; @@ -53,7 +53,7 @@ public class MetadataValueDAOImpl extends AbstractHibernateDAO im MetadataField metadataField, String value) throws SQLException { String queryString = "SELECT m from MetadataValue m " + - "join Item i on m.dSpaceObject = i.id where m.metadataField.id = :metadata_field_id " + + "join Item i on m.dSpaceObject = i where m.metadataField.id = :metadata_field_id " + "and m.value = :text_value"; Query query = createQuery(context, queryString); query.setParameter("metadata_field_id", metadataField.getID()); @@ -84,7 +84,7 @@ public class MetadataValueDAOImpl extends AbstractHibernateDAO im public MetadataValue getMinimum(Context context, int metadataFieldId) throws SQLException { String queryString = "SELECT m FROM MetadataValue m JOIN FETCH m.metadataField WHERE m.metadataField.id = " + - ":metadata_field_id ORDER BY text_value"; + ":metadata_field_id ORDER BY value"; Query query = createQuery(context, queryString); query.setParameter("metadata_field_id", metadataFieldId); query.setMaxResults(1); diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/ProcessDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/ProcessDAOImpl.java index d719b5006c..829dd32800 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/impl/ProcessDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/ProcessDAOImpl.java @@ -14,11 +14,11 @@ import java.util.Date; import java.util.LinkedList; import java.util.List; import java.util.Map; -import javax.persistence.criteria.CriteriaBuilder; -import javax.persistence.criteria.CriteriaQuery; -import javax.persistence.criteria.Predicate; -import javax.persistence.criteria.Root; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Predicate; +import jakarta.persistence.criteria.Root; import org.apache.commons.lang3.StringUtils; import org.dspace.content.ProcessStatus; import org.dspace.content.dao.ProcessDAO; @@ -75,9 +75,9 @@ public class ProcessDAOImpl extends AbstractHibernateDAO implements Pro public int countRows(Context context) throws SQLException { CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); - CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Process.class); + CriteriaQuery criteriaQuery = criteriaBuilder.createQuery(Long.class); Root processRoot = criteriaQuery.from(Process.class); - criteriaQuery.select(processRoot); + criteriaQuery.select(criteriaBuilder.count(processRoot)); return count(context, criteriaQuery, criteriaBuilder, processRoot); @@ -143,9 +143,9 @@ public class ProcessDAOImpl extends AbstractHibernateDAO implements Pro throws SQLException { CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); - CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Process.class); + CriteriaQuery criteriaQuery = criteriaBuilder.createQuery(Long.class); Root processRoot = criteriaQuery.from(Process.class); - criteriaQuery.select(processRoot); + criteriaQuery.select(criteriaBuilder.count(processRoot)); addProcessQueryParameters(processQueryParameterContainer, criteriaBuilder, criteriaQuery, processRoot); return count(context, criteriaQuery, criteriaBuilder, processRoot); @@ -178,7 +178,7 @@ public class ProcessDAOImpl extends AbstractHibernateDAO implements Pro criteriaQuery.select(processRoot); criteriaQuery.where(criteriaBuilder.equal(processRoot.get(Process_.E_PERSON), user)); - List orderList = new LinkedList<>(); + List orderList = new LinkedList<>(); orderList.add(criteriaBuilder.desc(processRoot.get(Process_.PROCESS_ID))); criteriaQuery.orderBy(orderList); @@ -188,10 +188,10 @@ public class ProcessDAOImpl extends AbstractHibernateDAO implements Pro @Override public int countByUser(Context context, EPerson user) throws SQLException { CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); - CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Process.class); + CriteriaQuery criteriaQuery = criteriaBuilder.createQuery(Long.class); Root processRoot = criteriaQuery.from(Process.class); - criteriaQuery.select(processRoot); + criteriaQuery.select(criteriaBuilder.count(processRoot)); criteriaQuery.where(criteriaBuilder.equal(processRoot.get(Process_.E_PERSON), user)); return count(context, criteriaQuery, criteriaBuilder, processRoot); } diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/RelationshipDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/RelationshipDAOImpl.java index e2f84bc1cb..43bbc15c31 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/impl/RelationshipDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/RelationshipDAOImpl.java @@ -12,13 +12,13 @@ import java.util.ArrayList; import java.util.List; import java.util.UUID; import java.util.stream.Collectors; -import javax.persistence.Query; -import javax.persistence.Tuple; -import javax.persistence.criteria.CriteriaBuilder; -import javax.persistence.criteria.CriteriaQuery; -import javax.persistence.criteria.Predicate; -import javax.persistence.criteria.Root; +import jakarta.persistence.Query; +import jakarta.persistence.Tuple; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Predicate; +import jakarta.persistence.criteria.Root; import org.dspace.content.Item; import org.dspace.content.Item_; import org.dspace.content.Relationship; @@ -167,9 +167,9 @@ public class RelationshipDAOImpl extends AbstractHibernateDAO impl Context context, Item item, boolean excludeTilted, boolean excludeNonLatest ) throws SQLException { CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); - CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Relationship.class); + CriteriaQuery criteriaQuery = criteriaBuilder.createQuery(Long.class); Root relationshipRoot = criteriaQuery.from(Relationship.class); - criteriaQuery.select(relationshipRoot); + criteriaQuery.select(criteriaBuilder.count(relationshipRoot)); criteriaQuery.where( criteriaBuilder.or( @@ -355,9 +355,9 @@ public class RelationshipDAOImpl extends AbstractHibernateDAO impl public int countByRelationshipType(Context context, RelationshipType relationshipType) throws SQLException { CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); - CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Relationship.class); + CriteriaQuery criteriaQuery = criteriaBuilder.createQuery(Long.class); Root relationshipRoot = criteriaQuery.from(Relationship.class); - criteriaQuery.select(relationshipRoot); + criteriaQuery.select(criteriaBuilder.count(relationshipRoot)); criteriaQuery .where(criteriaBuilder.equal(relationshipRoot.get(Relationship_.relationshipType), relationshipType)); return count(context, criteriaQuery, criteriaBuilder, relationshipRoot); @@ -366,9 +366,9 @@ public class RelationshipDAOImpl extends AbstractHibernateDAO impl @Override public int countRows(Context context) throws SQLException { CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); - CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Relationship.class); + CriteriaQuery criteriaQuery = criteriaBuilder.createQuery(Long.class); Root relationshipRoot = criteriaQuery.from(Relationship.class); - criteriaQuery.select(relationshipRoot); + criteriaQuery.select(criteriaBuilder.count(relationshipRoot)); return count(context, criteriaQuery, criteriaBuilder, relationshipRoot); } @@ -377,9 +377,9 @@ public class RelationshipDAOImpl extends AbstractHibernateDAO impl Context context, Item item, RelationshipType relationshipType, boolean isLeft, boolean excludeNonLatest ) throws SQLException { CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); - CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Relationship.class); + CriteriaQuery criteriaQuery = criteriaBuilder.createQuery(Long.class); Root relationshipRoot = criteriaQuery.from(Relationship.class); - criteriaQuery.select(relationshipRoot); + criteriaQuery.select(criteriaBuilder.count(relationshipRoot)); if (isLeft) { criteriaQuery.where( @@ -407,8 +407,9 @@ public class RelationshipDAOImpl extends AbstractHibernateDAO impl ids.add(relationshipType.getID()); } CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); - CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Relationship.class); + CriteriaQuery criteriaQuery = criteriaBuilder.createQuery(Long.class); Root relationshipRoot = criteriaQuery.from(Relationship.class); + criteriaQuery.select(criteriaBuilder.count(relationshipRoot)); criteriaQuery.where(relationshipRoot.get(Relationship_.relationshipType).in(ids)); return count(context, criteriaQuery, criteriaBuilder, relationshipRoot); } @@ -417,14 +418,14 @@ public class RelationshipDAOImpl extends AbstractHibernateDAO impl public List findByItemAndRelationshipTypeAndList(Context context, UUID focusUUID, RelationshipType relationshipType, List items, boolean isLeft, int offset, int limit) throws SQLException { - String side = isLeft ? "left_id" : "right_id"; - String otherSide = !isLeft ? "left_id" : "right_id"; + String side = isLeft ? "leftItem.id" : "rightItem.id"; + String otherSide = !isLeft ? "leftItem.id" : "rightItem.id"; Query query = createQuery(context, "FROM " + Relationship.class.getSimpleName() + - " WHERE type_id = (:typeId) " + + " WHERE relationshipType = :type " + "AND " + side + " = (:focusUUID) " + "AND " + otherSide + " in (:list) " + "ORDER BY id"); - query.setParameter("typeId", relationshipType.getID()); + query.setParameter("type", relationshipType); query.setParameter("focusUUID", focusUUID); query.setParameter("list", items); return list(query, limit, offset); @@ -433,14 +434,14 @@ public class RelationshipDAOImpl extends AbstractHibernateDAO impl @Override public int countByItemAndRelationshipTypeAndList(Context context, UUID focusUUID, RelationshipType relationshipType, List items, boolean isLeft) throws SQLException { - String side = isLeft ? "left_id" : "right_id"; - String otherSide = !isLeft ? "left_id" : "right_id"; + String side = isLeft ? "leftItem.id" : "rightItem.id"; + String otherSide = !isLeft ? "leftItem.id" : "rightItem.id"; Query query = createQuery(context, "SELECT count(*) " + "FROM " + Relationship.class.getSimpleName() + - " WHERE type_id = (:typeId) " + + " WHERE relationshipType = :type " + "AND " + side + " = (:focusUUID) " + "AND " + otherSide + " in (:list)"); - query.setParameter("typeId", relationshipType.getID()); + query.setParameter("type", relationshipType); query.setParameter("focusUUID", focusUUID); query.setParameter("list", items); return count(query); diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/RelationshipTypeDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/RelationshipTypeDAOImpl.java index 7fff2a1f57..7b0e33fd41 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/impl/RelationshipTypeDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/RelationshipTypeDAOImpl.java @@ -10,10 +10,10 @@ package org.dspace.content.dao.impl; import java.sql.SQLException; import java.util.LinkedList; 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.content.EntityType; import org.dspace.content.RelationshipType; import org.dspace.content.RelationshipType_; @@ -93,7 +93,7 @@ public class RelationshipTypeDAOImpl extends AbstractHibernateDAO orderList = new LinkedList<>(); + List orderList = new LinkedList<>(); orderList.add(criteriaBuilder.asc(relationshipTypeRoot.get(RelationshipType_.ID))); criteriaQuery.orderBy(orderList); return list(context, criteriaQuery, false, RelationshipType.class, limit, offset); @@ -128,9 +128,9 @@ public class RelationshipTypeDAOImpl extends AbstractHibernateDAO criteriaQuery = criteriaBuilder.createQuery(Long.class); Root relationshipTypeRoot = criteriaQuery.from(RelationshipType.class); - criteriaQuery.select(relationshipTypeRoot); + criteriaQuery.select(criteriaBuilder.count(relationshipTypeRoot)); criteriaQuery.where(criteriaBuilder.or( criteriaBuilder.equal(relationshipTypeRoot.get(RelationshipType_.leftType), entityType), criteriaBuilder.equal(relationshipTypeRoot.get(RelationshipType_.rightType), entityType) diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/SiteDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/SiteDAOImpl.java index 8889909b1a..ebaa78ae37 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/impl/SiteDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/SiteDAOImpl.java @@ -8,10 +8,10 @@ package org.dspace.content.dao.impl; import java.sql.SQLException; -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.content.Site; import org.dspace.content.dao.SiteDAO; import org.dspace.core.AbstractHibernateDAO; diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/WorkspaceItemDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/WorkspaceItemDAOImpl.java index 1384513655..0862a81e86 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/impl/WorkspaceItemDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/WorkspaceItemDAOImpl.java @@ -12,11 +12,11 @@ import java.util.AbstractMap; import java.util.LinkedList; import java.util.List; import java.util.Map; -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.content.Collection; import org.dspace.content.Item; import org.dspace.content.WorkspaceItem; @@ -88,7 +88,7 @@ public class WorkspaceItemDAOImpl extends AbstractHibernateDAO im Root workspaceItemRoot = criteriaQuery.from(WorkspaceItem.class); criteriaQuery.select(workspaceItemRoot); - List orderList = new LinkedList<>(); + List orderList = new LinkedList<>(); orderList.add(criteriaBuilder.asc(workspaceItemRoot.get(WorkspaceItem_.workspaceItemId))); criteriaQuery.orderBy(orderList); @@ -103,7 +103,7 @@ public class WorkspaceItemDAOImpl extends AbstractHibernateDAO im Root workspaceItemRoot = criteriaQuery.from(WorkspaceItem.class); criteriaQuery.select(workspaceItemRoot); - List orderList = new LinkedList<>(); + List orderList = new LinkedList<>(); orderList.add(criteriaBuilder.asc(workspaceItemRoot.get(WorkspaceItem_.workspaceItemId))); criteriaQuery.orderBy(orderList); diff --git a/dspace-api/src/main/java/org/dspace/content/dto/MetadataValueDTO.java b/dspace-api/src/main/java/org/dspace/content/dto/MetadataValueDTO.java index 2ef0ab0b23..952aa799d2 100644 --- a/dspace-api/src/main/java/org/dspace/content/dto/MetadataValueDTO.java +++ b/dspace-api/src/main/java/org/dspace/content/dto/MetadataValueDTO.java @@ -7,6 +7,9 @@ */ package org.dspace.content.dto; +import java.util.Comparator; +import java.util.Objects; + import org.dspace.content.MetadataField; import org.dspace.content.MetadataSchema; import org.dspace.content.MetadataValue; @@ -49,7 +52,7 @@ public class MetadataValueDTO { * @param schema The schema to be assigned to this MetadataValueDTO object * @param element The element to be assigned to this MetadataValueDTO object * @param qualifier The qualifier to be assigned to this MetadataValueDTO object - * @param language The language to be assigend to this MetadataValueDTO object + * @param language The language to be assigned to this MetadataValueDTO object * @param value The value to be assigned to this MetadataValueDTO object * @param authority The authority to be assigned to this MetadataValueDTO object * @param confidence The confidence to be assigned to this MetadataValueDTO object @@ -70,7 +73,7 @@ public class MetadataValueDTO { * @param schema The schema to be assigned to this MetadataValueDTO object * @param element The element to be assigned to this MetadataValueDTO object * @param qualifier The qualifier to be assigned to this MetadataValueDTO object - * @param language The language to be assigend to this MetadataValueDTO object + * @param language The language to be assigned to this MetadataValueDTO object * @param value The value to be assigned to this MetadataValueDTO object */ public MetadataValueDTO(String schema, String element, String qualifier, String language, String value) { @@ -136,4 +139,62 @@ public class MetadataValueDTO { public void setConfidence(int confidence) { this.confidence = confidence; } + + @Override + public String toString() { + return "MetadataValueDTO{" + + "schema='" + schema + '\'' + + ", element='" + element + '\'' + + ", qualifier='" + qualifier + '\'' + + ", language='" + language + '\'' + + ", value='" + value + '\'' + + ", authority='" + authority + '\'' + + ", confidence=" + confidence + + "}\n"; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + MetadataValueDTO that = (MetadataValueDTO) o; + return confidence == that.confidence && + Objects.equals(schema, that.schema) && Objects.equals(element, that.element) && + Objects.equals(qualifier, that.qualifier) && Objects.equals(language, that.language) && + Objects.equals(value, that.value) && Objects.equals(authority, that.authority); + } + + @Override + public int hashCode() { + return Objects.hash(schema, element, qualifier, language, value, authority, confidence); + } + + /** + * Build a comparator to support proper sorting of MetadataValueDTO objects. + * Order of sorting is based on how these things are normally sorted in human-readable formats, with + * field name -> value -> lang/auth/etc being the usual order we use. In all these individual tests, nulls are + * sorted first (eg. dc.title before dc.title.alternative) + * @see org.dspace.external.model.ExternalDataObject#equals(Object) + * 1. Qualifier + * 2. Element + * 3. Schema + * 4. Value + * 5. Language + * 6. Authority + * @return comparator + */ + public static Comparator comparator() { + return Comparator.comparing(MetadataValueDTO::getQualifier, Comparator.nullsFirst(Comparator.naturalOrder())) + .thenComparing(MetadataValueDTO::getElement, Comparator.nullsFirst(Comparator.naturalOrder())) + .thenComparing(MetadataValueDTO::getSchema, Comparator.nullsFirst(Comparator.naturalOrder())) + .thenComparing(MetadataValueDTO::getValue, Comparator.nullsFirst(Comparator.naturalOrder())) + .thenComparing(MetadataValueDTO::getLanguage, Comparator.nullsFirst(Comparator.naturalOrder())) + .thenComparing(MetadataValueDTO::getAuthority, Comparator.nullsFirst(Comparator.naturalOrder())); + } + + } diff --git a/dspace-api/src/main/java/org/dspace/content/factory/ContentServiceFactory.java b/dspace-api/src/main/java/org/dspace/content/factory/ContentServiceFactory.java index 0b06b34038..3a897081f0 100644 --- a/dspace-api/src/main/java/org/dspace/content/factory/ContentServiceFactory.java +++ b/dspace-api/src/main/java/org/dspace/content/factory/ContentServiceFactory.java @@ -20,6 +20,7 @@ import org.dspace.content.service.CollectionService; import org.dspace.content.service.CommunityService; import org.dspace.content.service.DSpaceObjectLegacySupportService; import org.dspace.content.service.DSpaceObjectService; +import org.dspace.content.service.DuplicateDetectionService; import org.dspace.content.service.EntityService; import org.dspace.content.service.EntityTypeService; import org.dspace.content.service.InProgressSubmissionService; @@ -113,6 +114,13 @@ public abstract class ContentServiceFactory { } } + /** + * Return the implementation of the DuplicateDetectionService interface + * + * @return the DuplicateDetectionService + */ + public abstract DuplicateDetectionService getDuplicateDetectionService(); + public DSpaceObjectService getDSpaceObjectService(T dso) { return getDSpaceObjectService(dso.getType()); } diff --git a/dspace-api/src/main/java/org/dspace/content/factory/ContentServiceFactoryImpl.java b/dspace-api/src/main/java/org/dspace/content/factory/ContentServiceFactoryImpl.java index e970f0bdab..3c3c2bf162 100644 --- a/dspace-api/src/main/java/org/dspace/content/factory/ContentServiceFactoryImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/factory/ContentServiceFactoryImpl.java @@ -18,6 +18,7 @@ import org.dspace.content.service.CollectionService; import org.dspace.content.service.CommunityService; import org.dspace.content.service.DSpaceObjectLegacySupportService; import org.dspace.content.service.DSpaceObjectService; +import org.dspace.content.service.DuplicateDetectionService; import org.dspace.content.service.EntityService; import org.dspace.content.service.EntityTypeService; import org.dspace.content.service.InstallItemService; @@ -81,6 +82,8 @@ public class ContentServiceFactoryImpl extends ContentServiceFactory { private EntityTypeService entityTypeService; @Autowired(required = true) private EntityService entityService; + @Autowired(required = true) + private DuplicateDetectionService duplicateDetectionService; @Override public List> getDSpaceObjectServices() { @@ -181,4 +184,9 @@ public class ContentServiceFactoryImpl extends ContentServiceFactory { public RelationshipMetadataService getRelationshipMetadataService() { return relationshipMetadataService; } + + @Override + public DuplicateDetectionService getDuplicateDetectionService() { + return duplicateDetectionService; + } } diff --git a/dspace-api/src/main/java/org/dspace/content/logic/FilterUtils.java b/dspace-api/src/main/java/org/dspace/content/logic/FilterUtils.java index a878d69e6e..96ec827267 100644 --- a/dspace-api/src/main/java/org/dspace/content/logic/FilterUtils.java +++ b/dspace-api/src/main/java/org/dspace/content/logic/FilterUtils.java @@ -74,7 +74,7 @@ public class FilterUtils { Map, Filter> filters = new HashMap<>(); // Put DOI 'can we create DOI on install / workspace?' filter Filter filter = FilterUtils.getFilterFromConfiguration("identifiers.submission.filter." + configurationSuffix); - // A null filter should be handled safely by the identifier provier (default, or "always true") + // A null filter should be handled safely by the identifier provider (default, or "always true") filters.put(DOI.class, filter); // This won't have an affect until handle providers implement filtering, but is an example of // how the filters can be used for other types diff --git a/dspace-api/src/main/java/org/dspace/content/packager/DSpaceAIPIngester.java b/dspace-api/src/main/java/org/dspace/content/packager/DSpaceAIPIngester.java index e7be7ab511..e231c30809 100644 --- a/dspace-api/src/main/java/org/dspace/content/packager/DSpaceAIPIngester.java +++ b/dspace-api/src/main/java/org/dspace/content/packager/DSpaceAIPIngester.java @@ -139,7 +139,7 @@ public class DSpaceAIPIngester } } - // MODS is acceptable otehrwise.. + // MODS is acceptable otherwise.. if (found == -1) { for (int i = 0; i < dmds.length; ++i) { //NOTE: METS standard actually says this should be MODS (all uppercase). But, diff --git a/dspace-api/src/main/java/org/dspace/content/packager/PDFPackager.java b/dspace-api/src/main/java/org/dspace/content/packager/PDFPackager.java index 6c7baad454..c9105f8afe 100644 --- a/dspace-api/src/main/java/org/dspace/content/packager/PDFPackager.java +++ b/dspace-api/src/main/java/org/dspace/content/packager/PDFPackager.java @@ -360,7 +360,7 @@ public class PDFPackager * CreationDate -> date.created * ModDate -> date.created * Creator -> description.provenance (application that created orig) - * Producer -> description.provenance (convertor to pdf) + * Producer -> description.provenance (converter to pdf) * Subject -> description.abstract * Keywords -> subject.other * date is java.util.Calendar diff --git a/dspace-api/src/main/java/org/dspace/content/packager/PackageDisseminator.java b/dspace-api/src/main/java/org/dspace/content/packager/PackageDisseminator.java index c5ebffc9f8..6c82c639a0 100644 --- a/dspace-api/src/main/java/org/dspace/content/packager/PackageDisseminator.java +++ b/dspace-api/src/main/java/org/dspace/content/packager/PackageDisseminator.java @@ -38,7 +38,7 @@ import org.dspace.core.Context; * format output by disseminate may be affected by * parameters, it is given to the getMIMEType method as well. * The parameters list is a generalized mechanism to pass parameters - * from the package requestor to the packager, since different packagers will + * from the package requester to the packager, since different packagers will * understand different sets of parameters. * * @author Larry Stone diff --git a/dspace-api/src/main/java/org/dspace/content/packager/PackageIngester.java b/dspace-api/src/main/java/org/dspace/content/packager/PackageIngester.java index 9b1d9f4f49..c28bd98e18 100644 --- a/dspace-api/src/main/java/org/dspace/content/packager/PackageIngester.java +++ b/dspace-api/src/main/java/org/dspace/content/packager/PackageIngester.java @@ -34,7 +34,7 @@ import org.dspace.workflow.WorkflowException; * The ingest methods are also given an attribute-value * list of "parameters" which may modify their actions. * The parameters list is a generalized mechanism to pass parameters - * from the requestor to the packager, since different packagers will + * from the requester to the packager, since different packagers will * understand different sets of parameters. * * @author Larry Stone diff --git a/dspace-api/src/main/java/org/dspace/content/packager/PackageParameters.java b/dspace-api/src/main/java/org/dspace/content/packager/PackageParameters.java index b472a52c3b..f2b9511867 100644 --- a/dspace-api/src/main/java/org/dspace/content/packager/PackageParameters.java +++ b/dspace-api/src/main/java/org/dspace/content/packager/PackageParameters.java @@ -9,7 +9,8 @@ package org.dspace.content.packager; import java.util.Enumeration; import java.util.Properties; -import javax.servlet.ServletRequest; + +import jakarta.servlet.ServletRequest; /** * Parameter list for SIP and DIP packagers. It's really just diff --git a/dspace-api/src/main/java/org/dspace/content/packager/RoleIngester.java b/dspace-api/src/main/java/org/dspace/content/packager/RoleIngester.java index 2ce3f50a3c..ca27abe206 100644 --- a/dspace-api/src/main/java/org/dspace/content/packager/RoleIngester.java +++ b/dspace-api/src/main/java/org/dspace/content/packager/RoleIngester.java @@ -19,6 +19,8 @@ import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.apache.commons.codec.DecoderException; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Collection; import org.dspace.content.Community; @@ -36,8 +38,6 @@ import org.dspace.eperson.PasswordHash; import org.dspace.eperson.factory.EPersonServiceFactory; import org.dspace.eperson.service.EPersonService; import org.dspace.eperson.service.GroupService; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; @@ -51,8 +51,7 @@ import org.xml.sax.SAXException; * @author mwood */ public class RoleIngester implements PackageIngester { - private static final Logger log = LoggerFactory - .getLogger(RoleIngester.class); + private static final Logger log = LogManager.getLogger(); protected CommunityService communityService = ContentServiceFactory.getInstance().getCommunityService(); protected CollectionService collectionService = ContentServiceFactory.getInstance().getCollectionService(); @@ -217,10 +216,10 @@ public class RoleIngester implements PackageIngester { // Community or Collection that doesn't currently exist in the // system. So, log a warning & skip it for now. log.warn( - "Skipping group named '" + name + "' as it seems to correspond to a Community or Collection that " + + "Skipping group named '{}' as it seems to correspond to a Community or Collection that " + "does not exist in the system. " + "If you are performing an AIP restore, you can ignore this warning as the " + - "Community/Collection AIP will likely create this group once it is processed."); + "Community/Collection AIP will likely create this group once it is processed.", name); continue; } log.debug("Translated group name: {}", name); @@ -307,7 +306,7 @@ public class RoleIngester implements PackageIngester { // Always set the name: parent.createBlop() is guessing groupService.setName(groupObj, name); - log.info("Created Group {}.", groupObj.getName()); + log.info("Created Group {}.", groupObj::getName); } // Add EPeople to newly created Group diff --git a/dspace-api/src/main/java/org/dspace/content/service/BitstreamService.java b/dspace-api/src/main/java/org/dspace/content/service/BitstreamService.java index 8effabf284..c22428f11a 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/BitstreamService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/BitstreamService.java @@ -13,8 +13,8 @@ import java.sql.SQLException; import java.util.Iterator; import java.util.List; import java.util.UUID; -import javax.annotation.Nullable; +import jakarta.annotation.Nullable; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Bitstream; import org.dspace.content.BitstreamFormat; diff --git a/dspace-api/src/main/java/org/dspace/content/service/CollectionService.java b/dspace-api/src/main/java/org/dspace/content/service/CollectionService.java index fd681f68f0..3a865d9d63 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/CollectionService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/CollectionService.java @@ -416,6 +416,34 @@ public interface CollectionService public List findCollectionsWithSubmit(String q, Context context, Community community, int offset, int limit) throws SQLException, SearchServiceException; + /** + * Retrieve the first collection in the community or its descending that support + * the provided entityType + * + * @param context the DSpace context + * @param community the root from where the search start + * @param entityType the requested entity type + * @return the first collection in the community or its descending + * that support the provided entityType + */ + public Collection retrieveCollectionWithSubmitByCommunityAndEntityType(Context context, Community community, + String entityType); + + /** + * Retrieve the close collection to the item for which the current user has + * 'submit' privileges that support the provided entityType. Close mean the + * collection that can be reach with the minimum steps starting from the item + * (owningCollection, brothers collections, etc) + * + * @param context the DSpace context + * @param item the item from where the search start + * @param entityType the requested entity type + * @return the first collection in the community or its descending + * that support the provided entityType + */ + public Collection retrieveCollectionWithSubmitByEntityType(Context context, Item item, String entityType) + throws SQLException; + /** * Counts the number of Collection for which the current user has 'submit' privileges. * NOTE: for better performance, this method retrieves its results from an index (cache) diff --git a/dspace-api/src/main/java/org/dspace/content/service/CommunityService.java b/dspace-api/src/main/java/org/dspace/content/service/CommunityService.java index 163baedf5f..5734d5a9d9 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/CommunityService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/CommunityService.java @@ -181,7 +181,7 @@ public interface CommunityService extends DSpaceObjectService, DSpace /** - * Add an exisiting collection to the community + * Add an existing collection to the community * * @param context context * @param community community diff --git a/dspace-api/src/main/java/org/dspace/content/service/DuplicateDetectionService.java b/dspace-api/src/main/java/org/dspace/content/service/DuplicateDetectionService.java new file mode 100644 index 0000000000..1f0d3495b1 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/service/DuplicateDetectionService.java @@ -0,0 +1,85 @@ +/** + * 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.content.service; + +import java.sql.SQLException; +import java.util.List; +import java.util.Optional; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.DuplicateDetectionServiceImpl; +import org.dspace.content.Item; +import org.dspace.content.virtual.PotentialDuplicate; +import org.dspace.core.Context; +import org.dspace.discovery.DiscoverResult; +import org.dspace.discovery.IndexableObject; +import org.dspace.discovery.SearchServiceException; + +/** + * Duplicate Detection Service handles get, search and validation operations for duplicate detection. + * @see DuplicateDetectionServiceImpl for implementation details + * + * @author Kim Shepherd + */ +public interface DuplicateDetectionService { + + /** + * Logger + */ + Logger log = LogManager.getLogger(DuplicateDetectionService.class); + + /** + * Get a list of PotentialDuplicate objects (wrappers with some metadata included for previewing) that + * are identified as potential duplicates of the given item + * + * @param context DSpace context + * @param item Item to check + * @return List of potential duplicates (empty if none found) + * @throws SearchServiceException if an error occurs performing the discovery search + */ + List getPotentialDuplicates(Context context, Item item) + throws SearchServiceException; + + /** + * Validate an indexable object (returned by discovery search) to ensure it is permissible, readable and valid + * and can be added to a list of results. + * An Optional is returned, if it is empty then it was invalid or did not pass validation. + * + * @param context The DSpace context + * @param indexableObject The discovery search result + * @param original The original item (to compare IDs, submitters, etc) + * @return An Optional potential duplicate + * @throws SQLException + * @throws AuthorizeException + */ + Optional validateDuplicateResult(Context context, IndexableObject indexableObject, + Item original) throws SQLException, AuthorizeException; + + /** + * Search discovery for potential duplicates of a given item. The search uses levenshtein distance (configurable) + * and a single-term "comparison value" constructed out of the item title + * + * @param context DSpace context + * @param item The item to check + * @return DiscoverResult as a result of performing search. Null if invalid. + * + * @throws SearchServiceException if an error was encountered during the discovery search itself. + */ + DiscoverResult searchDuplicates(Context context, Item item) throws SearchServiceException; + + /** + * Build a comparison value string made up of values of configured fields, used when indexing and querying + * items for deduplication + * @param context DSpace context + * @param item The DSpace item + * @return a constructed, normalised string + */ + String buildComparisonValue(Context context, Item item); +} diff --git a/dspace-api/src/main/java/org/dspace/content/service/FeedbackService.java b/dspace-api/src/main/java/org/dspace/content/service/FeedbackService.java index d21afd6780..68bb0b1558 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/FeedbackService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/FeedbackService.java @@ -7,9 +7,9 @@ */ package org.dspace.content.service; import java.io.IOException; -import javax.mail.MessagingException; -import javax.servlet.http.HttpServletRequest; +import jakarta.mail.MessagingException; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.core.Context; /** @@ -20,7 +20,7 @@ import org.dspace.core.Context; public interface FeedbackService { /** - * This method sends the feeback email to the recipient passed as parameter + * This method sends the feedback email to the recipient passed as parameter * @param context current DSpace application context * @param request current servlet request * @param recipientEmail recipient to which mail is sent diff --git a/dspace-api/src/main/java/org/dspace/content/service/ItemService.java b/dspace-api/src/main/java/org/dspace/content/service/ItemService.java index 12867ad18c..47d2d5bdaa 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/ItemService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/ItemService.java @@ -23,10 +23,10 @@ import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.EntityType; import org.dspace.content.Item; -import org.dspace.content.MetadataField; import org.dspace.content.MetadataValue; import org.dspace.content.Thumbnail; import org.dspace.content.WorkspaceItem; +import org.dspace.contentreport.QueryPredicate; import org.dspace.core.Context; import org.dspace.discovery.SearchServiceException; import org.dspace.eperson.EPerson; @@ -42,7 +42,7 @@ import org.dspace.eperson.Group; public interface ItemService extends DSpaceObjectService, DSpaceObjectLegacySupportService { - public Thumbnail getThumbnail(Context context, Item item, boolean requireOriginal) throws SQLException; + Thumbnail getThumbnail(Context context, Item item, boolean requireOriginal) throws SQLException; /** * Create a new item, with a new internal ID. Authorization is done @@ -54,7 +54,7 @@ public interface ItemService * @throws SQLException if database error * @throws AuthorizeException if authorization error */ - public Item create(Context context, WorkspaceItem workspaceItem) throws SQLException, AuthorizeException; + Item create(Context context, WorkspaceItem workspaceItem) throws SQLException, AuthorizeException; /** * Create a new item, with a provided ID. Authorisation is done @@ -67,7 +67,7 @@ public interface ItemService * @throws SQLException if database error * @throws AuthorizeException if authorization error */ - public Item create(Context context, WorkspaceItem workspaceItem, UUID uuid) throws SQLException, AuthorizeException; + Item create(Context context, WorkspaceItem workspaceItem, UUID uuid) throws SQLException, AuthorizeException; /** * Create an empty template item for this collection. If one already exists, @@ -81,7 +81,7 @@ public interface ItemService * @throws SQLException if database error * @throws AuthorizeException if authorization error */ - public Item createTemplateItem(Context context, Collection collection) throws SQLException, AuthorizeException; + Item createTemplateItem(Context context, Collection collection) throws SQLException, AuthorizeException; /** * Populate the given item with all template item specified metadata. @@ -103,7 +103,7 @@ public interface ItemService * @return an iterator over the items in the archive. * @throws SQLException if database error */ - public Iterator findAll(Context context) throws SQLException; + Iterator findAll(Context context) throws SQLException; /** * Get all the items in the archive. Only items with the "in archive" flag @@ -115,7 +115,7 @@ public interface ItemService * @return an iterator over the items in the archive. * @throws SQLException if database error */ - public Iterator findAll(Context context, Integer limit, Integer offset) throws SQLException; + Iterator findAll(Context context, Integer limit, Integer offset) throws SQLException; /** * Get all "final" items in the archive, both archived ("in archive" flag) or @@ -125,8 +125,7 @@ public interface ItemService * @return an iterator over the items in the archive. * @throws SQLException if database error */ - @Deprecated - public Iterator findAllUnfiltered(Context context) throws SQLException; + @Deprecated Iterator findAllUnfiltered(Context context) throws SQLException; /** * Find all items that are: @@ -139,7 +138,7 @@ public interface ItemService * @return iterator over all regular items. * @throws SQLException if database error. */ - public Iterator findAllRegularItems(Context context) throws SQLException; + Iterator findAllRegularItems(Context context) throws SQLException; /** * Find all the items in the archive by a given submitter. The order is @@ -150,7 +149,7 @@ public interface ItemService * @return an iterator over the items submitted by eperson * @throws SQLException if database error */ - public Iterator findBySubmitter(Context context, EPerson eperson) + Iterator findBySubmitter(Context context, EPerson eperson) throws SQLException; /** @@ -165,7 +164,7 @@ public interface ItemService * @return an iterator over the items submitted by eperson * @throws SQLException if database error */ - public Iterator findBySubmitter(Context context, EPerson eperson, boolean retrieveAllItems) + Iterator findBySubmitter(Context context, EPerson eperson, boolean retrieveAllItems) throws SQLException; /** @@ -177,7 +176,7 @@ public interface ItemService * @return an iterator over the items submitted by eperson * @throws SQLException if database error */ - public Iterator findBySubmitterDateSorted(Context context, EPerson eperson, Integer limit) + Iterator findBySubmitterDateSorted(Context context, EPerson eperson, Integer limit) throws SQLException; /** @@ -188,7 +187,7 @@ public interface ItemService * @return an iterator over the items in the collection. * @throws SQLException if database error */ - public Iterator findByCollection(Context context, Collection collection) throws SQLException; + Iterator findByCollection(Context context, Collection collection) throws SQLException; /** * Get all the archived items in this collection. The order is indeterminate. @@ -200,7 +199,7 @@ public interface ItemService * @return an iterator over the items in the collection. * @throws SQLException if database error */ - public Iterator findByCollection(Context context, Collection collection, Integer limit, Integer offset) + Iterator findByCollection(Context context, Collection collection, Integer limit, Integer offset) throws SQLException; /** @@ -213,7 +212,7 @@ public interface ItemService * @return an iterator over the items in the collection. * @throws SQLException if database error */ - public Iterator findByCollectionMapping(Context context, Collection collection, Integer limit, Integer offset) + Iterator findByCollectionMapping(Context context, Collection collection, Integer limit, Integer offset) throws SQLException; /** @@ -224,7 +223,7 @@ public interface ItemService * @return an iterator over the items in the collection. * @throws SQLException if database error */ - public int countByCollectionMapping(Context context, Collection collection) throws SQLException; + int countByCollectionMapping(Context context, Collection collection) throws SQLException; /** * Get all the items (including private and withdrawn) in this collection. The order is indeterminate. @@ -236,7 +235,7 @@ public interface ItemService * @param offset offset value * @throws SQLException if database error */ - public Iterator findAllByCollection(Context context, Collection collection, Integer limit, Integer offset) + Iterator findAllByCollection(Context context, Collection collection, Integer limit, Integer offset) throws SQLException; /** @@ -247,7 +246,7 @@ public interface ItemService * @return an iterator over the items in the collection. * @throws SQLException if database error */ - public Iterator findInArchiveOrWithdrawnDiscoverableModifiedSince(Context context, Date since) + Iterator findInArchiveOrWithdrawnDiscoverableModifiedSince(Context context, Date since) throws SQLException; /** @@ -257,7 +256,7 @@ public interface ItemService * @return an iterator over the items in the collection. * @throws SQLException if database error */ - public Iterator findInArchiveOrWithdrawnNonDiscoverableModifiedSince(Context context, Date since) + Iterator findInArchiveOrWithdrawnNonDiscoverableModifiedSince(Context context, Date since) throws SQLException; /** @@ -268,7 +267,7 @@ public interface ItemService * @return an iterator over the items in the collection. * @throws SQLException if database error */ - public Iterator findAllByCollection(Context context, Collection collection) throws SQLException; + Iterator findAllByCollection(Context context, Collection collection) throws SQLException; /** * See whether this Item is contained by a given Collection. @@ -278,7 +277,7 @@ public interface ItemService * @return true if {@code collection} contains this Item. * @throws SQLException if database error */ - public boolean isIn(Item item, Collection collection) throws SQLException; + boolean isIn(Item item, Collection collection) throws SQLException; /** * Get the communities this item is in. Returns an unordered array of the @@ -290,7 +289,7 @@ public interface ItemService * @return the communities this item is in. * @throws SQLException if database error */ - public List getCommunities(Context context, Item item) throws SQLException; + List getCommunities(Context context, Item item) throws SQLException; /** @@ -301,7 +300,7 @@ public interface ItemService * @return the bundles in an unordered array * @throws SQLException if database error */ - public List getBundles(Item item, String name) throws SQLException; + List getBundles(Item item, String name) throws SQLException; /** * Add an existing bundle to this item. This has immediate effect. @@ -312,7 +311,7 @@ public interface ItemService * @throws SQLException if database error * @throws AuthorizeException if authorization error */ - public void addBundle(Context context, Item item, Bundle bundle) throws SQLException, AuthorizeException; + void addBundle(Context context, Item item, Bundle bundle) throws SQLException, AuthorizeException; /** * Remove a bundle. This may result in the bundle being deleted, if the @@ -325,7 +324,7 @@ public interface ItemService * @throws AuthorizeException if authorization error * @throws IOException if IO error */ - public void removeBundle(Context context, Item item, Bundle bundle) throws SQLException, AuthorizeException, + void removeBundle(Context context, Item item, Bundle bundle) throws SQLException, AuthorizeException, IOException; /** @@ -338,7 +337,7 @@ public interface ItemService * @throws AuthorizeException if authorization error * @throws IOException if IO error */ - public void removeAllBundles(Context context, Item item) throws AuthorizeException, SQLException, IOException; + void removeAllBundles(Context context, Item item) throws AuthorizeException, SQLException, IOException; /** * Create a single bitstream in a new bundle. Provided as a convenience @@ -353,7 +352,7 @@ public interface ItemService * @throws IOException if IO error * @throws SQLException if database error */ - public Bitstream createSingleBitstream(Context context, InputStream is, Item item, String name) + Bitstream createSingleBitstream(Context context, InputStream is, Item item, String name) throws AuthorizeException, IOException, SQLException; /** @@ -367,7 +366,7 @@ public interface ItemService * @throws IOException if IO error * @throws SQLException if database error */ - public Bitstream createSingleBitstream(Context context, InputStream is, Item item) + Bitstream createSingleBitstream(Context context, InputStream is, Item item) throws AuthorizeException, IOException, SQLException; /** @@ -380,7 +379,7 @@ public interface ItemService * @return non-internal bitstreams. * @throws SQLException if database error */ - public List getNonInternalBitstreams(Context context, Item item) throws SQLException; + List getNonInternalBitstreams(Context context, Item item) throws SQLException; /** * Remove just the DSpace license from an item This is useful to update the @@ -395,7 +394,7 @@ public interface ItemService * @throws AuthorizeException if authorization error * @throws IOException if IO error */ - public void removeDSpaceLicense(Context context, Item item) throws SQLException, AuthorizeException, + void removeDSpaceLicense(Context context, Item item) throws SQLException, AuthorizeException, IOException; /** @@ -407,7 +406,7 @@ public interface ItemService * @throws AuthorizeException if authorization error * @throws IOException if IO error */ - public void removeLicenses(Context context, Item item) throws SQLException, AuthorizeException, IOException; + void removeLicenses(Context context, Item item) throws SQLException, AuthorizeException, IOException; /** * Withdraw the item from the archive. It is kept in place, and the content @@ -418,7 +417,7 @@ public interface ItemService * @throws SQLException if database error * @throws AuthorizeException if authorization error */ - public void withdraw(Context context, Item item) throws SQLException, AuthorizeException; + void withdraw(Context context, Item item) throws SQLException, AuthorizeException; /** @@ -429,7 +428,7 @@ public interface ItemService * @throws SQLException if database error * @throws AuthorizeException if authorization error */ - public void reinstate(Context context, Item item) throws SQLException, AuthorizeException; + void reinstate(Context context, Item item) throws SQLException, AuthorizeException; /** * Return true if this Collection 'owns' this item @@ -438,7 +437,7 @@ public interface ItemService * @param collection Collection * @return true if this Collection owns this item */ - public boolean isOwningCollection(Item item, Collection collection); + boolean isOwningCollection(Item item, Collection collection); /** * remove all of the policies for item and replace them with a new list of @@ -452,7 +451,7 @@ public interface ItemService * @throws SQLException if database error * @throws AuthorizeException if authorization error */ - public void replaceAllItemPolicies(Context context, Item item, List newpolicies) + void replaceAllItemPolicies(Context context, Item item, List newpolicies) throws SQLException, AuthorizeException; @@ -468,7 +467,7 @@ public interface ItemService * @throws SQLException if database error * @throws AuthorizeException if authorization error */ - public void replaceAllBitstreamPolicies(Context context, Item item, List newpolicies) + void replaceAllBitstreamPolicies(Context context, Item item, List newpolicies) throws SQLException, AuthorizeException; @@ -482,7 +481,7 @@ public interface ItemService * @throws SQLException if database error * @throws AuthorizeException if authorization error */ - public void removeGroupPolicies(Context context, Item item, Group group) throws SQLException, AuthorizeException; + void removeGroupPolicies(Context context, Item item, Group group) throws SQLException, AuthorizeException; /** * Remove all policies on an item and its contents, and replace them with @@ -497,7 +496,7 @@ public interface ItemService * draconian, but default policies must be enforced. * @throws AuthorizeException if authorization error */ - public void inheritCollectionDefaultPolicies(Context context, Item item, Collection collection) + void inheritCollectionDefaultPolicies(Context context, Item item, Collection collection) throws java.sql.SQLException, AuthorizeException; /** @@ -516,7 +515,7 @@ public interface ItemService * draconian, but default policies must be enforced. * @throws AuthorizeException if authorization error */ - public void inheritCollectionDefaultPolicies(Context context, Item item, Collection collection, + void inheritCollectionDefaultPolicies(Context context, Item item, Collection collection, boolean overrideItemReadPolicies) throws java.sql.SQLException, AuthorizeException; @@ -529,14 +528,14 @@ public interface ItemService * already applied to the bundle/bitstream. Collection's policies are inherited * if there are no other policies defined or if the append mode is defined by * the configuration via the core.authorization.installitem.inheritance-read.append-mode property - * + * * @param context DSpace context object * @param item Item to adjust policies on * @param collection Collection * @throws SQLException If database error * @throws AuthorizeException If authorization error */ - public void adjustBundleBitstreamPolicies(Context context, Item item, Collection collection) + void adjustBundleBitstreamPolicies(Context context, Item item, Collection collection) throws SQLException, AuthorizeException; /** @@ -557,7 +556,7 @@ public interface ItemService * @throws SQLException If database error * @throws AuthorizeException If authorization error */ - public void adjustBundleBitstreamPolicies(Context context, Item item, Collection collection, + void adjustBundleBitstreamPolicies(Context context, Item item, Collection collection, boolean replaceReadRPWithCollectionRP) throws SQLException, AuthorizeException; @@ -578,7 +577,7 @@ public interface ItemService * @throws SQLException If database error * @throws AuthorizeException If authorization error */ - public void adjustBitstreamPolicies(Context context, Item item, Collection collection, Bitstream bitstream) + void adjustBitstreamPolicies(Context context, Item item, Collection collection, Bitstream bitstream) throws SQLException, AuthorizeException; /** @@ -600,7 +599,7 @@ public interface ItemService * @throws SQLException If database error * @throws AuthorizeException If authorization error */ - public void adjustBitstreamPolicies(Context context, Item item, Collection collection, Bitstream bitstream, + void adjustBitstreamPolicies(Context context, Item item, Collection collection, Bitstream bitstream, boolean replaceReadRPWithCollectionRP) throws SQLException, AuthorizeException; @@ -612,14 +611,14 @@ public interface ItemService * inherited as appropriate. Collection's policies are inherited if there are no * other policies defined or if the append mode is defined by the configuration * via the core.authorization.installitem.inheritance-read.append-mode property - * + * * @param context DSpace context object * @param item Item to adjust policies on * @param collection Collection * @throws SQLException If database error * @throws AuthorizeException If authorization error */ - public void adjustItemPolicies(Context context, Item item, Collection collection) + void adjustItemPolicies(Context context, Item item, Collection collection) throws SQLException, AuthorizeException; /** @@ -638,7 +637,7 @@ public interface ItemService * @throws SQLException If database error * @throws AuthorizeException If authorization error */ - public void adjustItemPolicies(Context context, Item item, Collection collection, + void adjustItemPolicies(Context context, Item item, Collection collection, boolean replaceReadRPWithCollectionRP) throws SQLException, AuthorizeException; @@ -653,7 +652,7 @@ public interface ItemService * @throws AuthorizeException if authorization error * @throws IOException if IO error */ - public void move(Context context, Item item, Collection from, Collection to) + void move(Context context, Item item, Collection from, Collection to) throws SQLException, AuthorizeException, IOException; /** @@ -668,7 +667,7 @@ public interface ItemService * @throws AuthorizeException if authorization error * @throws IOException if IO error */ - public void move(Context context, Item item, Collection from, Collection to, boolean inheritDefaultPolicies) + void move(Context context, Item item, Collection from, Collection to, boolean inheritDefaultPolicies) throws SQLException, AuthorizeException, IOException; /** @@ -679,7 +678,7 @@ public interface ItemService * bitstreams inside * @throws SQLException if database error */ - public boolean hasUploadedFiles(Item item) throws SQLException; + boolean hasUploadedFiles(Item item) throws SQLException; /** * Get the collections this item is not in. @@ -689,7 +688,7 @@ public interface ItemService * @return the collections this item is not in, if any. * @throws SQLException if database error */ - public List getCollectionsNotLinked(Context context, Item item) throws SQLException; + List getCollectionsNotLinked(Context context, Item item) throws SQLException; /** * return TRUE if context's user can edit item, false otherwise @@ -699,7 +698,7 @@ public interface ItemService * @return boolean true = current user can edit item * @throws SQLException if database error */ - public boolean canEdit(Context context, Item item) throws java.sql.SQLException; + boolean canEdit(Context context, Item item) throws java.sql.SQLException; /** * return TRUE if context's user can create new version of the item, false @@ -710,7 +709,7 @@ public interface ItemService * @return boolean true = current user can create new version of the item * @throws SQLException if database error */ - public boolean canCreateNewVersion(Context context, Item item) throws SQLException; + boolean canCreateNewVersion(Context context, Item item) throws SQLException; /** * Returns an iterator of in archive items possessing the passed metadata field, or only @@ -725,7 +724,7 @@ public interface ItemService * @throws SQLException if database error * @throws AuthorizeException if authorization error */ - public Iterator findArchivedByMetadataField(Context context, String schema, + Iterator findArchivedByMetadataField(Context context, String schema, String element, String qualifier, String value) throws SQLException, AuthorizeException; @@ -740,7 +739,7 @@ public interface ItemService * @throws SQLException if database error * @throws AuthorizeException if authorization error */ - public Iterator findArchivedByMetadataField(Context context, String metadataField, String value) + Iterator findArchivedByMetadataField(Context context, String metadataField, String value) throws SQLException, AuthorizeException; /** @@ -757,14 +756,41 @@ public interface ItemService * @throws AuthorizeException if authorization error * @throws IOException if IO error */ - public Iterator findByMetadataField(Context context, + Iterator findByMetadataField(Context context, String schema, String element, String qualifier, String value) throws SQLException, AuthorizeException, IOException; - public Iterator findByMetadataQuery(Context context, List> listFieldList, - List query_op, List query_val, List collectionUuids, - String regexClause, int offset, int limit) - throws SQLException, AuthorizeException, IOException; + /** + * Returns a list of items that match the given predicates, within the + * specified collections, if any. This querying method is used by the + * Filtered Items report functionality. + * @param context DSpace context object + * @param queryPredicates metadata field predicates + * @param collectionUuids UUIDs of the collections to search + * @param offset position in the list to start returning items + * @param limit maximum number of items to return + * @return a list of matching items in the specified collections, + * or in any collection if no collection UUIDs are provided + * @throws SQLException if a database error occurs + */ + List findByMetadataQuery(Context context, List queryPredicates, + List collectionUuids, long offset, int limit) + throws SQLException; + + /** + * Returns the total number of items that match the given predicates, within the + * specified collections, if any. This querying method is used for pagination by the + * Filtered Items report functionality. + * @param context DSpace context object + * @param queryPredicates metadata field predicates + * @param collectionUuids UUIDs of the collections to search + * @return the total number of matching items in the specified collections, + * or in any collection if no collection UUIDs are provided + * @throws SQLException if a database error occurs + */ + long countForMetadataQuery(Context context, List queryPredicates, + List collectionUuids) + throws SQLException; /** * Find all the items in the archive with a given authority key value @@ -780,12 +806,12 @@ public interface ItemService * @throws AuthorizeException if authorization error * @throws IOException if IO error */ - public Iterator findByAuthorityValue(Context context, + Iterator findByAuthorityValue(Context context, String schema, String element, String qualifier, String value) throws SQLException, AuthorizeException; - public Iterator findByMetadataFieldAuthority(Context context, String mdString, String authority) + Iterator findByMetadataFieldAuthority(Context context, String mdString, String authority) throws SQLException, AuthorizeException; /** @@ -797,7 +823,7 @@ public interface ItemService * @param item item * @return true or false */ - public boolean isItemListedForUser(Context context, Item item); + boolean isItemListedForUser(Context context, Item item); /** * counts items in the given collection @@ -807,7 +833,7 @@ public interface ItemService * @return total items * @throws SQLException if database error */ - public int countItems(Context context, Collection collection) throws SQLException; + int countItems(Context context, Collection collection) throws SQLException; /** * counts all items in the given collection including withdrawn items @@ -817,7 +843,7 @@ public interface ItemService * @return total items * @throws SQLException if database error */ - public int countAllItems(Context context, Collection collection) throws SQLException; + int countAllItems(Context context, Collection collection) throws SQLException; /** * Find all Items modified since a Date. @@ -827,7 +853,7 @@ public interface ItemService * @return iterator over items * @throws SQLException if database error */ - public Iterator findByLastModifiedSince(Context context, Date last) + Iterator findByLastModifiedSince(Context context, Date last) throws SQLException; /** @@ -838,7 +864,7 @@ public interface ItemService * @return total items * @throws SQLException if database error */ - public int countItems(Context context, Community community) throws SQLException; + int countItems(Context context, Community community) throws SQLException; /** * counts all items in the given community including withdrawn @@ -848,7 +874,7 @@ public interface ItemService * @return total items * @throws SQLException if database error */ - public int countAllItems(Context context, Community community) throws SQLException; + int countAllItems(Context context, Community community) throws SQLException; /** * counts all items @@ -895,7 +921,7 @@ public interface ItemService * @throws SQLException * @throws SearchServiceException */ - public List findItemsWithEdit(Context context, int offset, int limit) + List findItemsWithEdit(Context context, int offset, int limit) throws SQLException, SearchServiceException; /** @@ -905,7 +931,7 @@ public interface ItemService * @throws SQLException * @throws SearchServiceException */ - public int countItemsWithEdit(Context context) throws SQLException, SearchServiceException; + int countItemsWithEdit(Context context) throws SQLException, SearchServiceException; /** * Check if the supplied item is an inprogress submission @@ -965,7 +991,7 @@ public interface ItemService * relationships. * @return metadata fields that match the parameters */ - public List getMetadata(Item item, String schema, String element, String qualifier, + List getMetadata(Item item, String schema, String element, String qualifier, String lang, boolean enableVirtualMetadata); /** @@ -973,7 +999,7 @@ public interface ItemService * @param item the item. * @return the label of the entity type, taken from the item metadata, or null if not found. */ - public String getEntityTypeLabel(Item item); + String getEntityTypeLabel(Item item); /** * Retrieve the entity type of the given item. @@ -981,6 +1007,6 @@ public interface ItemService * @param item the item. * @return the entity type of the given item, or null if not found. */ - public EntityType getEntityType(Context context, Item item) throws SQLException; + EntityType getEntityType(Context context, Item item) throws SQLException; } diff --git a/dspace-api/src/main/java/org/dspace/content/service/RelationshipService.java b/dspace-api/src/main/java/org/dspace/content/service/RelationshipService.java index 719f966e46..f32e965c62 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/RelationshipService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/RelationshipService.java @@ -282,7 +282,7 @@ public interface RelationshipService extends DSpaceCRUDService { List findByRelationshipType(Context context, RelationshipType relationshipType) throws SQLException; /** - * This method returns a list of Relationship objets for which the relationshipType property is equal to the given + * This method returns a list of Relationship objects for which the relationshipType property is equal to the given * RelationshipType object * NOTE: tilted relationships are NEVER excluded when fetching one relationship type * @param context The relevant DSpace context diff --git a/dspace-api/src/main/java/org/dspace/content/virtual/Concatenate.java b/dspace-api/src/main/java/org/dspace/content/virtual/Concatenate.java index b788cbf9fc..0bc7e3cd2a 100644 --- a/dspace-api/src/main/java/org/dspace/content/virtual/Concatenate.java +++ b/dspace-api/src/main/java/org/dspace/content/virtual/Concatenate.java @@ -62,16 +62,16 @@ public class Concatenate implements VirtualMetadataConfiguration { } /** - * Generic getter for the seperator - * @return the seperator to be used by this bean + * Generic getter for the separator + * @return the separator to be used by this bean */ public String getSeparator() { return separator; } /** - * Generic setter for the seperator property - * @param separator The String seperator value to which this seperator value will be set to + * Generic setter for the separator property + * @param separator The String separator value to which this separator value will be set to */ public void setSeparator(String separator) { this.separator = separator; diff --git a/dspace-api/src/main/java/org/dspace/content/virtual/PotentialDuplicate.java b/dspace-api/src/main/java/org/dspace/content/virtual/PotentialDuplicate.java new file mode 100644 index 0000000000..6c193bb285 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/virtual/PotentialDuplicate.java @@ -0,0 +1,176 @@ +/** + * 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.content.virtual; + +import java.util.LinkedList; +import java.util.List; +import java.util.UUID; + +import org.dspace.content.Item; +import org.dspace.content.MetadataValue; + +/** + * Model of potential duplicate item. Provides as little data as possible, but enough to be useful + * about the context / state of the duplicate, and metadata for preview purposes. + * This class lives in the virtual package because it is not stored, addressable data, it's a stub / preview + * based on an items' search result and metadata. + * + * @author Kim Shepherd + */ +public class PotentialDuplicate { + /** + * Title of duplicate object + */ + private String title; + /** + * UUID of duplicate object + */ + private UUID uuid; + /** + * Owning collection name (title) for duplicate item + */ + private String owningCollectionName; + /** + * Workspace item ID, if the duplicate is a workspace item + */ + private Integer workspaceItemId; + /** + * Workflow item ID, if the duplicate is a workflow item + */ + private Integer workflowItemId; + + /** + * List of configured metadata values copied across from the duplicate item + */ + private List metadataValueList; + + /** + * Default constructor + */ + public PotentialDuplicate() { + this.metadataValueList = new LinkedList<>(); + } + + /** + * Constructor that accepts an item and sets some values accordingly + * @param item the potential duplicate item + */ + public PotentialDuplicate(Item item) { + // Throw error if item is null + if (item == null) { + throw new NullPointerException("Null item passed to potential duplicate constructor"); + } + // Instantiate metadata value list + this.metadataValueList = new LinkedList<>(); + // Set title + this.title = item.getName(); + // Set UUID + this.uuid = item.getID(); + // Set owning collection name + if (item.getOwningCollection() != null) { + this.owningCollectionName = item.getOwningCollection().getName(); + } + } + + /** + * Get UUID of duplicate item + * @return UUID of duplicate item + */ + public UUID getUuid() { + return uuid; + } + + /** + * Set UUID of duplicate item + * @param uuid UUID of duplicate item + */ + public void setUuid(UUID uuid) { + this.uuid = uuid; + } + + /** + * Get title of duplicate item + * @return title of duplicate item + */ + public String getTitle() { + return title; + } + + /** + * Set title of duplicate item + * @param title of duplicate item + */ + public void setTitle(String title) { + this.title = title; + } + + /** + * Get owning collection name (title) of duplicate item + * @return owning collection name (title) of duplicate item + */ + public String getOwningCollectionName() { + return owningCollectionName; + } + + /** + * Set owning collection name (title) of duplicate item + * @param owningCollectionName owning collection name (title) of duplicate item + */ + public void setOwningCollectionName(String owningCollectionName) { + this.owningCollectionName = owningCollectionName; + } + + /** + * Get workspace ID for duplicate item, if any + * @return workspace item ID or null + */ + public Integer getWorkspaceItemId() { + return workspaceItemId; + } + + /** + * Set workspace ID for duplicate item + * @param workspaceItemId workspace item ID + */ + public void setWorkspaceItemId(Integer workspaceItemId) { + this.workspaceItemId = workspaceItemId; + } + + /** + * Get workflow ID for duplicate item, if anh + * @return workflow item ID or null + */ + public Integer getWorkflowItemId() { + return workflowItemId; + } + + /** + * Set workflow ID for duplicate item + * @param workflowItemId workspace item ID + */ + public void setWorkflowItemId(Integer workflowItemId) { + this.workflowItemId = workflowItemId; + } + + /** + * Get metadata (sorted, field->value list) for duplicate item + * @return (sorted, field->value list) for duplicate item + */ + public List getMetadataValueList() { + return metadataValueList; + } + + /** + * Set metadata (sorted, field->value list) for duplicate item + * @param metadataValueList MetadataRest list of values mapped to field keys + */ + public void setMetadataValueList(List metadataValueList) { + this.metadataValueList = metadataValueList; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/contentreport/ContentReportServiceImpl.java b/dspace-api/src/main/java/org/dspace/contentreport/ContentReportServiceImpl.java new file mode 100644 index 0000000000..e22ac0e96f --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/contentreport/ContentReportServiceImpl.java @@ -0,0 +1,190 @@ +/** + * 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.contentreport; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.UUID; + +import org.apache.logging.log4j.Logger; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.Item; +import org.dspace.content.MetadataField; +import org.dspace.content.service.CollectionService; +import org.dspace.content.service.ItemService; +import org.dspace.content.service.MetadataFieldService; +import org.dspace.contentreport.service.ContentReportService; +import org.dspace.core.Context; +import org.dspace.services.ConfigurationService; +import org.springframework.beans.factory.annotation.Autowired; + +public class ContentReportServiceImpl implements ContentReportService { + + private static final Logger log = org.apache.logging.log4j.LogManager + .getLogger(ContentReportServiceImpl.class); + + @Autowired + protected ConfigurationService configurationService; + @Autowired + private CollectionService collectionService; + @Autowired + private ItemService itemService; + @Autowired + private MetadataFieldService metadataFieldService; + + /** + * Returns true< if Content Reports are enabled. + * @return true< if Content Reports are enabled + */ + @Override + public boolean getEnabled() { + return configurationService.getBooleanProperty("contentreport.enable"); + } + + /** + * Retrieves item statistics per collection according to a set of Boolean filters. + * @param context DSpace context + * @param filters Set of filters + * @return a list of collections with the requested statistics for each of them + */ + @Override + public List findFilteredCollections(Context context, java.util.Collection filters) { + List colls = new ArrayList<>(); + try { + List collections = collectionService.findAll(context); + for (Collection collection : collections) { + FilteredCollection coll = new FilteredCollection(); + coll.setHandle(collection.getHandle()); + coll.setLabel(collection.getName()); + Community community = collection.getCommunities().stream() + .findFirst() + .orElse(null); + if (community != null) { + coll.setCommunityLabel(community.getName()); + coll.setCommunityHandle(community.getHandle()); + } + colls.add(coll); + + Iterator items = itemService.findAllByCollection(context, collection); + int nbTotalItems = 0; + while (items.hasNext()) { + Item item = items.next(); + nbTotalItems++; + boolean matchesAllFilters = true; + for (Filter filter : filters) { + if (filter.testItem(context, item)) { + coll.addValue(filter, 1); + } else { + // This ensures the requested filter is present in the collection record + // even when there are no matching items. + coll.addValue(filter, 0); + matchesAllFilters = false; + } + } + if (matchesAllFilters) { + coll.addAllFiltersValue(1); + } + } + coll.setTotalItems(nbTotalItems); + coll.seal(); + } + } catch (SQLException e) { + log.error("SQLException trying to receive filtered collections statistics", e); + } + return colls; + } + + /** + * Retrieves a list of items according to a set of criteria. + * @param context DSpace context + * @param query structured query to find items against + * @return a list of items filtered according to the provided query + */ + @Override + public FilteredItems findFilteredItems(Context context, FilteredItemsQuery query) { + FilteredItems report = new FilteredItems(); + + List predicates = query.getQueryPredicates(); + List collectionUuids = getUuidsFromStrings(query.getCollections()); + Set filters = query.getFilters(); + + try { + List items = itemService.findByMetadataQuery(context, predicates, collectionUuids, + query.getOffset(), query.getPageLimit()); + items.stream() + .filter(item -> filters.stream().allMatch(f -> f.testItem(context, item))) + .forEach(report::addItem); + } catch (SQLException e) { + log.error(e.getMessage(), e); + } + try { + long count = itemService.countForMetadataQuery(context, predicates, collectionUuids); + report.setItemCount(count); + } catch (SQLException e) { + log.error(e.getMessage(), e); + } + + return report; + } + + /** + * Converts a metadata field name to a list of {@link MetadataField} instances + * (one if no wildcards are used, possibly more otherwise). + * @param context DSpace context + * @param metadataField field to search for + * @return a corresponding list of {@link MetadataField} entries + */ + @Override + public List getMetadataFields(org.dspace.core.Context context, String metadataField) + throws SQLException { + List fields = new ArrayList<>(); + if ("*".equals(metadataField)) { + return fields; + } + String schema = ""; + String element = ""; + String qualifier = null; + String[] parts = metadataField.split("\\."); + if (parts.length > 0) { + schema = parts[0]; + } + if (parts.length > 1) { + element = parts[1]; + } + if (parts.length > 2) { + qualifier = parts[2]; + } + + if (Item.ANY.equals(qualifier)) { + fields.addAll(metadataFieldService.findFieldsByElementNameUnqualified(context, schema, element)); + } else { + MetadataField mf = metadataFieldService.findByElement(context, schema, element, qualifier); + if (mf != null) { + fields.add(mf); + } + } + return fields; + } + + private static List getUuidsFromStrings(List collSel) { + List uuids = new ArrayList<>(); + for (String s: collSel) { + try { + uuids.add(UUID.fromString(s)); + } catch (IllegalArgumentException e) { + log.warn("Invalid collection UUID: " + s); + } + } + return uuids; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/contentreport/Filter.java b/dspace-api/src/main/java/org/dspace/contentreport/Filter.java new file mode 100644 index 0000000000..e5fa588140 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/contentreport/Filter.java @@ -0,0 +1,399 @@ +/** + * 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.contentreport; + +import java.sql.SQLException; +import java.util.Arrays; +import java.util.EnumSet; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.function.BiPredicate; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.dspace.authorize.factory.AuthorizeServiceFactory; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.content.Bundle; +import org.dspace.content.Item; +import org.dspace.contentreport.ItemFilterUtil.BundleName; +import org.dspace.core.Context; +import org.dspace.services.factory.DSpaceServicesFactory; + +/** + * Available filters for the Filtered Collections and Filtered Items reports. + * In this enum, each item corresponds to a separate property, not values of + * a single property, hence the @JsonProperty applied to each of them. + * For each item, the annotation value is read through reflection and copied into + * the id property, which eliminates repetitions, hence reducing the risk or errors. + * + * @author Jean-François Morin (Université Laval) + */ +public enum Filter { + + @JsonProperty("is_item") + IS_ITEM(FilterCategory.PROPERTY, (context, item) -> true), + @JsonProperty("is_withdrawn") + IS_WITHDRAWN(FilterCategory.PROPERTY, (context, item) -> item.isWithdrawn()), + @JsonProperty("is_not_withdrawn") + IS_NOT_WITHDRAWN(FilterCategory.PROPERTY, (context, item) -> !item.isWithdrawn()), + @JsonProperty("is_discoverable") + IS_DISCOVERABLE(FilterCategory.PROPERTY, (context, item) -> item.isDiscoverable()), + @JsonProperty("is_not_discoverable") + IS_NOT_DISCOVERABLE(FilterCategory.PROPERTY, (context, item) -> !item.isDiscoverable()), + + /** + * Matches items having multiple original bitstreams. + */ + @JsonProperty("has_multiple_originals") + HAS_MULTIPLE_ORIGINALS(FilterCategory.BITSTREAM, (context, item) -> + ItemFilterUtil.countOriginalBitstream(item) > 1), + /** + * Matches items having no original bitstreams. + */ + @JsonProperty("has_no_originals") + HAS_NO_ORIGINALS(FilterCategory.BITSTREAM, (context, item) -> ItemFilterUtil.countOriginalBitstream(item) == 0), + /** + * Matches items having exactly one original bitstream. + */ + @JsonProperty("has_one_original") + HAS_ONE_ORIGINAL(FilterCategory.BITSTREAM, (context, item) -> ItemFilterUtil.countOriginalBitstream(item) == 1), + + /** + * Matches items having bitstreams with a MIME type that matches one defined in the "rest.report-mime-document" + * configuration property. + */ + @JsonProperty("has_doc_original") + HAS_DOC_ORIGINAL(FilterCategory.BITSTREAM_MIME, (context, item) -> + ItemFilterUtil.countOriginalBitstreamMime(context, item, ItemFilterUtil.getDocumentMimeTypes()) > 0), + /** + * Matches items having bitstreams with a MIME type starting with "image" (e.g., image/jpeg, image/png). + */ + @JsonProperty("has_image_original") + HAS_IMAGE_ORIGINAL(FilterCategory.BITSTREAM_MIME, (context, item) -> + ItemFilterUtil.countOriginalBitstreamMimeStartsWith(context, item, "image") > 0), + /** + * Matches items having bitstreams with a MIME type other than document (cf. HAS_DOCUMENT above) or image + * (cf. HAS_IMAGE_ORIGINAL above). + */ + @JsonProperty("has_unsupp_type") + HAS_UNSUPPORTED_TYPE(FilterCategory.BITSTREAM_MIME, (context, item) -> { + int bitCount = ItemFilterUtil.countOriginalBitstream(item); + if (bitCount == 0) { + return false; + } + int docCount = ItemFilterUtil.countOriginalBitstreamMime(context, item, ItemFilterUtil.getDocumentMimeTypes()); + int imgCount = ItemFilterUtil.countOriginalBitstreamMimeStartsWith(context, item, "image"); + return (bitCount - docCount - imgCount) > 0; + }), + /** + * Matches items having bitstreams of multiple types (document, image, other). + */ + @JsonProperty("has_mixed_original") + HAS_MIXED_ORIGINAL(FilterCategory.BITSTREAM_MIME, (context, item) -> { + int countBit = ItemFilterUtil.countOriginalBitstream(item); + if (countBit <= 1) { + return false; + } + int countDoc = ItemFilterUtil.countOriginalBitstreamMime(context, item, ItemFilterUtil.getDocumentMimeTypes()); + if (countDoc > 0) { + return countDoc != countBit; + } + int countImg = ItemFilterUtil.countOriginalBitstreamMimeStartsWith(context, item, "image"); + if (countImg > 0) { + return countImg != countBit; + } + return false; + }), + @JsonProperty("has_pdf_original") + HAS_PDF_ORIGINAL(FilterCategory.BITSTREAM_MIME, (context, item) -> + ItemFilterUtil.countOriginalBitstreamMime(context, item, ItemFilterUtil.MIMES_PDF) > 0), + @JsonProperty("has_jpg_original") + HAS_JPEG_ORIGINAL(FilterCategory.BITSTREAM_MIME, (context, item) -> + ItemFilterUtil.countOriginalBitstreamMime(context, item, ItemFilterUtil.MIMES_JPG) > 0), + /** + * Matches items having at least one PDF of size less than 20 kb (configurable in rest.cfg). + */ + @JsonProperty("has_small_pdf") + HAS_SMALL_PDF(FilterCategory.BITSTREAM_MIME, (context, item) -> + ItemFilterUtil.countBitstreamSmallerThanMinSize( + context, BundleName.ORIGINAL, item, ItemFilterUtil.MIMES_PDF, "rest.report-pdf-min-size") > 0), + /** + * Matches items having at least one PDF of size more than 25 Mb (configurable in rest.cfg). + */ + @JsonProperty("has_large_pdf") + HAS_LARGE_PDF(FilterCategory.BITSTREAM_MIME, (context, item) -> + ItemFilterUtil.countBitstreamLargerThanMaxSize( + context, BundleName.ORIGINAL, item, ItemFilterUtil.MIMES_PDF, "rest.report-pdf-max-size") > 0), + /** + * Matches items having at least one non-text bitstream. + */ + @JsonProperty("has_doc_without_text") + HAS_DOC_WITHOUT_TEXT(FilterCategory.BITSTREAM_MIME, (context, item) -> { + int countDoc = ItemFilterUtil.countOriginalBitstreamMime(context, item, ItemFilterUtil.getDocumentMimeTypes()); + if (countDoc == 0) { + return false; + } + int countText = ItemFilterUtil.countBitstream(BundleName.TEXT, item); + return countDoc > countText; + }), + + /** + * Matches items having at least one image, but all of supported types. + */ + @JsonProperty("has_only_supp_image_type") + HAS_ONLY_SUPPORTED_IMAGE_TYPE(FilterCategory.MIME, (context, item) -> { + int imageCount = ItemFilterUtil.countOriginalBitstreamMimeStartsWith(context, item, "image/"); + if (imageCount == 0) { + return false; + } + int suppImageCount = ItemFilterUtil.countOriginalBitstreamMime( + context, item, ItemFilterUtil.getSupportedImageMimeTypes()); + return (imageCount == suppImageCount); + }), + /** + * Matches items having at least one image of an unsupported type. + */ + @JsonProperty("has_unsupp_image_type") + HAS_UNSUPPORTED_IMAGE_TYPE(FilterCategory.MIME, (context, item) -> { + int imageCount = ItemFilterUtil.countOriginalBitstreamMimeStartsWith(context, item, "image/"); + if (imageCount == 0) { + return false; + } + int suppImageCount = ItemFilterUtil.countOriginalBitstreamMime( + context, item, ItemFilterUtil.getSupportedImageMimeTypes()); + return (imageCount - suppImageCount) > 0; + }), + /** + * Matches items having at least one document, but all of supported types. + */ + @JsonProperty("has_only_supp_doc_type") + HAS_ONLY_SUPPORTED_DOC_TYPE(FilterCategory.MIME, (context, item) -> { + int docCount = ItemFilterUtil.countOriginalBitstreamMime(context, item, ItemFilterUtil.getDocumentMimeTypes()); + if (docCount == 0) { + return false; + } + int suppDocCount = ItemFilterUtil.countOriginalBitstreamMime( + context, item, ItemFilterUtil.getSupportedDocumentMimeTypes()); + return docCount == suppDocCount; + }), + /** + * Matches items having at least one document of an unsupported type. + */ + @JsonProperty("has_unsupp_doc_type") + HAS_UNSUPPORTED_DOC_TYPE(FilterCategory.MIME, (context, item) -> { + int docCount = ItemFilterUtil.countOriginalBitstreamMime(context, item, ItemFilterUtil.getDocumentMimeTypes()); + if (docCount == 0) { + return false; + } + int suppDocCount = ItemFilterUtil.countOriginalBitstreamMime( + context, item, ItemFilterUtil.getSupportedDocumentMimeTypes()); + return (docCount - suppDocCount) > 0; + }), + + /** + * Matches items having at least one unsupported bundle. + */ + @JsonProperty("has_unsupported_bundle") + HAS_UNSUPPORTED_BUNDLE(FilterCategory.BUNDLE, (context, item) -> { + String[] bundleList = DSpaceServicesFactory.getInstance().getConfigurationService() + .getArrayProperty("rest.report-supp-bundles"); + return ItemFilterUtil.hasUnsupportedBundle(item, bundleList); + }), + /** + * Matches items having at least one thumbnail of size less than 400 bytes (configurable in rest.cfg). + */ + @JsonProperty("has_small_thumbnail") + HAS_SMALL_THUMBNAIL(FilterCategory.BUNDLE, (context, item) -> + ItemFilterUtil.countBitstreamSmallerThanMinSize( + context, BundleName.THUMBNAIL, item, ItemFilterUtil.MIMES_JPG, "rest.report-thumbnail-min-size") > 0), + /** + * Matches items having at least one original without a thumbnail. + */ + @JsonProperty("has_original_without_thumbnail") + HAS_ORIGINAL_WITHOUT_THUMBNAIL(FilterCategory.BUNDLE, (context, item) -> { + int countBit = ItemFilterUtil.countOriginalBitstream(item); + if (countBit == 0) { + return false; + } + int countThumb = ItemFilterUtil.countBitstream(BundleName.THUMBNAIL, item); + return countBit > countThumb; + }), + /** + * Matches items having at least one non-JPEG thumbnail. + */ + @JsonProperty("has_invalid_thumbnail_name") + HAS_INVALID_THUMBNAIL_NAME(FilterCategory.BUNDLE, (context, item) -> { + List originalNames = ItemFilterUtil.getBitstreamNames(BundleName.ORIGINAL, item); + List thumbNames = ItemFilterUtil.getBitstreamNames(BundleName.THUMBNAIL, item); + if (thumbNames.size() != originalNames.size()) { + return false; + } + return originalNames.stream() + .anyMatch(name -> !thumbNames.contains(name + ".jpg") && !thumbNames.contains(name + ".jpeg")); + }), + /** + * Matches items having at least one non-generated thumbnail. + */ + @JsonProperty("has_non_generated_thumb") + HAS_NON_GENERATED_THUMBNAIL(FilterCategory.BUNDLE, (context, item) -> { + String[] generatedThumbDesc = DSpaceServicesFactory.getInstance().getConfigurationService() + .getArrayProperty("rest.report-gen-thumbnail-desc"); + int countThumb = ItemFilterUtil.countBitstream(BundleName.THUMBNAIL, item); + if (countThumb == 0) { + return false; + } + int countGen = ItemFilterUtil.countBitstreamByDesc(BundleName.THUMBNAIL, item, generatedThumbDesc); + return (countThumb > countGen); + }), + /** + * Matches items having no licence-typed bitstreams. + */ + @JsonProperty("no_license") + NO_LICENSE(FilterCategory.BUNDLE, (context, item) -> + ItemFilterUtil.countBitstream(BundleName.LICENSE, item) == 0), + /** + * Matches items having licence documentation (a licence bitstream named other than license.txt). + */ + @JsonProperty("has_license_documentation") + HAS_LICENSE_DOCUMENTATION(FilterCategory.BUNDLE, (context, item) -> { + List names = ItemFilterUtil.getBitstreamNames(BundleName.LICENSE, item); + return names.stream() + .anyMatch(name -> !name.equals("license.txt")); + }), + + /** + * Matches items having at least one original with restricted access. + */ + @JsonProperty("has_restricted_original") + HAS_RESTRICTED_ORIGINAL(FilterCategory.PERMISSION, (context, item) -> { + return item.getBundles().stream() + .filter(bundle -> bundle.getName().equals(BundleName.ORIGINAL.name())) + .map(Bundle::getBitstreams) + .flatMap(List::stream) + .anyMatch(bit -> { + try { + if (!getAuthorizeService() + .authorizeActionBoolean(getAnonymousContext(), bit, org.dspace.core.Constants.READ)) { + return true; + } + } catch (SQLException e) { + getLog().warn("SQL Exception testing original bitstream access " + e.getMessage(), e); + } + return false; + }); + }), + /** + * Matches items having at least one thumbnail with restricted access. + */ + @JsonProperty("has_restricted_thumbnail") + HAS_RESTRICTED_THUMBNAIL(FilterCategory.PERMISSION, (context, item) -> { + return item.getBundles().stream() + .filter(bundle -> bundle.getName().equals(BundleName.THUMBNAIL.name())) + .map(Bundle::getBitstreams) + .flatMap(List::stream) + .anyMatch(bit -> { + try { + if (!getAuthorizeService() + .authorizeActionBoolean(getAnonymousContext(), bit, org.dspace.core.Constants.READ)) { + return true; + } + } catch (SQLException e) { + getLog().warn("SQL Exception testing thumbnail bitstream access " + e.getMessage(), e); + } + return false; + }); + }), + /** + * Matches items having metadata with restricted access. + */ + @JsonProperty("has_restricted_metadata") + HAS_RESTRICTED_METADATA(FilterCategory.PERMISSION, (context, item) -> { + try { + return !getAuthorizeService() + .authorizeActionBoolean(getAnonymousContext(), item, org.dspace.core.Constants.READ); + } catch (SQLException e) { + getLog().warn("SQL Exception testing item metadata access " + e.getMessage(), e); + return false; + } + }); + + private static final Logger log = LogManager.getLogger(); + private static AuthorizeService authorizeService; + private static Context anonymousContext; + + private String id; + private FilterCategory category; + private BiPredicate itemTester; + + Filter(FilterCategory category, BiPredicate itemTester) { + try { + JsonProperty jp = getClass().getField(name()).getAnnotation(JsonProperty.class); + id = Optional.ofNullable(jp).map(JsonProperty::value).orElse(name()); + } catch (Exception e) { + id = name(); + } + this.category = category; + this.itemTester = itemTester; + } + + public String getId() { + return id; + } + + public FilterCategory getCategory() { + return category; + } + + public boolean testItem(Context context, Item item) { + return itemTester.test(context, item); + } + + private static Logger getLog() { + return log; + } + + private static AuthorizeService getAuthorizeService() { + if (authorizeService == null) { + authorizeService = AuthorizeServiceFactory.getInstance().getAuthorizeService(); + } + return authorizeService; + } + + private static Context getAnonymousContext() { + if (anonymousContext == null) { + anonymousContext = new Context(); + } + return anonymousContext; + } + + @JsonCreator + public static Filter get(String id) { + return Arrays.stream(values()) + .filter(item -> Objects.equals(item.id, id)) + .findFirst() + .orElse(null); + } + + public static Set getFilters(String filters) { + String[] ids = Optional.ofNullable(filters).orElse("").split("[^a-z_]+"); + Set set = Arrays.stream(ids) + .map(Filter::get) + .filter(f -> f != null) + .collect(Collectors.toCollection(() -> EnumSet.noneOf(Filter.class))); + if (set == null) { + set = EnumSet.noneOf(Filter.class); + } + return set; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/contentreport/FilterCategory.java b/dspace-api/src/main/java/org/dspace/contentreport/FilterCategory.java new file mode 100644 index 0000000000..8823ff3141 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/contentreport/FilterCategory.java @@ -0,0 +1,50 @@ +/** + * 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.contentreport; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Identifies the category/section of filters defined in the {@link Filter} enum. + * This enum will be used when/if the structured filter definitions are returned to + * the Angular layer through a REST endpoint. + * + * @author Jean-François Morin (Université Laval) + */ +public enum FilterCategory { + + PROPERTY("property"), + BITSTREAM("bitstream"), + BITSTREAM_MIME("bitstream_mime"), + MIME("mime"), + BUNDLE("bundle"), + PERMISSION("permission"); + + private String id; + private List filters; + + FilterCategory(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public List getFilters() { + if (filters == null) { + filters = Arrays.stream(Filter.values()) + .filter(f -> f.getCategory() == this) + .collect(Collectors.toUnmodifiableList()); + } + return filters; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/contentreport/FilteredCollection.java b/dspace-api/src/main/java/org/dspace/contentreport/FilteredCollection.java new file mode 100644 index 0000000000..21a39778ba --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/contentreport/FilteredCollection.java @@ -0,0 +1,219 @@ +/** + * 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.contentreport; + +import java.io.Serializable; +import java.util.EnumMap; +import java.util.Map; +import java.util.Optional; + +/** + * This class represents an entry in the Filtered Collections report. + * + * @author Jean-François Morin (Université Laval) + */ +public class FilteredCollection implements Cloneable, Serializable { + + private static final long serialVersionUID = -231735620268582719L; + + /** Name of the collection */ + private String label; + /** Handle of the collection, used to make it clickable from the generated report */ + private String handle; + /** Name of the owning community */ + private String communityLabel; + /** Handle of the owning community, used to make it clickable from the generated report */ + private String communityHandle; + /** Total number of items in the collection */ + private int totalItems; + /** Number of filtered items per requested filter in the collection */ + private Map values = new EnumMap<>(Filter.class); + /** Number of items in the collection that match all requested filters */ + private int allFiltersValue; + /** + * Indicates whether this object is protected against further changes. + * This is used in computing summary data in the parent FilteredCollectionsRest class. + */ + private boolean sealed; + + /** + * Shortcut method that builds a FilteredCollectionRest instance + * from its building blocks. + * @param label Name of the collection + * @param handle Handle of the collection + * @param communityLabel Name of the owning community + * @param communityHandle Handle of the owning community + * @param totalItems Total number of items in the collection + * @param allFiltersValue Number of items in the collection that match all requested filters + * @param values Number of filtered items per requested filter in the collection + * @param doSeal true if the collection must be sealed immediately + * @return a FilteredCollectionRest instance built from the provided parameters + */ + public static FilteredCollection of(String label, String handle, + String communityLabel, String communityHandle, + int totalItems, int allFiltersValue, Map values, boolean doSeal) { + var coll = new FilteredCollection(); + coll.label = label; + coll.handle = handle; + coll.communityLabel = communityLabel; + coll.communityHandle = communityHandle; + coll.totalItems = totalItems; + coll.allFiltersValue = allFiltersValue; + Optional.ofNullable(values).ifPresent(vs -> vs.forEach(coll::addValue)); + if (doSeal) { + coll.seal(); + } + return coll; + } + + /** + * Returns the item counts per filter. + * If this object is sealed, a defensive copy will be returned. + * + * @return the item counts per filter + */ + public Map getValues() { + if (sealed) { + return new EnumMap<>(values); + } + return values; + } + + /** + * Increments a filtered item count for a given filter. + * + * @param filter Filter to add to the requested filters in this collection + * @param delta Number by which the filtered item count must be incremented + * for the requested filter + */ + public void addValue(Filter filter, int delta) { + checkSealed(); + Integer oldValue = values.getOrDefault(filter, Integer.valueOf(0)); + int newValue = oldValue.intValue() + delta; + values.put(filter, Integer.valueOf(newValue)); + } + + /** + * Sets all filtered item counts for this collection. + * The contents are copied into this object's internal Map, which is protected against + * further tampering with the provided Map. + * + * @param values Values that replace the current ones + */ + public void setValues(Map values) { + checkSealed(); + this.values.clear(); + this.values.putAll(values); + } + + public String getLabel() { + return label; + } + + public void setLabel(String label) { + checkSealed(); + this.label = label; + } + + public String getHandle() { + return handle; + } + + public void setHandle(String handle) { + checkSealed(); + this.handle = handle; + } + + public String getCommunityLabel() { + return communityLabel; + } + + public void setCommunityLabel(String communityLabel) { + checkSealed(); + this.communityLabel = communityLabel; + } + + public String getCommunityHandle() { + return communityHandle; + } + + public void setCommunityHandle(String communityHandle) { + checkSealed(); + this.communityHandle = communityHandle; + } + + public int getTotalItems() { + return totalItems; + } + + public void setTotalItems(int totalItems) { + checkSealed(); + this.totalItems = totalItems; + } + + public int getAllFiltersValue() { + return allFiltersValue; + } + + /** + * Increments the count of items matching all filters. + * + * @param delta Number by which the count must be incremented + */ + public void addAllFiltersValue(int delta) { + checkSealed(); + allFiltersValue++; + } + + /** + * Replaces the count of items matching all filters. + * + * @param allFiltersValue Number that replaces the current item count + */ + public void setAllFiltersValue(int allFiltersValue) { + checkSealed(); + this.allFiltersValue = allFiltersValue; + } + + public boolean getSealed() { + return sealed; + } + + /** + * Seals this filtered collection object. + * No changes to this object can be made afterwards. Any attempt will throw + * an IllegalStateException. + */ + public void seal() { + sealed = true; + } + + private void checkSealed() { + if (sealed) { + throw new IllegalStateException("This filtered collection record is sealed" + + " and cannot be modified anymore. You can apply changes to a non-sealed clone."); + } + } + + /** + * Returns a non-sealed clone of this filtered collection record. + * + * @return a new non-sealed FilteredCollectionRest instance containing + * all attribute values of this object + */ + @Override + public FilteredCollection clone() { + var clone = new FilteredCollection(); + clone.label = label; + clone.handle = handle; + clone.values.putAll(values); + clone.allFiltersValue = allFiltersValue; + return clone; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/contentreport/FilteredCollections.java b/dspace-api/src/main/java/org/dspace/contentreport/FilteredCollections.java new file mode 100644 index 0000000000..058d1546d1 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/contentreport/FilteredCollections.java @@ -0,0 +1,107 @@ +/** + * 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.contentreport; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +/** + * This class represents the complete result of a Filtered Collections report query. + * In addition to the list of FilteredCollection entries, it contains the lazily computed + * summary to be included in the completed report. + * + * @author Jean-François Morin (Université Laval) + */ +public class FilteredCollections implements Serializable { + + private static final long serialVersionUID = 3622651208704009095L; + + /** Collections included in the report */ + private List collections = new ArrayList<>(); + /** + * Summary generated by adding up data for each filter included in the report. + * It will be regenerated if any non-sealed collection item is found in + * the {@link #collections} collection attribute. + */ + private FilteredCollection summary; + + /** + * Shortcut method that builds a FilteredCollectionsRest instance + * from its building blocks. + * @param collections a list of FilteredCollectionRest instances + * @return a FilteredCollectionsRest instance built from the provided parameters + */ + public static FilteredCollections of(Collection collections) { + var colls = new FilteredCollections(); + Optional.ofNullable(collections).ifPresent(cs -> cs.stream().forEach(colls::addCollection)); + return colls; + } + + /** + * Returns a defensive copy of the collections included in this report. + * + * @return the collections included in this report + */ + public List getCollections() { + return new ArrayList<>(collections); + } + + /** + * Adds a {@link FilteredCollectionRest} object to this report. + * + * @param coll {@link FilteredCollectionRest} to add to this report + */ + public void addCollection(FilteredCollection coll) { + summary = null; + collections.add(coll); + } + + /** + * Sets all collections for this report. + * The contents are copied into this object's internal list, which is protected against + * further tampering with the provided list. + * + * @param collections Values that replace the current ones + */ + public void setCollections(List collections) { + summary = null; + this.collections.clear(); + this.collections.addAll(collections); + } + + /** + * Returns the report summary. + * If the summary has not been computed yet and/or the report includes non-sealed collections, + * it will be regenerated. + * + * @return the generated report summary + */ + public FilteredCollection getSummary() { + boolean needsRefresh = summary == null || collections.stream().anyMatch(c -> !c.getSealed()); + if (needsRefresh) { + summary = new FilteredCollection(); + for (var coll : collections) { + coll.getValues().forEach(summary::addValue); + } + int total = collections.stream() + .mapToInt(FilteredCollection::getTotalItems) + .sum(); + summary.setTotalItems(total); + int allFilters = collections.stream() + .mapToInt(FilteredCollection::getAllFiltersValue) + .sum(); + summary.setAllFiltersValue(allFilters); + summary.seal(); + } + return summary; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/contentreport/FilteredItems.java b/dspace-api/src/main/java/org/dspace/contentreport/FilteredItems.java new file mode 100644 index 0000000000..71c4c74a3e --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/contentreport/FilteredItems.java @@ -0,0 +1,70 @@ +/** + * 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.contentreport; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +import org.dspace.content.Item; + +/** + * This class represents a list of items for a Filtered Items report query. + * Since the underlying list should correspond to only a page of results, + * the total number of items found through the query is included in this report. + * + * @author Jean-François Morin (Université Laval) + */ +public class FilteredItems implements Serializable { + + private static final long serialVersionUID = 7980375013177658249L; + + /** Items included in the report */ + private List items = new ArrayList<>(); + /** Total item count (for pagination) */ + private long itemCount; + + /** + * Returns a defensive copy of the items included in this report. + * + * @return the items included in this report + */ + public List getItems() { + return new ArrayList<>(items); + } + + /** + * Adds an {@link ItemRest} object to this report. + * + * @param item {@link ItemRest} to add to this report + */ + public void addItem(Item item) { + items.add(item); + } + + /** + * Sets all items for this report. + * The contents are copied into this object's internal list, which is protected + * against further tampering with the provided list. + * + * @param items Values that replace the current ones + */ + public void setItems(List items) { + this.items.clear(); + this.items.addAll(items); + } + + public long getItemCount() { + return itemCount; + } + + public void setItemCount(long itemCount) { + this.itemCount = itemCount; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/contentreport/FilteredItemsQuery.java b/dspace-api/src/main/java/org/dspace/contentreport/FilteredItemsQuery.java new file mode 100644 index 0000000000..3dc9faed1c --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/contentreport/FilteredItemsQuery.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.contentreport; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.EnumSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +/** + * Structured query contents for the Filtered Items report + * @author Jean-François Morin (Université Laval) + */ +public class FilteredItemsQuery { + + private List collections = new ArrayList<>(); + private List queryPredicates = new ArrayList<>(); + private long offset; + private int pageLimit; + private Set filters = EnumSet.noneOf(Filter.class); + private List additionalFields = new ArrayList<>(); + + /** + * Shortcut method that builds a FilteredItemsQuery instance + * from its building blocks. + * @param collectionUuids collection UUIDs to add + * @param predicates query predicates used to filter existing items + * @param pageLimit number of items per page + * @param filters filters to apply to existing items + * The filters mapping to true will be applied, others (either missing or + * mapping to false) will not. + * @param additionalFields additional fields to display in the resulting report + * @return a FilteredItemsQuery instance built from the provided parameters + */ + public static FilteredItemsQuery of(Collection collectionUuids, + Collection predicates, long offset, int pageLimit, + Collection filters, Collection additionalFields) { + var query = new FilteredItemsQuery(); + Optional.ofNullable(collectionUuids).ifPresent(query.collections::addAll); + Optional.ofNullable(predicates).ifPresent(query.queryPredicates::addAll); + query.offset = offset; + query.pageLimit = pageLimit; + Optional.ofNullable(filters).ifPresent(query.filters::addAll); + Optional.ofNullable(additionalFields).ifPresent(query.additionalFields::addAll); + return query; + } + + public List getCollections() { + return collections; + } + + public void setCollections(List collections) { + this.collections.clear(); + if (collections != null) { + this.collections.addAll(collections); + } + } + + public List getQueryPredicates() { + return queryPredicates; + } + + public void setQueryPredicates(List queryPredicates) { + this.queryPredicates.clear(); + if (queryPredicates != null) { + this.queryPredicates.addAll(queryPredicates); + } + } + + public long getOffset() { + return offset; + } + + public void setOffset(long offset) { + this.offset = offset; + } + + public int getPageLimit() { + return pageLimit; + } + + public void setPageLimit(int pageLimit) { + this.pageLimit = pageLimit; + } + + public Set getFilters() { + return filters; + } + + public void setFilters(Set filters) { + this.filters.clear(); + if (filters != null) { + this.filters.addAll(filters); + } + } + + public List getAdditionalFields() { + return additionalFields; + } + + public void setAdditionalFields(List additionalFields) { + this.additionalFields.clear(); + if (additionalFields != null) { + this.additionalFields.addAll(additionalFields); + } + } + +} diff --git a/dspace-api/src/main/java/org/dspace/contentreport/ItemFilterUtil.java b/dspace-api/src/main/java/org/dspace/contentreport/ItemFilterUtil.java new file mode 100644 index 0000000000..20c714fcf3 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/contentreport/ItemFilterUtil.java @@ -0,0 +1,353 @@ +/** + * 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.contentreport; + +import static org.dspace.content.Item.ANY; + +import java.sql.SQLException; +import java.util.Arrays; +import java.util.Calendar; +import java.util.List; +import java.util.Set; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.dspace.content.Bitstream; +import org.dspace.content.Bundle; +import org.dspace.content.Item; +import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.dspace.services.factory.DSpaceServicesFactory; + +/** + * Utility methods for applying some of the filters defined in the {@link Filter} enum. + * + * @author Jean-François Morin (Université Laval) (port to DSpace 7.x) + * @author Terry Brady, Georgetown University (original code in DSpace 6.x) + */ +public class ItemFilterUtil { + + protected static ItemService itemService = ContentServiceFactory.getInstance().getItemService(); + private static final Logger log = LogManager.getLogger(ItemFilterUtil.class); + public static final String[] MIMES_PDF = {"application/pdf"}; + public static final String[] MIMES_JPG = {"image/jpeg"}; + + /** + * Supported bundle types. + * N.B.: Bundle names are used in metadata as they are named here. + * Do NOT change these names, the name() method is invoked at multiple + * locations in this class and enum Filter. + * If these names are to change, the name() invocations shall be changed + * so that they refer to these unchanged names, likely through a String property. + */ + enum BundleName { + ORIGINAL, TEXT, LICENSE, THUMBNAIL; + } + + private ItemFilterUtil() {} + + static String[] getDocumentMimeTypes() { + return DSpaceServicesFactory.getInstance().getConfigurationService() + .getArrayProperty("rest.report-mime-document"); + } + + static String[] getSupportedDocumentMimeTypes() { + return DSpaceServicesFactory.getInstance().getConfigurationService() + .getArrayProperty("rest.report-mime-document-supported"); + } + + static String[] getSupportedImageMimeTypes() { + return DSpaceServicesFactory.getInstance().getConfigurationService() + .getArrayProperty("rest.report-mime-document-image"); + } + + /** + * Counts the original bitstreams of a given item. + * @param item Provided item + * @return the number of original bitstreams in the item + */ + static int countOriginalBitstream(Item item) { + return countBitstream(BundleName.ORIGINAL, item); + } + + /** + * Counts the bitstreams of a given item for a specific type. + * @param bundleName Type of bundle to filter bitstreams + * @param item Provided item + * @return the number of matching bitstreams in the item + */ + static int countBitstream(BundleName bundleName, Item item) { + return item.getBundles().stream() + .filter(bundle -> bundle.getName().equals(bundleName.name())) + .mapToInt(bundle -> bundle.getBitstreams().size()) + .sum(); + } + + /** + * Retrieves the bitstream names of an given item for a specific bundle type. + * @param bundleName Type of bundle to filter bitstreams + * @param item Provided item + * @return the names of matching bitstreams in the item + */ + static List getBitstreamNames(BundleName bundleName, Item item) { + return item.getBundles().stream() + .filter(bundle -> bundle.getName().equals(bundleName.name())) + .map(Bundle::getBitstreams) + .flatMap(List::stream) + .map(Bitstream::getName) + .collect(Collectors.toList()); + } + + /** + * Counts the original bitstreams of a given item matching one of a list of specific MIME types. + * @param context DSpace context + * @param item Provided item + * @param mimeList List of MIME types to filter bitstreams + * @return number of matching original bitstreams + */ + static int countOriginalBitstreamMime(Context context, Item item, String[] mimeList) { + return countBitstreamMime(context, BundleName.ORIGINAL, item, mimeList); + } + + /** + * Counts the bitstreams of a given item for a specific type matching one of a list of specific MIME types. + * @param context DSpace context + * @param bundleName Type of bundle to filter bitstreams + * @param item Provided item + * @param mimeList List of MIME types to filter bitstreams + * @return number of matching bitstreams + */ + static int countBitstreamMime(Context context, BundleName bundleName, Item item, String[] mimeList) { + return item.getBundles().stream() + .filter(bundle -> bundle.getName().equals(bundleName.name())) + .map(Bundle::getBitstreams) + .flatMap(List::stream) + .mapToInt(bit -> { + int count = 0; + for (String mime : mimeList) { + try { + if (bit.getFormat(context).getMIMEType().equals(mime.trim())) { + count++; + } + } catch (SQLException e) { + log.error("Get format error for bitstream " + bit.getName()); + } + } + return count; + }) + .sum(); + } + + /** + * Counts the bitstreams of a given item for a specific type matching one of a list of specific descriptions. + * @param bundleName Type of bundle to filter bitstreams + * @param item Provided item + * @param descList List of descriptions to filter bitstreams + * @return number of matching bitstreams + */ + static int countBitstreamByDesc(BundleName bundleName, Item item, String[] descList) { + return item.getBundles().stream() + .filter(bundle -> bundle.getName().equals(bundleName.name())) + .map(Bundle::getBitstreams) + .flatMap(List::stream) + .filter(bit -> bit.getDescription() != null) + .mapToInt(bit -> { + int count = 0; + for (String desc : descList) { + String bitDesc = bit.getDescription(); + if (bitDesc.equals(desc.trim())) { + count++; + } + } + return count; + }) + .sum(); + } + + /** + * Counts the bitstreams of a given item smaller than a given size for a specific type + * matching one of a list of specific MIME types. + * @param context DSpace context + * @param bundleName Type of bundle to filter bitstreams + * @param item Provided item + * @param mimeList List of MIME types to filter bitstreams + * @param prop Configurable property providing the size to filter bitstreams + * @return number of matching bitstreams + */ + static int countBitstreamSmallerThanMinSize( + Context context, BundleName bundleName, Item item, String[] mimeList, String prop) { + long size = DSpaceServicesFactory.getInstance().getConfigurationService().getLongProperty(prop); + return item.getBundles().stream() + .filter(bundle -> bundle.getName().equals(bundleName.name())) + .map(Bundle::getBitstreams) + .flatMap(List::stream) + .mapToInt(bit -> { + int count = 0; + for (String mime : mimeList) { + try { + if (bit.getFormat(context).getMIMEType().equals(mime.trim())) { + if (bit.getSizeBytes() < size) { + count++; + } + } + } catch (SQLException e) { + log.error(e.getMessage(), e); + } + } + return count; + }) + .sum(); + } + + /** + * Counts the bitstreams of a given item larger than a given size for a specific type + * matching one of a list of specific MIME types. + * @param context DSpace context + * @param bundleName Type of bundle to filter bitstreams + * @param item Provided item + * @param mimeList List of MIME types to filter bitstreams + * @param prop Configurable property providing the size to filter bitstreams + * @return number of matching bitstreams + */ + static int countBitstreamLargerThanMaxSize( + Context context, BundleName bundleName, Item item, String[] mimeList, String prop) { + long size = DSpaceServicesFactory.getInstance().getConfigurationService().getLongProperty(prop); + return item.getBundles().stream() + .filter(bundle -> bundle.getName().equals(bundleName.name())) + .map(Bundle::getBitstreams) + .flatMap(List::stream) + .mapToInt(bit -> { + int count = 0; + for (String mime : mimeList) { + try { + if (bit.getFormat(context).getMIMEType().equals(mime.trim())) { + if (bit.getSizeBytes() > size) { + count++; + } + } + } catch (SQLException e) { + log.error(e.getMessage(), e); + } + } + return count; + }) + .sum(); + } + + /** + * Counts the original bitstreams of a given item whose MIME type starts with a specific prefix. + * @param context DSpace context + * @param item Provided item + * @param prefix Prefix to filter bitstreams + * @return number of matching original bitstreams + */ + static int countOriginalBitstreamMimeStartsWith(Context context, Item item, String prefix) { + return countBitstreamMimeStartsWith(context, BundleName.ORIGINAL, item, prefix); + } + + /** + * Counts the bitstreams of a given item for a specific type whose MIME type starts with a specific prefix. + * @param context DSpace context + * @param bundleName Type of bundle to filter bitstreams + * @param item Provided item + * @param prefix Prefix to filter bitstreams + * @return number of matching bitstreams + */ + static int countBitstreamMimeStartsWith(Context context, BundleName bundleName, Item item, String prefix) { + return item.getBundles().stream() + .filter(bundle -> bundle.getName().equals(bundleName.name())) + .map(Bundle::getBitstreams) + .flatMap(List::stream) + .mapToInt(bit -> { + int count = 0; + try { + if (bit.getFormat(context).getMIMEType().startsWith(prefix)) { + count++; + } + } catch (SQLException e) { + log.error(e.getMessage(), e); + } + return count; + }) + .sum(); + } + + /** + * Returns true if a given item has a bundle not matching a specific list of bundles. + * @param item Provided item + * @param bundleList List of bundle names to filter bundles + * @return true if the item has a (non-)matching bundle + */ + static boolean hasUnsupportedBundle(Item item, String[] bundleList) { + if (bundleList == null) { + return false; + } + Set bundles = Arrays.stream(bundleList) + .collect(Collectors.toSet()); + return item.getBundles().stream() + .anyMatch(bundle -> !bundles.contains(bundle.getName())); + } + + static boolean hasOriginalBitstreamMime(Context context, Item item, String[] mimeList) { + return hasBitstreamMime(context, BundleName.ORIGINAL, item, mimeList); + } + + static boolean hasBitstreamMime(Context context, BundleName bundleName, Item item, String[] mimeList) { + return countBitstreamMime(context, bundleName, item, mimeList) > 0; + } + + /** + * Returns true if a given item has at least one field of a specific list whose value + * matches a provided regular expression. + * @param item Provided item + * @param fieldList List of fields to check + * @param regex Regular expression to check field values against + * @return true if there is at least one matching field, false otherwise + */ + static boolean hasMetadataMatch(Item item, String fieldList, Pattern regex) { + if ("*".equals(fieldList)) { + return itemService.getMetadata(item, ANY, ANY, ANY, ANY).stream() + .anyMatch(md -> regex.matcher(md.getValue()).matches()); + } + + return Arrays.stream(fieldList.split(",")) + .map(field -> itemService.getMetadataByMetadataString(item, field.trim())) + .flatMap(List::stream) + .anyMatch(md -> regex.matcher(md.getValue()).matches()); + } + + /** + * Returns true if a given item has at all fields of a specific list whose values + * match a provided regular expression. + * @param item Provided item + * @param fieldList List of fields to check + * @param regex Regular expression to check field values against + * @return true if all specified fields match, false otherwise + */ + static boolean hasOnlyMetadataMatch(Item item, String fieldList, Pattern regex) { + if ("*".equals(fieldList)) { + return itemService.getMetadata(item, ANY, ANY, ANY, ANY).stream() + .allMatch(md -> regex.matcher(md.getValue()).matches()); + } + + return Arrays.stream(fieldList.split(",")) + .map(field -> itemService.getMetadataByMetadataString(item, field.trim())) + .flatMap(List::stream) + .allMatch(md -> regex.matcher(md.getValue()).matches()); + } + + static boolean recentlyModified(Item item, int days) { + Calendar cal = Calendar.getInstance(); + cal.add(Calendar.DATE, -days); + return cal.getTime().before(item.getLastModified()); + } + +} diff --git a/dspace-api/src/main/java/org/dspace/contentreport/QueryOperator.java b/dspace-api/src/main/java/org/dspace/contentreport/QueryOperator.java new file mode 100644 index 0000000000..7cd8606f83 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/contentreport/QueryOperator.java @@ -0,0 +1,102 @@ +/** + * 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.contentreport; + +import java.util.Arrays; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.Expression; +import jakarta.persistence.criteria.Path; +import jakarta.persistence.criteria.Predicate; +import org.apache.commons.lang3.function.TriFunction; +import org.dspace.content.MetadataValue; +import org.dspace.content.MetadataValue_; +import org.dspace.util.DSpacePostgreSQLDialect; +import org.dspace.util.JpaCriteriaBuilderKit; + +/** + * Operators available for creating predicates to query the + * Filtered Items report + * @author Jean-François Morin (Université Laval) + */ +public enum QueryOperator { + + EXISTS("exists", true, false, + (val, regexClause, jpaKit) -> jpaKit.criteriaBuilder().isNotNull(jpaKit.root().get(MetadataValue_.VALUE))), + DOES_NOT_EXIST("doesnt_exist", true, true, + (val, regexClause, jpaKit) -> EXISTS.buildJpaPredicate(val, regexClause, jpaKit)), + EQUALS("equals", true, false, + (val, regexClause, jpaKit) -> jpaKit.criteriaBuilder().equal(jpaKit.root().get(MetadataValue_.VALUE), val)), + DOES_NOT_EQUAL("not_equals", true, true, + (val, regexClause, jpaKit) -> EQUALS.buildJpaPredicate(val, regexClause, jpaKit)), + LIKE("like", true, false, + (val, regexClause, jpaKit) -> jpaKit.criteriaBuilder().like(jpaKit.root().get(MetadataValue_.VALUE), val)), + NOT_LIKE("not_like", true, true, + (val, regexClause, jpaKit) -> LIKE.buildJpaPredicate(val, regexClause, jpaKit)), + CONTAINS("contains", true, false, + (val, regexClause, jpaKit) -> LIKE.buildJpaPredicate("%" + val + "%", regexClause, jpaKit)), + DOES_NOT_CONTAIN("doesnt_contain", true, true, + (val, regexClause, jpaKit) -> CONTAINS.buildJpaPredicate(val, regexClause, jpaKit)), + MATCHES("matches", false, false, + (val, regexClause, jpaKit) -> regexPredicate(val, DSpacePostgreSQLDialect.REGEX_MATCHES, jpaKit)), + DOES_NOT_MATCH("doesnt_match", false, false, + (val, regexClause, jpaKit) -> regexPredicate(val, DSpacePostgreSQLDialect.REGEX_NOT_MATCHES, jpaKit)); + + private final String code; + private final TriFunction, Predicate> predicateBuilder; + private final boolean usesRegex; + private final boolean negate; + + QueryOperator(String code, boolean usesRegex, boolean negate, + TriFunction, Predicate> predicateBuilder) { + this.code = code; + this.usesRegex = usesRegex; + this.negate = negate; + this.predicateBuilder = predicateBuilder; + } + + @JsonProperty + public String getCode() { + return code; + } + + public boolean getUsesRegex() { + return usesRegex; + } + + public boolean getNegate() { + return negate; + } + + public Predicate buildJpaPredicate(String val, String regexClause, JpaCriteriaBuilderKit jpaKit) { + return predicateBuilder.apply(val, regexClause, jpaKit); + } + + @JsonCreator + public static QueryOperator get(String code) { + return Arrays.stream(values()) + .filter(item -> item.code.equalsIgnoreCase(code)) + .findFirst() + .orElse(null); + } + + private static Predicate regexPredicate(String val, String regexFunction, + JpaCriteriaBuilderKit jpaKit) { + // Source: https://stackoverflow.com/questions/24995881/use-regular-expressions-in-jpa-criteriabuilder + CriteriaBuilder builder = jpaKit.criteriaBuilder(); + Expression patternExpression = builder.literal(val); + Path path = jpaKit.root().get(MetadataValue_.VALUE); + // "matches" comes from the name of the regex function + // defined in class DSpacePostgreSQLDialect + return builder.equal(builder + .function(regexFunction, Boolean.class, path, patternExpression), Boolean.TRUE); + } + +} diff --git a/dspace-api/src/main/java/org/dspace/contentreport/QueryPredicate.java b/dspace-api/src/main/java/org/dspace/contentreport/QueryPredicate.java new file mode 100644 index 0000000000..91c9b78255 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/contentreport/QueryPredicate.java @@ -0,0 +1,69 @@ +/** + * 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.contentreport; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.dspace.content.MetadataField; + +/** + * Data structure representing a query predicate used by the Filtered Items report + * to filter items to retrieve. + * @author Jean-François Morin (Université Laval) + */ +public class QueryPredicate { + + private List fields = new ArrayList<>(); + private QueryOperator operator; + private String value; + + /** + * Shortcut method that builds a QueryPredicate from a single field, an operator, and a value. + * @param field Predicate subject + * @param operator Predicate operator + * @param value Predicate object + * @return a QueryPredicate instance built from the provided parameters + */ + public static QueryPredicate of(MetadataField field, QueryOperator operator, String value) { + var predicate = new QueryPredicate(); + predicate.fields.add(field); + predicate.operator = operator; + predicate.value = value; + return predicate; + } + + /** + * Shortcut method that builds a QueryPredicate from a list of fields, an operator, and a value. + * @param fields Fields that form the predicate subject + * @param operator Predicate operator + * @param value Predicate object + * @return a QueryPredicate instance built from the provided parameters + */ + public static QueryPredicate of(Collection fields, QueryOperator operator, String value) { + var predicate = new QueryPredicate(); + predicate.fields.addAll(fields); + predicate.operator = operator; + predicate.value = value; + return predicate; + } + + public List getFields() { + return fields; + } + + public QueryOperator getOperator() { + return operator; + } + + public String getValue() { + return value; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/contentreport/service/ContentReportService.java b/dspace-api/src/main/java/org/dspace/contentreport/service/ContentReportService.java new file mode 100644 index 0000000000..16c01b8b48 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/contentreport/service/ContentReportService.java @@ -0,0 +1,55 @@ +/** + * 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.contentreport.service; + +import java.sql.SQLException; +import java.util.Collection; +import java.util.List; + +import org.dspace.content.MetadataField; +import org.dspace.contentreport.Filter; +import org.dspace.contentreport.FilteredCollection; +import org.dspace.contentreport.FilteredItems; +import org.dspace.contentreport.FilteredItemsQuery; +import org.dspace.core.Context; + +public interface ContentReportService { + + /** + * Returns true< if Content Reports are enabled. + * @return true< if Content Reports are enabled + */ + boolean getEnabled(); + + /** + * Retrieves item statistics per collection according to a set of Boolean filters. + * @param context DSpace context + * @param filters Set of filters + * @return a list of collections with the requested statistics for each of them + */ + List findFilteredCollections(Context context, Collection filters); + + /** + * Retrieves a list of items according to a set of criteria. + * @param context DSpace context + * @param query structured query to find items against + * @return a list of items filtered according to the provided query + */ + FilteredItems findFilteredItems(Context context, FilteredItemsQuery query); + + /** + * Converts a metadata field name to a list of {@link MetadataField} instances + * (one if no wildcards are used, possibly more otherwise). + * @param context DSpace context + * @param metadataField field to search for + * @return a corresponding list of {@link MetadataField} entries + */ + List getMetadataFields(org.dspace.core.Context context, String metadataField) + throws SQLException; + +} diff --git a/dspace-api/src/main/java/org/dspace/core/AbstractHibernateDAO.java b/dspace-api/src/main/java/org/dspace/core/AbstractHibernateDAO.java index 32ad747d76..b4347f0969 100644 --- a/dspace-api/src/main/java/org/dspace/core/AbstractHibernateDAO.java +++ b/dspace-api/src/main/java/org/dspace/core/AbstractHibernateDAO.java @@ -13,13 +13,13 @@ import java.util.List; import java.util.Map; import java.util.UUID; import java.util.stream.Stream; -import javax.persistence.Query; -import javax.persistence.criteria.CriteriaBuilder; -import javax.persistence.criteria.CriteriaQuery; -import javax.persistence.criteria.Expression; -import javax.persistence.criteria.Root; import com.google.common.collect.AbstractIterator; +import jakarta.persistence.Query; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Expression; +import jakarta.persistence.criteria.Root; import org.apache.commons.collections.CollectionUtils; import org.hibernate.Session; @@ -102,6 +102,16 @@ public abstract class AbstractHibernateDAO implements GenericDAO { return result; } + @Override + public T findByID(Context context, Class clazz, String id) throws SQLException { + if (id == null) { + return null; + } + @SuppressWarnings("unchecked") + T result = (T) getHibernateSession(context).get(clazz, id); + return result; + } + @Override public List findMany(Context context, String query) throws SQLException { @SuppressWarnings("unchecked") @@ -303,7 +313,7 @@ public abstract class AbstractHibernateDAO implements GenericDAO { org.hibernate.query.Query hquery = query.unwrap(org.hibernate.query.Query.class); Stream stream = hquery.stream(); Iterator iter = stream.iterator(); - return new AbstractIterator () { + return new AbstractIterator() { @Override protected T computeNext() { return iter.hasNext() ? iter.next() : endOfData(); @@ -336,7 +346,7 @@ public abstract class AbstractHibernateDAO implements GenericDAO { /** * This method will return the count of items for this query as an integer - * This query needs to already be in a formate that'll return one record that contains the amount + * This query needs to already be in a format that'll return one record that contains the amount * * @param query * The query for which the amount of results will be returned. @@ -461,4 +471,15 @@ public abstract class AbstractHibernateDAO implements GenericDAO { return executeCriteriaQuery(context, criteria, cacheable, maxResults, offset); } + /** + * Create a Query object from a CriteriaQuery + * @param context current Context + * @param criteriaQuery CriteriaQuery built via CriteriaBuilder + * @return corresponding Query + * @throws SQLException if error occurs + */ + public Query createQuery(Context context, CriteriaQuery criteriaQuery) throws SQLException { + return this.getHibernateSession(context).createQuery(criteriaQuery); + } + } diff --git a/dspace-api/src/main/java/org/dspace/core/AbstractHibernateDSODAO.java b/dspace-api/src/main/java/org/dspace/core/AbstractHibernateDSODAO.java index e9c6b95b7f..d72a4d6190 100644 --- a/dspace-api/src/main/java/org/dspace/core/AbstractHibernateDSODAO.java +++ b/dspace-api/src/main/java/org/dspace/core/AbstractHibernateDSODAO.java @@ -10,10 +10,10 @@ package org.dspace.core; import java.sql.SQLException; import java.util.Collection; 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.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.ListUtils; import org.apache.commons.lang3.StringUtils; diff --git a/dspace-api/src/main/java/org/dspace/core/Constants.java b/dspace-api/src/main/java/org/dspace/core/Constants.java index f730ef6545..98bb1b7731 100644 --- a/dspace-api/src/main/java/org/dspace/core/Constants.java +++ b/dspace-api/src/main/java/org/dspace/core/Constants.java @@ -55,11 +55,16 @@ public class Constants { */ public static final int EPERSON = 7; + /** + * Type of LDN MESSAGE objects + */ + public static final int LDN_MESSAGE = 8; + /** * lets you look up type names from the type IDs */ public static final String[] typeText = { "BITSTREAM", "BUNDLE", "ITEM", "COLLECTION", "COMMUNITY", "SITE", "GROUP", - "EPERSON"}; + "EPERSON", "LDN_MESSAGE"}; /** * Special Bundle and Bitstream Names: diff --git a/dspace-api/src/main/java/org/dspace/core/Context.java b/dspace-api/src/main/java/org/dspace/core/Context.java index 02a3fee09f..877b7a0055 100644 --- a/dspace-api/src/main/java/org/dspace/core/Context.java +++ b/dspace-api/src/main/java/org/dspace/core/Context.java @@ -713,7 +713,7 @@ public class Context implements AutoCloseable { public void switchContextUser(EPerson newUser) { if (currentUserPreviousState != null) { throw new IllegalStateException( - "A previous user is already set, you can only switch back and foreward one time"); + "A previous user is already set, you can only switch back and forward one time"); } currentUserPreviousState = currentUser; diff --git a/dspace-api/src/main/java/org/dspace/core/Email.java b/dspace-api/src/main/java/org/dspace/core/Email.java index c157d16d49..bb434c07cb 100644 --- a/dspace-api/src/main/java/org/dspace/core/Email.java +++ b/dspace-api/src/main/java/org/dspace/core/Email.java @@ -23,23 +23,23 @@ import java.util.Date; import java.util.Enumeration; import java.util.List; import java.util.Properties; -import javax.activation.DataHandler; -import javax.activation.DataSource; -import javax.activation.FileDataSource; -import javax.mail.Address; -import javax.mail.BodyPart; -import javax.mail.Message; -import javax.mail.MessagingException; -import javax.mail.Multipart; -import javax.mail.Session; -import javax.mail.Transport; -import javax.mail.internet.ContentType; -import javax.mail.internet.InternetAddress; -import javax.mail.internet.MimeBodyPart; -import javax.mail.internet.MimeMessage; -import javax.mail.internet.MimeMultipart; -import javax.mail.internet.ParseException; +import jakarta.activation.DataHandler; +import jakarta.activation.DataSource; +import jakarta.activation.FileDataSource; +import jakarta.mail.Address; +import jakarta.mail.BodyPart; +import jakarta.mail.Message; +import jakarta.mail.MessagingException; +import jakarta.mail.Multipart; +import jakarta.mail.Session; +import jakarta.mail.Transport; +import jakarta.mail.internet.ContentType; +import jakarta.mail.internet.InternetAddress; +import jakarta.mail.internet.MimeBodyPart; +import jakarta.mail.internet.MimeMessage; +import jakarta.mail.internet.MimeMultipart; +import jakarta.mail.internet.ParseException; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.velocity.Template; diff --git a/dspace-api/src/main/java/org/dspace/core/GenericDAO.java b/dspace-api/src/main/java/org/dspace/core/GenericDAO.java index a04a0ccbdc..9835e18ad3 100644 --- a/dspace-api/src/main/java/org/dspace/core/GenericDAO.java +++ b/dspace-api/src/main/java/org/dspace/core/GenericDAO.java @@ -102,6 +102,17 @@ public interface GenericDAO { */ public T findByID(Context context, Class clazz, UUID id) throws SQLException; + /** + * Fetch the entity identified by its String primary key. + * + * @param context current DSpace context. + * @param clazz class of entity to be found. + * @param id primary key of the database record. + * @return the found entity. + * @throws SQLException + */ + public T findByID(Context context, Class clazz, String id) throws SQLException; + /** * Execute a JPQL query and return a collection of results. * diff --git a/dspace-api/src/main/java/org/dspace/core/HibernateDBConnection.java b/dspace-api/src/main/java/org/dspace/core/HibernateDBConnection.java index b371af80ee..a867849077 100644 --- a/dspace-api/src/main/java/org/dspace/core/HibernateDBConnection.java +++ b/dspace-api/src/main/java/org/dspace/core/HibernateDBConnection.java @@ -27,7 +27,6 @@ import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.Transaction; import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.proxy.HibernateProxyHelper; import org.hibernate.resource.transaction.spi.TransactionStatus; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; diff --git a/dspace-api/src/main/java/org/dspace/core/HibernateProxyHelper.java b/dspace-api/src/main/java/org/dspace/core/HibernateProxyHelper.java new file mode 100644 index 0000000000..f9bdd1c014 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/core/HibernateProxyHelper.java @@ -0,0 +1,39 @@ +/** + * 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.core; + +import org.hibernate.proxy.HibernateProxy; +import org.hibernate.proxy.LazyInitializer; + +/** + * Utility methods for working with Hibernate proxies. + * This class existed in Hibernate 5 but was removed from v6. + * https://github.com/hibernate/hibernate-orm/blob/5.6/hibernate-core/src/main/java/org/hibernate/proxy/HibernateProxyHelper.java + * We've copied it into DSpace to utilize the below utility method. + */ +public final class HibernateProxyHelper { + + /** + * Get the class of an instance or the underlying class + * of a proxy (without initializing the proxy!). It is + * almost always better to use the entity name! + */ + public static Class getClassWithoutInitializingProxy(Object object) { + if (object instanceof HibernateProxy) { + HibernateProxy proxy = (HibernateProxy) object; + LazyInitializer li = proxy.getHibernateLazyInitializer(); + return li.getPersistentClass(); + } else { + return object.getClass(); + } + } + + private HibernateProxyHelper() { + //can't instantiate + } +} diff --git a/dspace-api/src/main/java/org/dspace/core/I18nUtil.java b/dspace-api/src/main/java/org/dspace/core/I18nUtil.java index 76f6a196fc..8a1a26d9c7 100644 --- a/dspace-api/src/main/java/org/dspace/core/I18nUtil.java +++ b/dspace-api/src/main/java/org/dspace/core/I18nUtil.java @@ -377,6 +377,22 @@ public class I18nUtil { return templateName; } + /** + * Get the appropriate localized version of a ldn template according to language settings + * + * @param locale Locale for this request + * @param name String - base name of the ldn template + * @return templateName + * String - localized filename of a ldn template + */ + public static String getLDNFilename(Locale locale, String name) { + String templateFile = + DSpaceServicesFactory.getInstance().getConfigurationService().getProperty("dspace.dir") + + File.separator + "config" + File.separator + "ldn" + File.separator + name; + + return getFilename(locale, templateFile, ""); + } + /** * Creates array of Locales from text list of locale-specifications. * Used to parse lists in DSpace configuration properties. diff --git a/dspace-api/src/main/java/org/dspace/core/LDN.java b/dspace-api/src/main/java/org/dspace/core/LDN.java new file mode 100644 index 0000000000..8ae5cddf5b --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/core/LDN.java @@ -0,0 +1,212 @@ +/** + * 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.core; + +import static org.apache.commons.lang3.StringUtils.EMPTY; + +import java.io.BufferedReader; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Properties; + +import jakarta.mail.MessagingException; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.velocity.Template; +import org.apache.velocity.VelocityContext; +import org.apache.velocity.app.Velocity; +import org.apache.velocity.app.VelocityEngine; +import org.apache.velocity.exception.MethodInvocationException; +import org.apache.velocity.exception.ParseErrorException; +import org.apache.velocity.exception.ResourceNotFoundException; +import org.apache.velocity.runtime.resource.loader.StringResourceLoader; +import org.apache.velocity.runtime.resource.util.StringResourceRepository; +import org.dspace.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; + +/** + * Class representing an LDN message json + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + */ +public class LDN { + /** + * The content of the ldn message + */ + private String content; + private String contentName; + + /** + * The arguments to fill out + */ + private final List arguments; + + private static final Logger LOG = LogManager.getLogger(); + + /** Velocity template settings. */ + private static final String RESOURCE_REPOSITORY_NAME = "LDN"; + private static final Properties VELOCITY_PROPERTIES = new Properties(); + static { + VELOCITY_PROPERTIES.put(Velocity.RESOURCE_LOADERS, "string"); + VELOCITY_PROPERTIES.put("resource.loader.string.description", + "Velocity StringResource loader"); + VELOCITY_PROPERTIES.put("resource.loader.string.class", + StringResourceLoader.class.getName()); + VELOCITY_PROPERTIES.put("resource.loader.string.repository.name", + RESOURCE_REPOSITORY_NAME); + VELOCITY_PROPERTIES.put("resource.loader.string.repository.static", + "false"); + } + + /** Velocity template for the message*/ + private Template template; + + /** + * Create a new ldn message. + */ + public LDN() { + arguments = new ArrayList<>(20); + template = null; + content = EMPTY; + } + + /** + * Set the content of the message. Setting this also "resets" the message + * formatting - addArgument will start over. Comments and any + * "Subject:" line must be stripped. + * + * @param name a name for this message + * @param cnt the content of the message + */ + public void setContent(String name, String cnt) { + content = cnt; + contentName = name; + arguments.clear(); + } + + /** + * Fill out the next argument in the template + * + * @param arg the value for the next argument + */ + public void addArgument(Object arg) { + arguments.add(arg); + } + + /** + * Generates the ldn message. + * + * @throws MessagingException if there was a problem sending the mail. + * @throws IOException if IO error + */ + public String generateLDNMessage() { + ConfigurationService config + = DSpaceServicesFactory.getInstance().getConfigurationService(); + + VelocityEngine templateEngine = new VelocityEngine(); + templateEngine.init(VELOCITY_PROPERTIES); + + VelocityContext vctx = new VelocityContext(); + vctx.put("config", new LDN.UnmodifiableConfigurationService(config)); + vctx.put("params", Collections.unmodifiableList(arguments)); + + if (null == template) { + if (StringUtils.isBlank(content)) { + LOG.error("template has no content"); + throw new RuntimeException("template has no content"); + } + // No template, so use a String of content. + StringResourceRepository repo = (StringResourceRepository) + templateEngine.getApplicationAttribute(RESOURCE_REPOSITORY_NAME); + repo.putStringResource(contentName, content); + // Turn content into a template. + template = templateEngine.getTemplate(contentName); + } + + StringWriter writer = new StringWriter(); + try { + template.merge(vctx, writer); + } catch (MethodInvocationException | ParseErrorException + | ResourceNotFoundException ex) { + LOG.error("Template not merged: {}", ex.getMessage()); + throw new RuntimeException("Template not merged", ex); + } + return writer.toString(); + } + + /** + * Get the VTL template for a ldn message. The message is suitable + * for inserting values using Apache Velocity. + * + * @param ldnMessageFile + * full name for the ldn template, for example "/dspace/config/ldn/request-review". + * + * @return the ldn object, configured with body. + * + * @throws IOException if IO error, + * if the template couldn't be found, or there was some other + * error reading the template + */ + public static LDN getLDNMessage(String ldnMessageFile) + throws IOException { + StringBuilder contentBuffer = new StringBuilder(); + try ( + InputStream is = new FileInputStream(ldnMessageFile); + InputStreamReader ir = new InputStreamReader(is, "UTF-8"); + BufferedReader reader = new BufferedReader(ir); + ) { + boolean more = true; + while (more) { + String line = reader.readLine(); + if (line == null) { + more = false; + } else { + contentBuffer.append(line); + contentBuffer.append("\n"); + } + } + } + LDN ldn = new LDN(); + ldn.setContent(ldnMessageFile, contentBuffer.toString()); + return ldn; + } + + /** + * Wrap ConfigurationService to prevent templates from modifying + * the configuration. + */ + public static class UnmodifiableConfigurationService { + private final ConfigurationService configurationService; + + /** + * Swallow an instance of ConfigurationService. + * + * @param cs the real instance, to be wrapped. + */ + public UnmodifiableConfigurationService(ConfigurationService cs) { + configurationService = cs; + } + + /** + * Look up a key in the actual ConfigurationService. + * + * @param key to be looked up in the DSpace configuration. + * @return whatever value ConfigurationService associates with {@code key}. + */ + public String get(String key) { + return configurationService.getProperty(key); + } + } +} diff --git a/dspace-api/src/main/java/org/dspace/core/LicenseServiceImpl.java b/dspace-api/src/main/java/org/dspace/core/LicenseServiceImpl.java index d895f9a764..766e72e941 100644 --- a/dspace-api/src/main/java/org/dspace/core/LicenseServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/core/LicenseServiceImpl.java @@ -17,14 +17,14 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.PrintWriter; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.core.service.LicenseService; import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.services.model.Request; import org.dspace.web.ContextUtil; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * Encapsulate the deposit license. @@ -32,7 +32,7 @@ import org.slf4j.LoggerFactory; * @author mhwood */ public class LicenseServiceImpl implements LicenseService { - private final Logger log = LoggerFactory.getLogger(LicenseServiceImpl.class); + private final Logger log = LogManager.getLogger(); /** * The default license @@ -53,7 +53,7 @@ public class LicenseServiceImpl implements LicenseService { out.print(newLicense); out.close(); } catch (IOException e) { - log.warn("license_write: " + e.getLocalizedMessage()); + log.warn("license_write: {}", e::getLocalizedMessage); } license = newLicense; } @@ -140,7 +140,7 @@ public class LicenseServiceImpl implements LicenseService { br.close(); } catch (IOException e) { - log.error("Can't load license: " + licenseFile.toString(), e); + log.error("Can't load license {}: ", licenseFile.toString(), e); // FIXME: Maybe something more graceful here, but with the // configuration we can't do anything diff --git a/dspace-api/src/main/java/org/dspace/core/LogHelper.java b/dspace-api/src/main/java/org/dspace/core/LogHelper.java index 00cc0f2766..5c3f345dfb 100644 --- a/dspace-api/src/main/java/org/dspace/core/LogHelper.java +++ b/dspace-api/src/main/java/org/dspace/core/LogHelper.java @@ -50,7 +50,7 @@ public class LogHelper { StringBuilder result = new StringBuilder(); - // Escape everthing but the extra context info because for some crazy reason two fields + // Escape everything but the extra context info because for some crazy reason two fields // are generated inside this entry one for the session id, and another for the ip // address. Everything else should be escaped. result.append(escapeLogField(email)).append(":").append(contextExtraInfo).append(":") diff --git a/dspace-api/src/main/java/org/dspace/core/NewsServiceImpl.java b/dspace-api/src/main/java/org/dspace/core/NewsServiceImpl.java index d2bdf787f8..59b4c43fa8 100644 --- a/dspace-api/src/main/java/org/dspace/core/NewsServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/core/NewsServiceImpl.java @@ -19,11 +19,11 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.core.service.NewsService; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; /** @@ -32,7 +32,7 @@ import org.springframework.beans.factory.annotation.Autowired; * @author mhwood */ public class NewsServiceImpl implements NewsService { - private final Logger log = LoggerFactory.getLogger(NewsServiceImpl.class); + private final Logger log = LogManager.getLogger(); private List acceptableFilenames; @@ -94,7 +94,7 @@ public class NewsServiceImpl implements NewsService { ir.close(); fir.close(); } catch (IOException e) { - log.warn("news_read: " + e.getLocalizedMessage()); + log.warn("news_read: {}", e::getLocalizedMessage); } return text.toString(); @@ -117,7 +117,7 @@ public class NewsServiceImpl implements NewsService { out.print(news); out.close(); } catch (IOException e) { - log.warn("news_write: " + e.getLocalizedMessage()); + log.warn("news_write: {}", e::getLocalizedMessage); } return news; diff --git a/dspace-api/src/main/java/org/dspace/core/Utils.java b/dspace-api/src/main/java/org/dspace/core/Utils.java index ea9ed57eca..b5423decf2 100644 --- a/dspace-api/src/main/java/org/dspace/core/Utils.java +++ b/dspace-api/src/main/java/org/dspace/core/Utils.java @@ -95,7 +95,7 @@ public final class Utils { private static final SimpleDateFormat outFmtSecond = new SimpleDateFormat("yyyy'-'MM'-'dd'T'HH':'mm':'ssZ"); - // output format with millsecond precision + // output format with millisecond precision private static final SimpleDateFormat outFmtMillisec = new SimpleDateFormat("yyyy'-'MM'-'dd'T'HH':'mm':'ss.SSSZ"); diff --git a/dspace-api/src/main/java/org/dspace/correctiontype/CorrectionType.java b/dspace-api/src/main/java/org/dspace/correctiontype/CorrectionType.java new file mode 100644 index 0000000000..bc5abaef4e --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/correctiontype/CorrectionType.java @@ -0,0 +1,81 @@ +/** + * 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.correctiontype; + +import java.sql.SQLException; + +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.Item; +import org.dspace.content.QAEvent; +import org.dspace.core.Context; +import org.dspace.qaevent.service.dto.QAMessageDTO; + +/** + * Interface class that model the CorrectionType. + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.com) + */ +public interface CorrectionType { + + /** + * Retrieves the unique identifier associated to the CorrectionType. + */ + public String getId(); + + /** + * Retrieves the topic associated with the to the CorrectionType. + */ + public String getTopic(); + + /** + * Checks whether the CorrectionType required related item. + */ + public boolean isRequiredRelatedItem(); + + /** + * Checks whether target item is allowed for current CorrectionType + * + * @param context Current DSpace session + * @param targetItem Target item + * @throws AuthorizeException if authorize error + * @throws SQLException if there's a database problem + */ + public boolean isAllowed(Context context, Item targetItem) throws AuthorizeException, SQLException; + + /** + * Checks whether target item and related item are allowed for current CorrectionType + * + * @param context Current DSpace session + * @param targetItem Target item + * @param relatedItem Related item + * @throws AuthorizeException if authorize error + * @throws SQLException if there's a database problem + */ + public boolean isAllowed(Context context, Item targetItem, Item relatedItem) throws AuthorizeException,SQLException; + + /** + * Creates a QAEvent for a specific target item. + * + * @param context Current DSpace session + * @param targetItem Target item + * @param reason Reason + * @return QAEvent + */ + public QAEvent createCorrection(Context context, Item targetItem, QAMessageDTO reason); + + /** + * Creates a QAEvent for a target item and related item. + * @param context Current DSpace session + * @param targetItem Target item + * @param relatedItem Related item + * @param reason Reason + * @return QAEvent + */ + public QAEvent createCorrection(Context context, Item targetItem, Item relatedItem, QAMessageDTO reason); + +} diff --git a/dspace-api/src/main/java/org/dspace/correctiontype/ReinstateCorrectionType.java b/dspace-api/src/main/java/org/dspace/correctiontype/ReinstateCorrectionType.java new file mode 100644 index 0000000000..847236d110 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/correctiontype/ReinstateCorrectionType.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.correctiontype; + +import static org.dspace.content.QAEvent.DSPACE_USERS_SOURCE; +import static org.dspace.correctiontype.WithdrawnCorrectionType.WITHDRAWAL_REINSTATE_GROUP; + +import java.sql.SQLException; +import java.util.Date; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.apache.commons.lang3.StringUtils; +import org.dspace.authorize.AuthorizeException; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.content.Item; +import org.dspace.content.QAEvent; +import org.dspace.core.Context; +import org.dspace.eperson.Group; +import org.dspace.eperson.service.GroupService; +import org.dspace.qaevent.service.QAEventService; +import org.dspace.qaevent.service.dto.CorrectionTypeMessageDTO; +import org.dspace.qaevent.service.dto.QAMessageDTO; +import org.dspace.services.ConfigurationService; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Implementation class for {@link CorrectionType} + * that will reinstate target item if it's withdrawn. + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com) + */ +public class ReinstateCorrectionType implements CorrectionType, InitializingBean { + + private String id; + private String topic; + private String creationForm; + + @Autowired + private GroupService groupService; + @Autowired + private QAEventService qaEventService; + @Autowired + private AuthorizeService authorizeService; + @Autowired + private ConfigurationService configurationService; + + @Override + public boolean isAllowed(Context context, Item targetItem) throws SQLException { + if (!targetItem.isWithdrawn()) { + return false; + } + boolean isAdmin = authorizeService.isAdmin(context); + if (!currentUserIsMemberOfwithdrawalReinstateGroup(context) && !isAdmin) { + return false; + } + long tot = qaEventService.countSourcesByTarget(context, targetItem.getID()); + return tot == 0; + } + + private boolean currentUserIsMemberOfwithdrawalReinstateGroup(Context context) throws SQLException { + String groupName = configurationService.getProperty(WITHDRAWAL_REINSTATE_GROUP); + if (StringUtils.isBlank(groupName)) { + return false; + } + Group withdrawalReinstateGroup = groupService.findByName(context, groupName); + return withdrawalReinstateGroup != null && groupService.isMember(context, withdrawalReinstateGroup); + } + + @Override + public boolean isAllowed(Context context, Item targetItem, Item relatedItem) throws AuthorizeException, + SQLException { + return isAllowed(context, targetItem); + } + + @Override + public QAEvent createCorrection(Context context, Item targetItem, QAMessageDTO reason) { + ObjectNode reasonJson = createReasonJson(reason); + QAEvent qaEvent = new QAEvent(DSPACE_USERS_SOURCE, + context.getCurrentUser().getID().toString(), + targetItem.getID().toString(), + targetItem.getName(), + this.getTopic(), + 1.0, + reasonJson.toString(), + new Date() + ); + + qaEventService.store(context, qaEvent); + return qaEvent; + } + + private ObjectNode createReasonJson(QAMessageDTO reason) { + CorrectionTypeMessageDTO mesasge = (CorrectionTypeMessageDTO) reason; + ObjectNode jsonNode = new ObjectMapper().createObjectNode(); + jsonNode.put("reason", mesasge.getReason()); + return jsonNode; + } + + @Override + public QAEvent createCorrection(Context context, Item targetItem, Item relatedItem, QAMessageDTO reason) { + return this.createCorrection(context, targetItem, reason); + } + + @Override + public boolean isRequiredRelatedItem() { + return false; + } + + @Override + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + @Override + public String getTopic() { + return topic; + } + + public void setTopic(String topic) { + this.topic = topic; + } + + @Override + public void afterPropertiesSet() throws Exception {} + + public void setCreationForm(String creationForm) { + this.creationForm = creationForm; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/correctiontype/WithdrawnCorrectionType.java b/dspace-api/src/main/java/org/dspace/correctiontype/WithdrawnCorrectionType.java new file mode 100644 index 0000000000..e3d074b1d3 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/correctiontype/WithdrawnCorrectionType.java @@ -0,0 +1,149 @@ +/** + * 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.correctiontype; + +import static org.dspace.content.QAEvent.DSPACE_USERS_SOURCE; +import static org.dspace.core.Constants.READ; + +import java.sql.SQLException; +import java.util.Date; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.apache.commons.lang3.StringUtils; +import org.dspace.authorize.AuthorizeException; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.content.Item; +import org.dspace.content.QAEvent; +import org.dspace.core.Context; +import org.dspace.eperson.Group; +import org.dspace.eperson.service.GroupService; +import org.dspace.qaevent.service.QAEventService; +import org.dspace.qaevent.service.dto.CorrectionTypeMessageDTO; +import org.dspace.qaevent.service.dto.QAMessageDTO; +import org.dspace.services.ConfigurationService; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Implementation class for {@link CorrectionType} + * that will withdrawn target item if it archived and wasn't withdrawn already. + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.com) + */ +public class WithdrawnCorrectionType implements CorrectionType, InitializingBean { + + public static final String WITHDRAWAL_REINSTATE_GROUP = "qaevents.withdraw-reinstate.group"; + + private String id; + private String topic; + private String creationForm; + + @Autowired + private GroupService groupService; + @Autowired + private QAEventService qaEventService; + @Autowired + private AuthorizeService authorizeService; + @Autowired + private ConfigurationService configurationService; + + + @Override + public boolean isAllowed(Context context, Item targetItem) throws SQLException { + if (targetItem.isWithdrawn() || !targetItem.isArchived()) { + return false; + } + try { + authorizeService.authorizeAction(context, targetItem, READ); + } catch (AuthorizeException e) { + return false; + } + boolean isAdmin = authorizeService.isAdmin(context); + if (!currentUserIsMemberOfwithdrawalReinstateGroup(context) && !isAdmin) { + return false; + } + long tot = qaEventService.countSourcesByTarget(context, targetItem.getID()); + return tot == 0; + } + + private boolean currentUserIsMemberOfwithdrawalReinstateGroup(Context context) throws SQLException { + String groupName = configurationService.getProperty(WITHDRAWAL_REINSTATE_GROUP); + if (StringUtils.isBlank(groupName)) { + return false; + } + Group withdrawalReinstateGroup = groupService.findByName(context, groupName); + return withdrawalReinstateGroup != null && groupService.isMember(context, withdrawalReinstateGroup); + } + + @Override + public QAEvent createCorrection(Context context, Item targetItem, QAMessageDTO reason) { + ObjectNode reasonJson = createReasonJson(reason); + QAEvent qaEvent = new QAEvent(DSPACE_USERS_SOURCE, + context.getCurrentUser().getID().toString(), + targetItem.getID().toString(), + targetItem.getName(), + this.getTopic(), + 1.0, + reasonJson.toString(), + new Date() + ); + + qaEventService.store(context, qaEvent); + return qaEvent; + } + + private ObjectNode createReasonJson(QAMessageDTO reason) { + CorrectionTypeMessageDTO mesasge = (CorrectionTypeMessageDTO) reason; + ObjectNode jsonNode = new ObjectMapper().createObjectNode(); + jsonNode.put("reason", mesasge.getReason()); + return jsonNode; + } + + @Override + public boolean isAllowed(Context context, Item targetItem, Item relatedItem) + throws AuthorizeException, SQLException { + return isAllowed(context, targetItem); + } + + @Override + public QAEvent createCorrection(Context context, Item targetItem, Item relatedItem, QAMessageDTO reason) { + return createCorrection(context, targetItem, reason); + } + + @Override + public boolean isRequiredRelatedItem() { + return false; + } + + @Override + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + @Override + public String getTopic() { + return topic; + } + + public void setTopic(String topic) { + this.topic = topic; + } + + @Override + public void afterPropertiesSet() throws Exception {} + + public void setCreationForm(String creationForm) { + this.creationForm = creationForm; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/correctiontype/service/CorrectionTypeService.java b/dspace-api/src/main/java/org/dspace/correctiontype/service/CorrectionTypeService.java new file mode 100644 index 0000000000..e76e1f7ec1 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/correctiontype/service/CorrectionTypeService.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.correctiontype.service; + +import java.sql.SQLException; +import java.util.List; + +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.Item; +import org.dspace.core.Context; +import org.dspace.correctiontype.CorrectionType; + +/** + * Service interface class for the CorrectionType object. + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + */ +public interface CorrectionTypeService { + + /** + * Retrieves a CorrectionType object from the system based on a unique identifier. + * + * @param id The unique identifier of the CorrectionType object to be retrieved. + * @return The CorrectionType object corresponding to the provided identifier, + * or null if no object is found. + */ + public CorrectionType findOne(String id); + + /** + * Retrieves a list of all CorrectionType objects available in the system. + * + * @return Returns a List containing all CorrectionType objects in the system. + */ + public List findAll(); + + /** + * Retrieves a list of CorrectionType objects related to the provided Item. + * + * @param context Current DSpace session. + * @param item Target item + * @throws AuthorizeException If authorize error + * @throws SQLException If a database error occurs during the operation. + */ + public List findByItem(Context context, Item item) throws AuthorizeException, SQLException; + + /** + * Retrieves a CorrectionType object associated with a specific topic. + * + * @param topic The topic for which the CorrectionType object is to be retrieved. + */ + public CorrectionType findByTopic(String topic); + +} diff --git a/dspace-api/src/main/java/org/dspace/correctiontype/service/impl/CorrectionTypeServiceImpl.java b/dspace-api/src/main/java/org/dspace/correctiontype/service/impl/CorrectionTypeServiceImpl.java new file mode 100644 index 0000000000..e64120c46a --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/correctiontype/service/impl/CorrectionTypeServiceImpl.java @@ -0,0 +1,64 @@ +/** + * 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.correctiontype.service.impl; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.collections4.CollectionUtils; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.Item; +import org.dspace.core.Context; +import org.dspace.correctiontype.CorrectionType; +import org.dspace.correctiontype.service.CorrectionTypeService; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Service implementation class for the CorrectionType object. + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + */ +public class CorrectionTypeServiceImpl implements CorrectionTypeService { + + @Autowired + private List correctionTypes; + + @Override + public CorrectionType findOne(String id) { + return findAll().stream() + .filter(correctionType -> correctionType.getId().equals(id)) + .findFirst() + .orElse(null); + } + + @Override + public List findAll() { + return CollectionUtils.isNotEmpty(correctionTypes) ? correctionTypes : List.of(); + } + + @Override + public List findByItem(Context context, Item item) throws AuthorizeException, SQLException { + List correctionTypes = new ArrayList<>(); + for (CorrectionType correctionType : findAll()) { + if (correctionType.isAllowed(context, item)) { + correctionTypes.add(correctionType); + } + } + return correctionTypes; + } + + @Override + public CorrectionType findByTopic(String topic) { + return findAll().stream() + .filter(correctionType -> correctionType.getTopic().equals(topic)) + .findFirst() + .orElse(null); + } + +} diff --git a/dspace-api/src/main/java/org/dspace/ctask/general/BasicLinkChecker.java b/dspace-api/src/main/java/org/dspace/ctask/general/BasicLinkChecker.java index a302159ea9..9bd08bffdb 100644 --- a/dspace-api/src/main/java/org/dspace/ctask/general/BasicLinkChecker.java +++ b/dspace-api/src/main/java/org/dspace/ctask/general/BasicLinkChecker.java @@ -161,7 +161,7 @@ public class BasicLinkChecker extends AbstractCurationTask { } /** - * Internal utitity method to get a description of the handle + * Internal utility method to get a description of the handle * * @param item The item to get a description of * @return The handle, or in workflow diff --git a/dspace-api/src/main/java/org/dspace/ctask/general/CitationPage.java b/dspace-api/src/main/java/org/dspace/ctask/general/CitationPage.java index fa630029b8..2455ca0953 100644 --- a/dspace-api/src/main/java/org/dspace/ctask/general/CitationPage.java +++ b/dspace-api/src/main/java/org/dspace/ctask/general/CitationPage.java @@ -114,7 +114,7 @@ public class CitationPage extends AbstractCurationTask { // don't inherit now otherwise they will be copied over the moved bitstreams resourcePolicyService.removeAllPolicies(Curator.curationContext(), dBundle); } catch (AuthorizeException e) { - log.error("User not authroized to create bundle on item \"{}\": {}", + log.error("User not authorized to create bundle on item \"{}\": {}", item::getName, e::getMessage); return; } @@ -144,7 +144,7 @@ public class CitationPage extends AbstractCurationTask { // don't inherit now otherwise they will be copied over the moved bitstreams resourcePolicyService.removeAllPolicies(Curator.curationContext(), pBundle); } catch (AuthorizeException e) { - log.error("User not authroized to create bundle on item \"" + log.error("User not authorized to create bundle on item \"" + item.getName() + "\": " + e.getMessage()); } bundles = itemService.getBundles(item, "ORIGINAL"); @@ -173,7 +173,7 @@ public class CitationPage extends AbstractCurationTask { InputStream citedInputStream = new ByteArrayInputStream( citationDocument.makeCitedDocument(Curator.curationContext(), bitstream).getLeft()); - //Add the cited document to the approiate bundle + //Add the cited document to the appropriate bundle this.addCitedPageToItem(citedInputStream, bundle, pBundle, dBundle, item, bitstream); // now set the policies of the preservation and display bundle diff --git a/dspace-api/src/main/java/org/dspace/ctask/general/ClamScan.java b/dspace-api/src/main/java/org/dspace/ctask/general/ClamScan.java index d1fa70565d..47fa6ee645 100644 --- a/dspace-api/src/main/java/org/dspace/ctask/general/ClamScan.java +++ b/dspace-api/src/main/java/org/dspace/ctask/general/ClamScan.java @@ -19,6 +19,8 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.List; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Bitstream; import org.dspace.content.Bundle; @@ -29,8 +31,6 @@ import org.dspace.content.service.BitstreamService; import org.dspace.curate.AbstractCurationTask; import org.dspace.curate.Curator; import org.dspace.curate.Suspendable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * ClamScan.java @@ -58,7 +58,7 @@ public class ClamScan extends AbstractCurationTask { protected final String SCAN_FAIL_MESSAGE = "Error encountered using virus service - check setup"; protected final String NEW_ITEM_HANDLE = "in workflow"; - private static final Logger log = LoggerFactory.getLogger(ClamScan.class); + private static final Logger log = LogManager.getLogger(); protected String host = null; protected int port = 0; @@ -100,7 +100,7 @@ public class ClamScan extends AbstractCurationTask { try { Bundle bundle = itemService.getBundles(item, "ORIGINAL").get(0); - results = new ArrayList(); + results = new ArrayList<>(); for (Bitstream bitstream : bundle.getBitstreams()) { InputStream inputstream = bitstreamService.retrieve(Curator.curationContext(), bitstream); logDebugMessage("Scanning " + bitstream.getName() + " . . . "); @@ -157,7 +157,7 @@ public class ClamScan extends AbstractCurationTask { try { socket.setSoTimeout(timeout); } catch (SocketException e) { - log.error("Could not set socket timeout . . . " + timeout + "ms", e); + log.error("Could not set socket timeout . . . {}ms", timeout, e); throw (new IOException(e)); } try { @@ -293,8 +293,6 @@ public class ClamScan extends AbstractCurationTask { protected void logDebugMessage(String message) { - if (log.isDebugEnabled()) { - log.debug(message); - } + log.debug(message); } } diff --git a/dspace-api/src/main/java/org/dspace/ctask/test/WorkflowReportTest.java b/dspace-api/src/main/java/org/dspace/ctask/test/WorkflowReportTest.java index 21ffdd0260..7b47a9fd90 100644 --- a/dspace-api/src/main/java/org/dspace/ctask/test/WorkflowReportTest.java +++ b/dspace-api/src/main/java/org/dspace/ctask/test/WorkflowReportTest.java @@ -10,11 +10,11 @@ package org.dspace.ctask.test; import java.io.IOException; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.content.DSpaceObject; import org.dspace.curate.AbstractCurationTask; import org.dspace.curate.Curator; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * Curation task which simply reports its invocation without changing anything. @@ -24,13 +24,13 @@ import org.slf4j.LoggerFactory; */ public class WorkflowReportTest extends AbstractCurationTask { - private static final Logger LOG = LoggerFactory.getLogger(WorkflowReportTest.class); + private static final Logger LOG = LogManager.getLogger(); @Override public int perform(DSpaceObject dso) throws IOException { LOG.info("Class {} as task {} received 'perform' for object {}", - WorkflowReportTest.class.getSimpleName(), taskId, dso); + WorkflowReportTest.class::getSimpleName, () -> taskId, () -> dso); curator.report(String.format( "Class %s as task %s received 'perform' for object %s%n", WorkflowReportTest.class.getSimpleName(), taskId, dso)); diff --git a/dspace-api/src/main/java/org/dspace/ctask/testing/PropertyParameterTestingTask.java b/dspace-api/src/main/java/org/dspace/ctask/testing/PropertyParameterTestingTask.java index 279204cf57..14bd420494 100644 --- a/dspace-api/src/main/java/org/dspace/ctask/testing/PropertyParameterTestingTask.java +++ b/dspace-api/src/main/java/org/dspace/ctask/testing/PropertyParameterTestingTask.java @@ -10,12 +10,12 @@ package org.dspace.ctask.testing; import java.io.IOException; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.content.DSpaceObject; import org.dspace.core.Context; import org.dspace.curate.AbstractCurationTask; import org.dspace.curate.Curator; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * Logs what it was asked to do, samples run parameters and task @@ -32,8 +32,7 @@ import org.slf4j.LoggerFactory; */ public class PropertyParameterTestingTask extends AbstractCurationTask { - private static final Logger LOG - = LoggerFactory.getLogger(PropertyParameterTestingTask.class); + private static final Logger LOG = LogManager.getLogger(); @Override public void init(Curator curator, String taskId) diff --git a/dspace-api/src/main/java/org/dspace/curate/Curation.java b/dspace-api/src/main/java/org/dspace/curate/Curation.java index 4d70286e79..ece1b7738a 100644 --- a/dspace-api/src/main/java/org/dspace/curate/Curation.java +++ b/dspace-api/src/main/java/org/dspace/curate/Curation.java @@ -165,7 +165,7 @@ public class Curation extends DSpaceRunnable { * End of curation script; logs script time if -v verbose is set * * @param timeRun Time script was started - * @throws SQLException If DSpace contextx can't complete + * @throws SQLException If DSpace context can't complete */ private void endScript(long timeRun) throws SQLException { context.complete(); @@ -300,9 +300,17 @@ public class Curation extends DSpaceRunnable { // scope if (this.commandLine.getOptionValue('s') != null) { this.scope = this.commandLine.getOptionValue('s'); - if (this.scope != null && Curator.TxScope.valueOf(this.scope.toUpperCase()) == null) { - this.handler.logError("Bad transaction scope '" + this.scope + "': only 'object', 'curation' or " + - "'open' recognized"); + boolean knownScope; + try { + Curator.TxScope.valueOf(this.scope.toUpperCase()); + knownScope = true; + } catch (IllegalArgumentException | NullPointerException e) { + knownScope = false; + } + if (!knownScope) { + this.handler.logError("Bad transaction scope '" + + this.scope + + "': only 'object', 'curation' or 'open' recognized"); throw new IllegalArgumentException( "Bad transaction scope '" + this.scope + "': only 'object', 'curation' or " + "'open' recognized"); diff --git a/dspace-api/src/main/java/org/dspace/curate/LogReporter.java b/dspace-api/src/main/java/org/dspace/curate/LogReporter.java index bd3ee3cffb..bdf4443576 100644 --- a/dspace-api/src/main/java/org/dspace/curate/LogReporter.java +++ b/dspace-api/src/main/java/org/dspace/curate/LogReporter.java @@ -10,8 +10,8 @@ package org.dspace.curate; import java.io.IOException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; /** * Write curation report records through the logging framework. @@ -22,7 +22,7 @@ import org.slf4j.LoggerFactory; */ public class LogReporter implements Reporter { - private static final Logger LOG = LoggerFactory.getLogger("curation"); + private static final Logger LOG = LogManager.getLogger("curation"); private final StringBuilder buffer = new StringBuilder(); @Override @@ -31,7 +31,7 @@ public class LogReporter for (int pos = 0; pos < cs.length(); pos++) { char c = cs.charAt(pos); if (c == '\n') { - LOG.info(buffer.toString()); + LOG.info(buffer::toString); buffer.delete(0, buffer.length()); // Clear the buffer } else { buffer.append(c); @@ -56,7 +56,7 @@ public class LogReporter public void close() throws Exception { if (buffer.length() > 0) { - LOG.info(buffer.toString()); + LOG.info(buffer::toString); } } } diff --git a/dspace-api/src/main/java/org/dspace/curate/ScriptedTask.java b/dspace-api/src/main/java/org/dspace/curate/ScriptedTask.java index 7f63cb76dd..8d8ab491d2 100644 --- a/dspace-api/src/main/java/org/dspace/curate/ScriptedTask.java +++ b/dspace-api/src/main/java/org/dspace/curate/ScriptedTask.java @@ -16,7 +16,7 @@ import org.dspace.core.Context; * ScriptedTask describes a rather generic ability to perform an operation * upon a DSpace object. It's semantics are identical to the CurationTask interface, * but is designed to be implemented in scripting languages, rather than - * Java. For this reason, the 'perform' methods are renamed to accomodate + * Java. For this reason, the 'perform' methods are renamed to accommodate * languages (like Ruby) that lack method overloading. * * @author richardrodgers diff --git a/dspace-api/src/main/java/org/dspace/curate/XmlWorkflowCuratorServiceImpl.java b/dspace-api/src/main/java/org/dspace/curate/XmlWorkflowCuratorServiceImpl.java index 27a162d543..ec32ff92f9 100644 --- a/dspace-api/src/main/java/org/dspace/curate/XmlWorkflowCuratorServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/curate/XmlWorkflowCuratorServiceImpl.java @@ -140,14 +140,14 @@ public class XmlWorkflowCuratorServiceImpl item.setOwningCollection(wfi.getCollection()); for (Task task : step.tasks) { curator.addTask(task.name); + } - // Check whether the task is configured to be queued rather than automatically run - if (StringUtils.isNotEmpty(step.queue)) { - // queue attribute has been set in the FlowStep configuration: add task to configured queue - curator.queue(c, item.getID().toString(), step.queue); - } else { - // Task is configured to be run automatically - curator.curate(c, item); + if (StringUtils.isNotEmpty(step.queue)) { // Step's tasks are to be queued. + curator.queue(c, item.getID().toString(), step.queue); + } else { // Step's tasks are to be run now. + curator.curate(c, item); + + for (Task task : step.tasks) { int status = curator.getStatus(task.name); String result = curator.getResult(task.name); String action = "none"; @@ -184,14 +184,14 @@ public class XmlWorkflowCuratorServiceImpl } } curator.clear(); - } - // Record any reporting done by the tasks. - if (reporter.length() > 0) { - LOG.info("Curation tasks over item {} for step {} report:%n{}", - () -> wfi.getItem().getID(), - () -> step.step, - () -> reporter.toString()); + // Record any reporting done by the tasks. + if (reporter.length() > 0) { + LOG.info("Curation tasks over item {} for step {} report:\n{}", + () -> wfi.getItem().getID(), + () -> step.step, + () -> reporter.toString()); + } } } return true; diff --git a/dspace-api/src/main/java/org/dspace/curate/package-info.java b/dspace-api/src/main/java/org/dspace/curate/package-info.java index 492642f60c..1168bbd283 100644 --- a/dspace-api/src/main/java/org/dspace/curate/package-info.java +++ b/dspace-api/src/main/java/org/dspace/curate/package-info.java @@ -20,6 +20,8 @@ * * *

    Curation requests may be run immediately or queued for batch processing. + * See {@link TaskQueue} and its relatives, {@link Curation} and its relatives + * for more on queued curation. * *

    Tasks may also be attached to a workflow step, so that a set of tasks is * applied to each uninstalled Item which passes through that step. See @@ -27,5 +29,15 @@ * *

    A task may return to the Curator a status code, a final status message, * and an optional report character stream. + * + *

    The {@link Reporter} classes absorb strings of text and preserve it in + * various ways. A Reporter is a simple {@link Appendable} and makes no + * assumptions about e.g. whether a string represents a complete line. If you + * want your report formatted, insert appropriate newlines and other whitespace + * as needed. Your tasks can emit marked-up text if you wish, but the stock + * Reporter implementations make no attempt to render it. + * + *

    Tasks may be annotated to inform the Curator of special properties. See + * {@link Distributive}, {@link Mutative}, {@link Suspendable} etc. */ package org.dspace.curate; diff --git a/dspace-api/src/main/java/org/dspace/discovery/FullTextContentStreams.java b/dspace-api/src/main/java/org/dspace/discovery/FullTextContentStreams.java index 21468def68..58abf18698 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/FullTextContentStreams.java +++ b/dspace-api/src/main/java/org/dspace/discovery/FullTextContentStreams.java @@ -20,10 +20,10 @@ import java.util.ArrayList; import java.util.Enumeration; import java.util.Iterator; import java.util.List; -import javax.annotation.Nullable; import com.google.common.base.Function; import com.google.common.collect.Iterables; +import jakarta.annotation.Nullable; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; diff --git a/dspace-api/src/main/java/org/dspace/discovery/IndexClient.java b/dspace-api/src/main/java/org/dspace/discovery/IndexClient.java index 661c48d91c..3479c25bf3 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/IndexClient.java +++ b/dspace-api/src/main/java/org/dspace/discovery/IndexClient.java @@ -7,20 +7,27 @@ */ package org.dspace.discovery; +import static org.dspace.discovery.IndexClientOptions.TYPE_OPTION; + import java.io.IOException; import java.sql.SQLException; +import java.util.Arrays; import java.util.Iterator; +import java.util.List; import java.util.Optional; import java.util.UUID; +import java.util.stream.Collectors; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.ParseException; +import org.apache.commons.lang3.StringUtils; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.DSpaceObject; import org.dspace.content.Item; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.ItemService; +import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.discovery.indexobject.IndexableCollection; import org.dspace.discovery.indexobject.IndexableCommunity; @@ -51,6 +58,17 @@ public class IndexClient extends DSpaceRunnable indexableObjectTypes = IndexObjectFactoryFactory.getInstance().getIndexFactories().stream() + .map((indexFactory -> indexFactory.getType())).collect(Collectors.toList()); + type = commandLine.getOptionValue(TYPE_OPTION); + if (!indexableObjectTypes.contains(type)) { + handler.handleException(String.format("%s is not a valid indexable object type, options: %s", + type, Arrays.toString(indexableObjectTypes.toArray()))); + } + } + /** Acquire from dspace-services in future */ /** * new DSpace.getServiceManager().getServiceByName("org.dspace.discovery.SolrIndexer"); @@ -92,7 +110,7 @@ public class IndexClient extends DSpaceRunnable indexableObjectTypes = IndexObjectFactoryFactory.getInstance().getIndexFactories().stream() + .map((indexFactory -> indexFactory.getType())).collect(Collectors.toList()); options .addOption("r", "remove", true, "remove an Item, Collection or Community from index based on its handle"); options.addOption("i", "index", true, "add or update an Item, Collection or Community based on its handle or uuid"); + options.addOption(TYPE_OPTION, "type", true, "reindex only specific type of " + + "(re)indexable objects; options: " + Arrays.toString(indexableObjectTypes.toArray())); options.addOption("c", "clean", false, "clean existing index removing any documents that no longer exist in the db"); options.addOption("d", "delete", false, @@ -83,7 +94,7 @@ public enum IndexClientOptions { options.addOption("b", "build", false, "(re)build index, wiping out current one if it exists"); options.addOption("s", "spellchecker", false, "Rebuild the spellchecker, can be combined with -b and -f."); options.addOption("f", "force", false, - "if updating existing index, force each handle to be reindexed even if uptodate"); + "if updating existing index, force each handle to be reindexed even if up-to-date"); options.addOption("h", "help", false, "print this help message"); return options; } diff --git a/dspace-api/src/main/java/org/dspace/discovery/IndexEventConsumer.java b/dspace-api/src/main/java/org/dspace/discovery/IndexEventConsumer.java index 80602ac804..611200e62a 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/IndexEventConsumer.java +++ b/dspace-api/src/main/java/org/dspace/discovery/IndexEventConsumer.java @@ -73,17 +73,22 @@ public class IndexEventConsumer implements Consumer { int st = event.getSubjectType(); if (!(st == Constants.ITEM || st == Constants.BUNDLE - || st == Constants.COLLECTION || st == Constants.COMMUNITY || st == Constants.SITE)) { + || st == Constants.COLLECTION || st == Constants.COMMUNITY || st == Constants.SITE + || st == Constants.LDN_MESSAGE)) { log .warn("IndexConsumer should not have been given this kind of Subject in an event, skipping: " + event.toString()); return; } - DSpaceObject subject = event.getSubject(ctx); - - DSpaceObject object = event.getObject(ctx); - + DSpaceObject subject = null; + DSpaceObject object = null; + try { + subject = event.getSubject(ctx); + object = event.getObject(ctx); + } catch (Exception e) { + log.warn("Could not find the related DSpace Object for event subject: " + st); + } // If event subject is a Bundle and event was Add or Remove, // transform the event to be a Modify on the owning Item. @@ -110,7 +115,7 @@ public class IndexEventConsumer implements Consumer { case Event.MODIFY: case Event.MODIFY_METADATA: if (subject == null) { - if (st == Constants.SITE) { + if (st == Constants.SITE || st == Constants.LDN_MESSAGE) { // Update the indexable objects of type in event.detail of objects with ids in event.identifiers for (String id : event.getIdentifiers()) { IndexFactory indexableObjectService = IndexObjectFactoryFactory.getInstance(). diff --git a/dspace-api/src/main/java/org/dspace/discovery/IndexingService.java b/dspace-api/src/main/java/org/dspace/discovery/IndexingService.java index 2ef5affa47..6a80d52820 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/IndexingService.java +++ b/dspace-api/src/main/java/org/dspace/discovery/IndexingService.java @@ -79,7 +79,7 @@ public interface IndexingService { /** * Atomically update the index of a single field for an object * @param context The DSpace context - * @param uniqueIndexId The unqiue index ID of the object to update the index for + * @param uniqueIndexId The unique index ID of the object to update the index for * @param field The field to update * @param fieldModifier The modifiers for the field to update. More information on how to atomically update a solr * field using a field modifier can be found here: https://yonik.com/solr/atomic-updates/ diff --git a/dspace-api/src/main/java/org/dspace/discovery/SearchUtils.java b/dspace-api/src/main/java/org/dspace/discovery/SearchUtils.java index 60bf52836b..e94b1f2fb7 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SearchUtils.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SearchUtils.java @@ -88,7 +88,7 @@ public class SearchUtils { /** * Retrieves the Discovery Configuration with a null prefix for a DSpace object. * @param context - * the dabase context + * the database context * @param dso * the DSpace object * @return the Discovery Configuration for the specified DSpace object diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrSearchCore.java b/dspace-api/src/main/java/org/dspace/discovery/SolrSearchCore.java index f31feab612..6304f39a8c 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SolrSearchCore.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrSearchCore.java @@ -8,8 +8,8 @@ package org.dspace.discovery; import java.io.IOException; -import javax.inject.Named; +import jakarta.inject.Named; import org.apache.commons.validator.routines.UrlValidator; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java index cd3797e3e3..67891f122b 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java @@ -30,8 +30,8 @@ import java.util.Map; import java.util.Optional; import java.util.TimeZone; import java.util.UUID; -import javax.mail.MessagingException; +import jakarta.mail.MessagingException; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.MapUtils; import org.apache.commons.collections4.Transformer; @@ -1230,7 +1230,7 @@ public class SolrServiceImpl implements SearchService, IndexingService { } catch (IOException | SQLException | SolrServerException e) { // Any acception that we get ignore it. // We do NOT want any crashed to shown by the user - log.error(LogHelper.getHeader(context, "Error while quering solr", "Query: " + query), e); + log.error(LogHelper.getHeader(context, "Error while querying solr", "Query: " + query), e); return new ArrayList<>(0); } } @@ -1359,7 +1359,7 @@ public class SolrServiceImpl implements SearchService, IndexingService { * Gets the solr field that contains the facet value split on each word break to the end, so can be searched * on each word in the value, see {@link org.dspace.discovery.indexobject.ItemIndexFactoryImpl * #saveFacetPrefixParts(SolrInputDocument, DiscoverySearchFilter, String, String)} - * Ony applicable to facets of type {@link DiscoveryConfigurationParameters.TYPE_TEXT}, otherwise uses the regular + * Only applicable to facets of type {@link DiscoveryConfigurationParameters.TYPE_TEXT}, otherwise uses the regular * facet filter field */ protected String transformPrefixFacetField(DiscoverFacetField facetFieldConfig, String field, diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceIndexComparisonPlugin.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceIndexComparisonPlugin.java new file mode 100644 index 0000000000..001d1c51a4 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceIndexComparisonPlugin.java @@ -0,0 +1,95 @@ +/** + * 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.discovery; + +import org.apache.logging.log4j.Logger; +import org.apache.solr.common.SolrInputDocument; +import org.apache.tika.utils.StringUtils; +import org.dspace.content.Item; +import org.dspace.content.WorkspaceItem; +import org.dspace.content.service.DuplicateDetectionService; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.dspace.discovery.indexobject.IndexableItem; +import org.dspace.discovery.indexobject.IndexableWorkflowItem; +import org.dspace.discovery.indexobject.IndexableWorkspaceItem; +import org.dspace.services.ConfigurationService; +import org.dspace.workflow.WorkflowItem; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Indexes special normalised values used for comparing items, to be used in e.g. basic duplicate detection + * + * @author Kim Shepherd + */ +public class SolrServiceIndexComparisonPlugin implements SolrServiceIndexPlugin { + + @Autowired + ConfigurationService configurationService; + @Autowired + ItemService itemService; + @Autowired + DuplicateDetectionService duplicateDetectionService; + + private static final Logger log = org.apache.logging.log4j.LogManager + .getLogger(SolrServiceIndexComparisonPlugin.class); + + /** + * Index the normalised name of the item to a solr field + * + * @param context DSpace context + * @param idxObj the indexable item + * @param document the Solr document + */ + @Override + public void additionalIndex(Context context, IndexableObject idxObj, SolrInputDocument document) { + // Immediately return if this feature is not configured + if (!configurationService.getBooleanProperty("duplicate.enable", false)) { + return; + } + // Otherwise, continue with item indexing. Handle items, workflow items, and workspace items + if (idxObj instanceof IndexableItem) { + indexItemComparisonValue(context, ((IndexableItem) idxObj).getIndexedObject(), document); + } else if (idxObj instanceof IndexableWorkspaceItem) { + WorkspaceItem workspaceItem = ((IndexableWorkspaceItem) idxObj).getIndexedObject(); + if (workspaceItem != null) { + Item item = workspaceItem.getItem(); + if (item != null) { + indexItemComparisonValue(context, item, document); + } + } + } else if (idxObj instanceof IndexableWorkflowItem) { + WorkflowItem workflowItem = ((IndexableWorkflowItem) idxObj).getIndexedObject(); + if (workflowItem != null) { + Item item = workflowItem.getItem(); + if (item != null) { + indexItemComparisonValue(context, item, document); + } + } + } + } + + /** + * Add the actual comparison value field to the given solr doc + * + * @param context DSpace context + * @param item DSpace item + * @param document Solr document + */ + private void indexItemComparisonValue(Context context, Item item, SolrInputDocument document) { + if (item != null) { + // Build normalised comparison value and add to the document + String comparisonValue = duplicateDetectionService.buildComparisonValue(context, item); + if (!StringUtils.isBlank(comparisonValue)) { + // Add the field to the document + document.addField(configurationService.getProperty("duplicate.comparison.solr.field", + "deduplication_keyword"), comparisonValue); + } + } + } +} diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceMetadataBrowseIndexingPlugin.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceMetadataBrowseIndexingPlugin.java index 746a0cb832..dda041c7b8 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceMetadataBrowseIndexingPlugin.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceMetadataBrowseIndexingPlugin.java @@ -79,7 +79,7 @@ public class SolrServiceMetadataBrowseIndexingPlugin implements SolrServiceIndex // Faceting for metadata browsing. It is different than search facet // because if there are authority with variants support we want all the // variants to go in the facet... they are sorted by count so just the - // prefered label is relevant + // preferred label is relevant for (BrowseIndex bi : bis) { log.debug("Indexing for item " + item.getID() + ", for index: " + bi.getTableName()); @@ -280,7 +280,7 @@ public class SolrServiceMetadataBrowseIndexingPlugin implements SolrServiceIndex } } - // Add sorting options as configurated for the browse system + // Add sorting options as configured for the browse system try { for (SortOption so : SortOption.getSortOptions()) { List dcvalue = itemService.getMetadataByMetadataString(item, so.getMetadata()); diff --git a/dspace-api/src/main/java/org/dspace/discovery/configuration/DiscoverySearchFilter.java b/dspace-api/src/main/java/org/dspace/discovery/configuration/DiscoverySearchFilter.java index ac64573c5d..8767ea83da 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/configuration/DiscoverySearchFilter.java +++ b/dspace-api/src/main/java/org/dspace/discovery/configuration/DiscoverySearchFilter.java @@ -57,7 +57,7 @@ public class DiscoverySearchFilter { * For the DiscoverySearchFilter only the TYPE_TEXT, TYPE_DATE and TYPE_HIERARCHICAL are allowed * * @param type The type for this DiscoverySearchFilter - * @throws DiscoveryConfigurationException If none of the types match, this error will be thrown indiciating this + * @throws DiscoveryConfigurationException If none of the types match, this error will be thrown indicating this */ public void setType(String type) throws DiscoveryConfigurationException { if (type.equalsIgnoreCase(DiscoveryConfigurationParameters.TYPE_TEXT)) { diff --git a/dspace-api/src/main/java/org/dspace/discovery/configuration/DiscoverySortConfiguration.java b/dspace-api/src/main/java/org/dspace/discovery/configuration/DiscoverySortConfiguration.java index cd1a4eecb8..066e3c73e9 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/configuration/DiscoverySortConfiguration.java +++ b/dspace-api/src/main/java/org/dspace/discovery/configuration/DiscoverySortConfiguration.java @@ -9,8 +9,8 @@ package org.dspace.discovery.configuration; import java.util.ArrayList; import java.util.List; -import javax.annotation.Nullable; +import jakarta.annotation.Nullable; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; diff --git a/dspace-api/src/main/java/org/dspace/discovery/indexobject/IndexableLDNNotification.java b/dspace-api/src/main/java/org/dspace/discovery/indexobject/IndexableLDNNotification.java new file mode 100644 index 0000000000..86d600b6c9 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/discovery/indexobject/IndexableLDNNotification.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.discovery.indexobject; + +import org.dspace.app.ldn.LDNMessageEntity; +import org.dspace.discovery.IndexableObject; + +/** + * {@link LDNMessageEntity} implementation for the {@link IndexableObject} + * + * @author Stefano Maffei at 4science.com + */ +public class IndexableLDNNotification extends AbstractIndexableObject { + + private LDNMessageEntity ldnMessage; + public static final String TYPE = LDNMessageEntity.class.getSimpleName(); + + public IndexableLDNNotification(LDNMessageEntity ldnMessage) { + super(); + this.ldnMessage = ldnMessage; + } + + @Override + public String getType() { + return getTypeText(); + } + + @Override + public String getID() { + return ldnMessage.getID(); + } + + @Override + public LDNMessageEntity getIndexedObject() { + return ldnMessage; + } + + @Override + public void setIndexedObject(LDNMessageEntity object) { + this.ldnMessage = object; + } + + @Override + public String getTypeText() { + return TYPE; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/discovery/indexobject/LDNMessageEntityIndexFactoryImpl.java b/dspace-api/src/main/java/org/dspace/discovery/indexobject/LDNMessageEntityIndexFactoryImpl.java new file mode 100644 index 0000000000..7752ae5862 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/discovery/indexobject/LDNMessageEntityIndexFactoryImpl.java @@ -0,0 +1,157 @@ +/** + * 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.discovery.indexobject; + +import static org.apache.commons.lang3.time.DateFormatUtils.format; + +import java.io.IOException; +import java.sql.SQLException; +import java.util.Arrays; +import java.util.Date; +import java.util.Iterator; +import java.util.List; +import java.util.Optional; + +import org.apache.solr.common.SolrInputDocument; +import org.dspace.app.ldn.LDNMessageEntity; +import org.dspace.app.ldn.NotifyServiceEntity; +import org.dspace.app.ldn.service.LDNMessageService; +import org.dspace.content.Item; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Factory implementation implementation for the + * {@link IndexableLDNNotification} + * + * @author Stefano Maffei at 4science.com + */ +public class LDNMessageEntityIndexFactoryImpl extends IndexFactoryImpl { + + @Autowired(required = true) + private LDNMessageService ldnMessageService; + @Autowired(required = true) + private ItemService itemService; + + @Override + public Iterator findAll(Context context) throws SQLException { + final Iterator ldnNotifications = ldnMessageService.findAll(context).iterator(); + return new Iterator<>() { + @Override + public boolean hasNext() { + return ldnNotifications.hasNext(); + } + + @Override + public IndexableLDNNotification next() { + return new IndexableLDNNotification(ldnNotifications.next()); + } + }; + } + + @Override + public String getType() { + return IndexableLDNNotification.TYPE; + } + + @Override + public Optional findIndexableObject(Context context, String id) throws SQLException { + final LDNMessageEntity ldnMessage = ldnMessageService.find(context, id); + return ldnMessage == null ? Optional.empty() : Optional.of(new IndexableLDNNotification(ldnMessage)); + } + + @Override + public boolean supports(Object object) { + return object instanceof LDNMessageEntity; + } + + @Override + public List getIndexableObjects(Context context, LDNMessageEntity object) + throws SQLException { + return Arrays.asList(new IndexableLDNNotification(object)); + } + + @Override + public SolrInputDocument buildDocument(Context context, IndexableLDNNotification indexableObject) + throws SQLException, IOException { + // Add the ID's, types and call the SolrServiceIndexPlugins + final SolrInputDocument doc = super.buildDocument(context, indexableObject); + final LDNMessageEntity ldnMessage = indexableObject.getIndexedObject(); + // add schema, element, qualifier and full fieldName + doc.addField("notification_id", ldnMessage.getID()); + doc.addField("queue_status_i", ldnMessage.getQueueStatus()); + doc.addField("queue_status_s", LDNMessageEntity.getQueueStatus(ldnMessage)); + addFacetIndex(doc, "queue_status", String.valueOf(ldnMessage.getQueueStatus()), + LDNMessageEntity.getQueueStatus(ldnMessage)); + if (ldnMessage.getObject() != null && ldnMessage.getObject().getID() != null) { + Item item = itemService.findByIdOrLegacyId(context, ldnMessage.getObject().getID().toString()); + if (item != null) { + addFacetIndex(doc, "object", item.getID().toString(), itemService.getMetadata(item, "dc.title")); + addFacetIndex(doc, "relateditem", item.getID().toString(), itemService.getMetadata(item, "dc.title")); + } + } + if (ldnMessage.getContext() != null && ldnMessage.getContext().getID() != null) { + Item item = itemService.findByIdOrLegacyId(context, ldnMessage.getContext().getID().toString()); + if (item != null) { + addFacetIndex(doc, "context", item.getID().toString(), itemService.getMetadata(item, "dc.title")); + addFacetIndex(doc, "relateditem", item.getID().toString(), itemService.getMetadata(item, "dc.title")); + } + } + NotifyServiceEntity origin = ldnMessage.getOrigin(); + if (origin != null) { + addFacetIndex(doc, "origin", String.valueOf(origin.getID()), + LDNMessageEntity.getServiceNameForNotifyServ(origin)); + addFacetIndex(doc, "ldn_service", String.valueOf(origin.getID()), + LDNMessageEntity.getServiceNameForNotifyServ(origin)); + } + NotifyServiceEntity target = ldnMessage.getTarget(); + if (target != null) { + addFacetIndex(doc, "target", String.valueOf(target.getID()), + LDNMessageEntity.getServiceNameForNotifyServ(target)); + addFacetIndex(doc, "ldn_service", String.valueOf(target.getID()), + LDNMessageEntity.getServiceNameForNotifyServ(target)); + } + if (ldnMessage.getInReplyTo() != null) { + doc.addField("in_reply_to", ldnMessage.getInReplyTo().getID()); + } + doc.addField("message", ldnMessage.getMessage()); + doc.addField("type", ldnMessage.getType()); + addFacetIndex(doc, "activity_stream_type", ldnMessage.getActivityStreamType(), + ldnMessage.getActivityStreamType()); + addFacetIndex(doc, "coar_notify_type", ldnMessage.getCoarNotifyType(), ldnMessage.getCoarNotifyType()); + doc.addField("queue_attempts", ldnMessage.getQueueAttempts()); + doc.addField("queue_attempts_sort", ldnMessage.getQueueAttempts()); + + indexDateFieldForFacet(doc, ldnMessage.getQueueLastStartTime()); + + doc.addField("queue_timeout", ldnMessage.getQueueTimeout()); + String notificationType = LDNMessageEntity.getNotificationType(ldnMessage); + addFacetIndex(doc, "notification_type", notificationType, notificationType); + + return doc; + } + + private void indexDateFieldForFacet(SolrInputDocument doc, Date queueLastStartTime) { + if (queueLastStartTime != null) { + String value = format(queueLastStartTime, "yyyy-MM-dd"); + addFacetIndex(doc, "queue_last_start_time", value, value); + doc.addField("queue_last_start_time", value); + doc.addField("queue_last_start_time_dt", queueLastStartTime); + doc.addField("queue_last_start_time_min", value); + doc.addField("queue_last_start_time_min_sort", value); + doc.addField("queue_last_start_time_max", value); + doc.addField("queue_last_start_time_max_sort", value); + doc.addField("queue_last_start_time.year", + Integer.parseInt(format(queueLastStartTime, "yyyy"))); + doc.addField("queue_last_start_time.year_sort", + Integer.parseInt(format(queueLastStartTime, "yyyy"))); + } + } + +} diff --git a/dspace-api/src/main/java/org/dspace/eperson/AccountServiceImpl.java b/dspace-api/src/main/java/org/dspace/eperson/AccountServiceImpl.java index 3d4eab125f..4fb95962b2 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/AccountServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/AccountServiceImpl.java @@ -10,8 +10,8 @@ package org.dspace.eperson; import java.io.IOException; import java.sql.SQLException; import java.util.Locale; -import javax.mail.MessagingException; +import jakarta.mail.MessagingException; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.authenticate.service.AuthenticationService; @@ -73,7 +73,7 @@ public class AccountServiceImpl implements AccountService { * @param email Email address to send the registration email to * @throws java.sql.SQLException passed through. * @throws java.io.IOException passed through. - * @throws javax.mail.MessagingException passed through. + * @throws jakarta.mail.MessagingException passed through. * @throws org.dspace.authorize.AuthorizeException passed through. */ @Override @@ -105,7 +105,7 @@ public class AccountServiceImpl implements AccountService { * @param email Email address to send the forgot-password email to * @throws java.sql.SQLException passed through. * @throws java.io.IOException passed through. - * @throws javax.mail.MessagingException passed through. + * @throws jakarta.mail.MessagingException passed through. * @throws org.dspace.authorize.AuthorizeException passed through. */ @Override diff --git a/dspace-api/src/main/java/org/dspace/eperson/CaptchaServiceImpl.java b/dspace-api/src/main/java/org/dspace/eperson/CaptchaServiceImpl.java index 0ab66aea5c..b213675b16 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/CaptchaServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/CaptchaServiceImpl.java @@ -14,9 +14,9 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.regex.Pattern; -import javax.annotation.PostConstruct; import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.annotation.PostConstruct; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; import org.apache.http.client.HttpClient; diff --git a/dspace-api/src/main/java/org/dspace/eperson/EPerson.java b/dspace-api/src/main/java/org/dspace/eperson/EPerson.java index da83a1cafd..f79efebf5f 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/EPerson.java +++ b/dspace-api/src/main/java/org/dspace/eperson/EPerson.java @@ -11,27 +11,25 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.Date; import java.util.List; -import javax.persistence.Cacheable; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.ManyToMany; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; -import javax.persistence.Transient; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.ManyToMany; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; +import jakarta.persistence.Transient; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; -import org.dspace.content.DSpaceObject; +import org.dspace.content.CacheableDSpaceObject; import org.dspace.content.DSpaceObjectLegacySupport; import org.dspace.content.Item; import org.dspace.core.Constants; import org.dspace.core.Context; +import org.dspace.core.HibernateProxyHelper; import org.dspace.eperson.factory.EPersonServiceFactory; import org.dspace.eperson.service.EPersonService; -import org.hibernate.annotations.CacheConcurrencyStrategy; -import org.hibernate.proxy.HibernateProxyHelper; /** * Class representing an e-person. @@ -39,10 +37,8 @@ import org.hibernate.proxy.HibernateProxyHelper; * @author David Stuve */ @Entity -@Cacheable -@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE, include = "non-lazy") @Table(name = "eperson") -public class EPerson extends DSpaceObject implements DSpaceObjectLegacySupport { +public class EPerson extends CacheableDSpaceObject implements DSpaceObjectLegacySupport { @Column(name = "eperson_id", insertable = false, updatable = false) private Integer legacyId; @@ -178,7 +174,7 @@ public class EPerson extends DSpaceObject implements DSpaceObjectLegacySupport { /** * Set the EPerson's language. Value is expected to be a Unix/POSIX * Locale specification of the form {language} or {language}_{territory}, - * e.g. "en", "en_US", "pt_BR" (the latter is Brazilian Portugese). + * e.g. "en", "en_US", "pt_BR" (the latter is Brazilian Portuguese). * * @param context The relevant DSpace Context. * @param language language code diff --git a/dspace-api/src/main/java/org/dspace/eperson/EPersonConsumer.java b/dspace-api/src/main/java/org/dspace/eperson/EPersonConsumer.java index feefe65717..8970bb2849 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/EPersonConsumer.java +++ b/dspace-api/src/main/java/org/dspace/eperson/EPersonConsumer.java @@ -10,8 +10,8 @@ package org.dspace.eperson; import java.io.IOException; import java.util.Date; import java.util.UUID; -import javax.mail.MessagingException; +import jakarta.mail.MessagingException; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.dspace.core.Constants; @@ -47,7 +47,7 @@ public class EPersonConsumer implements Consumer { = DSpaceServicesFactory.getInstance().getConfigurationService(); /** - * Initalise the consumer + * Initialise the consumer * * @throws Exception if error */ diff --git a/dspace-api/src/main/java/org/dspace/eperson/EPersonServiceImpl.java b/dspace-api/src/main/java/org/dspace/eperson/EPersonServiceImpl.java index 453d5d0726..40e0859be1 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/EPersonServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/EPersonServiceImpl.java @@ -33,6 +33,7 @@ import org.dspace.content.DSpaceObjectServiceImpl; import org.dspace.content.Item; import org.dspace.content.MetadataField; import org.dspace.content.MetadataValue; +import org.dspace.content.QAEventProcessed; import org.dspace.content.WorkspaceItem; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.ItemService; @@ -47,6 +48,7 @@ import org.dspace.eperson.service.GroupService; import org.dspace.eperson.service.SubscribeService; import org.dspace.event.Event; import org.dspace.orcid.service.OrcidTokenService; +import org.dspace.qaevent.dao.QAEventsDAO; import org.dspace.services.ConfigurationService; import org.dspace.util.UUIDUtils; import org.dspace.versioning.Version; @@ -106,6 +108,8 @@ public class EPersonServiceImpl extends DSpaceObjectServiceImpl impleme protected ConfigurationService configurationService; @Autowired protected OrcidTokenService orcidTokenService; + @Autowired + protected QAEventsDAO qaEventsDao; protected EPersonServiceImpl() { super(); @@ -339,11 +343,11 @@ public class EPersonServiceImpl extends DSpaceObjectServiceImpl impleme try { delete(context, ePerson, true); } catch (AuthorizeException ex) { - log.error("This AuthorizeException: " + ex + " occured while deleting Eperson with the ID: " + + log.error("This AuthorizeException: " + ex + " occurred while deleting Eperson with the ID: " + ePerson.getID()); throw new AuthorizeException(ex); } catch (IOException ex) { - log.error("This IOException: " + ex + " occured while deleting Eperson with the ID: " + ePerson.getID()); + log.error("This IOException: " + ex + " occurred while deleting Eperson with the ID: " + ePerson.getID()); throw new AuthorizeException(ex); } catch (EPersonDeletionException e) { throw new IllegalStateException(e); @@ -447,7 +451,7 @@ public class EPersonServiceImpl extends DSpaceObjectServiceImpl impleme ePerson, task.getStepID()); } catch (WorkflowConfigurationException ex) { log.error("This WorkflowConfigurationException: " + ex + - " occured while deleting Eperson with the ID: " + ePerson.getID()); + " occurred while deleting Eperson with the ID: " + ePerson.getID()); throw new AuthorizeException(new EPersonDeletionException(Collections .singletonList(tableName))); } @@ -492,6 +496,11 @@ public class EPersonServiceImpl extends DSpaceObjectServiceImpl impleme // Remove any subscriptions subscribeService.deleteByEPerson(context, ePerson); + List qaEvents = qaEventsDao.findByEPerson(context, ePerson); + for (QAEventProcessed qaEvent : qaEvents) { + qaEventsDao.delete(context, qaEvent); + } + // Remove ourself ePersonDAO.delete(context, ePerson); diff --git a/dspace-api/src/main/java/org/dspace/eperson/Group.java b/dspace-api/src/main/java/org/dspace/eperson/Group.java index 67655e0e0a..24b44b8149 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/Group.java +++ b/dspace-api/src/main/java/org/dspace/eperson/Group.java @@ -10,23 +10,21 @@ package org.dspace.eperson; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; -import javax.persistence.Cacheable; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.JoinColumn; -import javax.persistence.JoinTable; -import javax.persistence.ManyToMany; -import javax.persistence.Table; -import javax.persistence.Transient; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.JoinTable; +import jakarta.persistence.ManyToMany; +import jakarta.persistence.Table; +import jakarta.persistence.Transient; import org.apache.commons.lang3.StringUtils; -import org.dspace.content.DSpaceObject; +import org.dspace.content.CacheableDSpaceObject; import org.dspace.content.DSpaceObjectLegacySupport; import org.dspace.core.Constants; import org.dspace.core.Context; -import org.hibernate.annotations.CacheConcurrencyStrategy; -import org.hibernate.proxy.HibernateProxyHelper; +import org.dspace.core.HibernateProxyHelper; /** * Class representing a group of e-people. @@ -34,10 +32,8 @@ import org.hibernate.proxy.HibernateProxyHelper; * @author David Stuve */ @Entity -@Cacheable -@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE, include = "non-lazy") @Table(name = "epersongroup") -public class Group extends DSpaceObject implements DSpaceObjectLegacySupport { +public class Group extends CacheableDSpaceObject implements DSpaceObjectLegacySupport { @Transient public static final String ANONYMOUS = "Anonymous"; diff --git a/dspace-api/src/main/java/org/dspace/eperson/Group2GroupCache.java b/dspace-api/src/main/java/org/dspace/eperson/Group2GroupCache.java index 09bdf34d4c..a1c12371f5 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/Group2GroupCache.java +++ b/dspace-api/src/main/java/org/dspace/eperson/Group2GroupCache.java @@ -8,14 +8,14 @@ package org.dspace.eperson; import java.io.Serializable; -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.Id; -import javax.persistence.JoinColumn; -import javax.persistence.ManyToOne; -import javax.persistence.Table; -import org.hibernate.proxy.HibernateProxyHelper; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import org.dspace.core.HibernateProxyHelper; /** * Database entity representation of the group2groupcache table diff --git a/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java b/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java index 730053e42c..3fb20e2f1e 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java @@ -147,7 +147,7 @@ public class GroupServiceImpl extends DSpaceObjectServiceImpl implements public void addMember(Context context, Group groupParent, Group groupChild) throws SQLException { // don't add if it's already a member // and don't add itself - if (groupParent.contains(groupChild) || groupParent.getID() == groupChild.getID()) { + if (groupParent.contains(groupChild) || groupParent.getID().equals(groupChild.getID())) { return; } @@ -178,7 +178,7 @@ public class GroupServiceImpl extends DSpaceObjectServiceImpl implements Role role = stepByName.getRole(); for (CollectionRole collectionRole : collectionRoles) { if (StringUtils.equals(collectionRole.getRoleId(), role.getId()) - && claimedTask.getWorkflowItem().getCollection() == collectionRole.getCollection()) { + && claimedTask.getWorkflowItem().getCollection().equals(collectionRole.getCollection())) { // Count number of EPersons who are *direct* members of this group int totalDirectEPersons = ePersonService.countByGroups(context, Set.of(group)); // Count number of Groups which have this groupParent as a direct parent diff --git a/dspace-api/src/main/java/org/dspace/eperson/PasswordHash.java b/dspace-api/src/main/java/org/dspace/eperson/PasswordHash.java index 17340c5a58..ba34df4dae 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/PasswordHash.java +++ b/dspace-api/src/main/java/org/dspace/eperson/PasswordHash.java @@ -16,10 +16,10 @@ import java.util.Arrays; import org.apache.commons.codec.DecoderException; import org.apache.commons.codec.binary.Hex; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * For handling digested secrets (such as passwords). @@ -31,7 +31,7 @@ import org.slf4j.LoggerFactory; * @author mwood */ public class PasswordHash { - private static final Logger log = LoggerFactory.getLogger(PasswordHash.class); + private static final Logger log = LogManager.getLogger(); private static final ConfigurationService config = DSpaceServicesFactory.getInstance().getConfigurationService(); private static final Charset UTF_8 = Charset.forName("UTF-8"); // Should always succeed: UTF-8 is required @@ -133,7 +133,7 @@ public class PasswordHash { try { hash = digest(salt, algorithm, password); } catch (NoSuchAlgorithmException e) { - log.error(e.getMessage()); + log.error(e::getMessage); hash = new byte[] {0}; } } @@ -149,7 +149,7 @@ public class PasswordHash { try { candidate = digest(salt, algorithm, secret); } catch (NoSuchAlgorithmException e) { - log.error(e.getMessage()); + log.error(e::getMessage); return false; } return Arrays.equals(candidate, hash); @@ -225,7 +225,7 @@ public class PasswordHash { if (null == rng) { rng = new SecureRandom(); log.info("Initialized a random number stream using {} provided by {}", - rng.getAlgorithm(), rng.getProvider()); + rng::getAlgorithm, rng::getProvider); rngUses = 0; } diff --git a/dspace-api/src/main/java/org/dspace/eperson/RegistrationData.java b/dspace-api/src/main/java/org/dspace/eperson/RegistrationData.java index f4f41bdff2..0d0c5e7db8 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/RegistrationData.java +++ b/dspace-api/src/main/java/org/dspace/eperson/RegistrationData.java @@ -8,16 +8,16 @@ package org.dspace.eperson; import java.util.Date; -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.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.dspace.core.Context; import org.dspace.core.ReloadableEntity; diff --git a/dspace-api/src/main/java/org/dspace/eperson/Subscription.java b/dspace-api/src/main/java/org/dspace/eperson/Subscription.java index 5db63740f4..0f473a5a75 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/Subscription.java +++ b/dspace-api/src/main/java/org/dspace/eperson/Subscription.java @@ -9,19 +9,19 @@ package org.dspace.eperson; import java.util.ArrayList; import java.util.List; -import javax.persistence.CascadeType; -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.OneToMany; -import javax.persistence.SequenceGenerator; -import javax.persistence.Table; +import jakarta.persistence.CascadeType; +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.OneToMany; +import jakarta.persistence.SequenceGenerator; +import jakarta.persistence.Table; import org.dspace.content.DSpaceObject; import org.dspace.core.Context; import org.dspace.core.ReloadableEntity; diff --git a/dspace-api/src/main/java/org/dspace/eperson/SubscriptionParameter.java b/dspace-api/src/main/java/org/dspace/eperson/SubscriptionParameter.java index 7526535d7f..8d372966b1 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/SubscriptionParameter.java +++ b/dspace-api/src/main/java/org/dspace/eperson/SubscriptionParameter.java @@ -7,16 +7,15 @@ */ package org.dspace.eperson; -import javax.persistence.Column; -import javax.persistence.Entity; -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 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; /** diff --git a/dspace-api/src/main/java/org/dspace/eperson/dao/impl/EPersonDAOImpl.java b/dspace-api/src/main/java/org/dspace/eperson/dao/impl/EPersonDAOImpl.java index 87d6c5869b..7d8e0720c4 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/dao/impl/EPersonDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/dao/impl/EPersonDAOImpl.java @@ -15,11 +15,11 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.UUID; -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.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.ListUtils; import org.apache.commons.lang3.StringUtils; diff --git a/dspace-api/src/main/java/org/dspace/eperson/dao/impl/Group2GroupCacheDAOImpl.java b/dspace-api/src/main/java/org/dspace/eperson/dao/impl/Group2GroupCacheDAOImpl.java index 83fb48aaf0..1cd359188c 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/dao/impl/Group2GroupCacheDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/dao/impl/Group2GroupCacheDAOImpl.java @@ -10,12 +10,12 @@ package org.dspace.eperson.dao.impl; import java.sql.SQLException; import java.util.LinkedList; import java.util.List; -import javax.persistence.Query; -import javax.persistence.criteria.CriteriaBuilder; -import javax.persistence.criteria.CriteriaQuery; -import javax.persistence.criteria.Predicate; -import javax.persistence.criteria.Root; +import jakarta.persistence.Query; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Predicate; +import jakarta.persistence.criteria.Root; import org.dspace.core.AbstractHibernateDAO; import org.dspace.core.Context; import org.dspace.eperson.Group; diff --git a/dspace-api/src/main/java/org/dspace/eperson/dao/impl/GroupDAOImpl.java b/dspace-api/src/main/java/org/dspace/eperson/dao/impl/GroupDAOImpl.java index 6aea9ecd8d..abd9fc830f 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/dao/impl/GroupDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/dao/impl/GroupDAOImpl.java @@ -11,8 +11,8 @@ import java.sql.SQLException; import java.util.Collections; import java.util.List; import java.util.UUID; -import javax.persistence.Query; +import jakarta.persistence.Query; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.dspace.content.MetadataField; diff --git a/dspace-api/src/main/java/org/dspace/eperson/dao/impl/RegistrationDataDAOImpl.java b/dspace-api/src/main/java/org/dspace/eperson/dao/impl/RegistrationDataDAOImpl.java index 4a15dcc867..63e87400ce 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/dao/impl/RegistrationDataDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/dao/impl/RegistrationDataDAOImpl.java @@ -8,11 +8,11 @@ package org.dspace.eperson.dao.impl; import java.sql.SQLException; -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.core.AbstractHibernateDAO; import org.dspace.core.Context; import org.dspace.eperson.RegistrationData; diff --git a/dspace-api/src/main/java/org/dspace/eperson/dao/impl/SubscriptionDAOImpl.java b/dspace-api/src/main/java/org/dspace/eperson/dao/impl/SubscriptionDAOImpl.java index 6c36211f31..d3d4748728 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/dao/impl/SubscriptionDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/dao/impl/SubscriptionDAOImpl.java @@ -11,12 +11,12 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; -import javax.persistence.Query; -import javax.persistence.criteria.CriteriaBuilder; -import javax.persistence.criteria.CriteriaQuery; -import javax.persistence.criteria.Join; -import javax.persistence.criteria.Root; +import jakarta.persistence.Query; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Join; +import jakarta.persistence.criteria.Root; import org.dspace.content.DSpaceObject; import org.dspace.core.AbstractHibernateDAO; import org.dspace.core.Context; @@ -44,11 +44,12 @@ public class SubscriptionDAOImpl extends AbstractHibernateDAO impl public List findByEPerson(Context context, EPerson eperson, Integer limit, Integer offset) throws SQLException { CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); - javax.persistence.criteria.CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Subscription.class); + jakarta.persistence.criteria.CriteriaQuery criteriaQuery = + getCriteriaQuery(criteriaBuilder, Subscription.class); Root subscriptionRoot = criteriaQuery.from(Subscription.class); criteriaQuery.select(subscriptionRoot); criteriaQuery.where(criteriaBuilder.equal(subscriptionRoot.get(Subscription_.ePerson), eperson)); - List orderList = new LinkedList<>(); + List orderList = new LinkedList<>(); orderList.add(criteriaBuilder.asc(subscriptionRoot.get(Subscription_.dSpaceObject))); criteriaQuery.orderBy(orderList); return list(context, criteriaQuery, false, Subscription.class, limit, offset); @@ -59,7 +60,7 @@ public class SubscriptionDAOImpl extends AbstractHibernateDAO impl DSpaceObject dSpaceObject, Integer limit, Integer offset) throws SQLException { CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); - javax.persistence.criteria.CriteriaQuery criteriaQuery = + jakarta.persistence.criteria.CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Subscription.class); Root subscriptionRoot = criteriaQuery.from(Subscription.class); criteriaQuery.select(subscriptionRoot); @@ -67,7 +68,7 @@ public class SubscriptionDAOImpl extends AbstractHibernateDAO impl subscriptionRoot.get(Subscription_.ePerson), eperson), criteriaBuilder.equal(subscriptionRoot.get(Subscription_.dSpaceObject), dSpaceObject) )); - List orderList = new LinkedList<>(); + List orderList = new LinkedList<>(); orderList.add(criteriaBuilder.asc(subscriptionRoot.get(Subscription_.dSpaceObject))); criteriaQuery.orderBy(orderList); return list(context, criteriaQuery, false, Subscription.class, limit, offset); @@ -104,7 +105,7 @@ public class SubscriptionDAOImpl extends AbstractHibernateDAO impl public List findAllOrderedByIDAndResourceType(Context context, String resourceType, Integer limit, Integer offset) throws SQLException { String hqlQuery = "select s from Subscription s join %s dso " + - "ON dso.id = s.dSpaceObject ORDER BY subscription_id"; + "ON dso = s.dSpaceObject ORDER BY s.id"; if (resourceType != null) { hqlQuery = String.format(hqlQuery, resourceType); } @@ -125,7 +126,7 @@ public class SubscriptionDAOImpl extends AbstractHibernateDAO impl CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Subscription.class); Root subscriptionRoot = criteriaQuery.from(Subscription.class); criteriaQuery.select(subscriptionRoot); - List orderList = new LinkedList<>(); + List orderList = new LinkedList<>(); orderList.add(criteriaBuilder.asc(subscriptionRoot.get(Subscription_.dSpaceObject))); criteriaQuery.orderBy(orderList); return list(context, criteriaQuery, false, Subscription.class, limit, offset); @@ -145,7 +146,7 @@ public class SubscriptionDAOImpl extends AbstractHibernateDAO impl criteriaBuilder.equal(childJoin.get(SubscriptionParameter_.name), "frequency"), criteriaBuilder.equal(childJoin.get(SubscriptionParameter_.value), frequencyValue) )); - List orderList = new ArrayList<>(1); + List orderList = new ArrayList<>(1); orderList.add(criteriaBuilder.asc(subscriptionRoot.get(Subscription_.ePerson))); orderList.add(criteriaBuilder.asc(subscriptionRoot.get(Subscription_.id))); criteriaQuery.orderBy(orderList); diff --git a/dspace-api/src/main/java/org/dspace/eperson/service/AccountService.java b/dspace-api/src/main/java/org/dspace/eperson/service/AccountService.java index c8ecb0cc67..637b81c41d 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/service/AccountService.java +++ b/dspace-api/src/main/java/org/dspace/eperson/service/AccountService.java @@ -9,8 +9,8 @@ package org.dspace.eperson.service; import java.io.IOException; import java.sql.SQLException; -import javax.mail.MessagingException; +import jakarta.mail.MessagingException; import org.dspace.authorize.AuthorizeException; import org.dspace.core.Context; import org.dspace.eperson.EPerson; diff --git a/dspace-api/src/main/java/org/dspace/eperson/service/EPersonService.java b/dspace-api/src/main/java/org/dspace/eperson/service/EPersonService.java index 2afec161a6..b3583155f3 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/service/EPersonService.java +++ b/dspace-api/src/main/java/org/dspace/eperson/service/EPersonService.java @@ -13,8 +13,8 @@ import java.sql.SQLException; import java.util.Date; import java.util.List; import java.util.Set; -import javax.validation.constraints.NotNull; +import jakarta.validation.constraints.NotNull; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Item; import org.dspace.content.MetadataFieldName; diff --git a/dspace-api/src/main/java/org/dspace/event/Event.java b/dspace-api/src/main/java/org/dspace/event/Event.java index af8b2d4571..1f90381d00 100644 --- a/dspace-api/src/main/java/org/dspace/event/Event.java +++ b/dspace-api/src/main/java/org/dspace/event/Event.java @@ -104,8 +104,10 @@ public class Event implements Serializable { protected static final int EPERSON = 1 << Constants.EPERSON; // 7 + protected static final int LDN_MESSAGE = 1 << Constants.LDN_MESSAGE; // 8 + protected static final int ALL_OBJECTS_MASK = BITSTREAM | BUNDLE | ITEM - | COLLECTION | COMMUNITY | SITE | GROUP | EPERSON; + | COLLECTION | COMMUNITY | SITE | GROUP | EPERSON | LDN_MESSAGE; protected static Map objTypeToMask = new HashMap(); @@ -135,6 +137,9 @@ public class Event implements Serializable { objTypeToMask.put(Constants.EPERSON, EPERSON); objMaskToType.put(EPERSON, Constants.EPERSON); + + objTypeToMask.put(Constants.LDN_MESSAGE, LDN_MESSAGE); + objMaskToType.put(LDN_MESSAGE, Constants.LDN_MESSAGE); } /** ---------- Event Fields ------------- * */ @@ -188,7 +193,7 @@ public class Event implements Serializable { * Contains all identifiers of the DSpaceObject that was changed (added, * modified, deleted, ...). * - * All events gets fired when a context that contains events gets commited. + * All events gets fired when a context that contains events gets committed. * When the delete event is fired, a deleted DSpaceObject is already gone. * This array contains all identifiers of the object, not only the handle * as the detail field does. The field may be an empty array if no diff --git a/dspace-api/src/main/java/org/dspace/event/EventServiceImpl.java b/dspace-api/src/main/java/org/dspace/event/EventServiceImpl.java index 1afc0a22f5..575c42250b 100644 --- a/dspace-api/src/main/java/org/dspace/event/EventServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/event/EventServiceImpl.java @@ -107,7 +107,7 @@ public class EventServiceImpl implements EventService { try { return (Dispatcher) dispatcherPool.borrowObject(name); } catch (Exception e) { - throw new IllegalStateException("Unable to aquire dispatcher named " + name, e); + throw new IllegalStateException("Unable to acquire dispatcher named " + name, e); } } @@ -153,7 +153,7 @@ public class EventServiceImpl implements EventService { // Prefix of keys in DSpace Configuration private static final String PROP_PFX = "event.dispatcher"; - // Cache of event dispatchers, keyed by name, for re-use. + // Cache of event dispatchers, keyed by name, for reuse. protected Map dispatchers = new HashMap(); public DispatcherPoolFactory() { diff --git a/dspace-api/src/main/java/org/dspace/event/service/EventService.java b/dspace-api/src/main/java/org/dspace/event/service/EventService.java index 5c032c3f74..d8b26e891b 100644 --- a/dspace-api/src/main/java/org/dspace/event/service/EventService.java +++ b/dspace-api/src/main/java/org/dspace/event/service/EventService.java @@ -27,7 +27,7 @@ public interface EventService { * if one exists. * * @param name dispatcher name - * @return chached instance of dispatcher + * @return cached instance of dispatcher */ public Dispatcher getDispatcher(String name); diff --git a/dspace-api/src/main/java/org/dspace/external/OpenAIRERestConnector.java b/dspace-api/src/main/java/org/dspace/external/OpenaireRestConnector.java similarity index 91% rename from dspace-api/src/main/java/org/dspace/external/OpenAIRERestConnector.java rename to dspace-api/src/main/java/org/dspace/external/OpenaireRestConnector.java index b0aa4aba13..27688df6c7 100644 --- a/dspace-api/src/main/java/org/dspace/external/OpenAIRERestConnector.java +++ b/dspace-api/src/main/java/org/dspace/external/OpenaireRestConnector.java @@ -16,7 +16,6 @@ import java.net.URL; import java.util.ArrayList; import java.util.Base64; import java.util.List; -import javax.xml.bind.JAXBException; import eu.openaire.jaxb.helper.OpenAIREHandler; import eu.openaire.jaxb.model.Response; @@ -40,20 +39,20 @@ import org.json.JSONObject; import org.springframework.beans.factory.annotation.Autowired; /** - * based on OrcidRestConnector it's a rest connector for OpenAIRE API providing + * based on OrcidRestConnector it's a rest connector for Openaire API providing * ways to perform searches and token grabbing * * @author paulo-graca * */ -public class OpenAIRERestConnector { +public class OpenaireRestConnector { /** * log4j logger */ - private static Logger log = org.apache.logging.log4j.LogManager.getLogger(OpenAIRERestConnector.class); + private static Logger log = org.apache.logging.log4j.LogManager.getLogger(OpenaireRestConnector.class); /** - * OpenAIRE API Url + * Openaire API Url * and can be configured with: openaire.api.url */ private String url = "https://api.openaire.eu"; @@ -65,30 +64,30 @@ public class OpenAIRERestConnector { boolean tokenEnabled = false; /** - * OpenAIRE Authorization and Authentication Token Service URL + * Openaire Authorization and Authentication Token Service URL * and can be configured with: openaire.token.url */ private String tokenServiceUrl; /** - * OpenAIRE clientId + * Openaire clientId * and can be configured with: openaire.token.clientId */ private String clientId; /** - * OpenAIRERest access token + * OpenaireRest access token */ - private OpenAIRERestToken accessToken; + private OpenaireRestToken accessToken; /** - * OpenAIRE clientSecret + * Openaire clientSecret * and can be configured with: openaire.token.clientSecret */ private String clientSecret; - public OpenAIRERestConnector(String url) { + public OpenaireRestConnector(String url) { this.url = url; } @@ -99,7 +98,7 @@ public class OpenAIRERestConnector { * * @throws IOException */ - public OpenAIRERestToken grabNewAccessToken() throws IOException { + public OpenaireRestToken grabNewAccessToken() throws IOException { if (StringUtils.isBlank(tokenServiceUrl) || StringUtils.isBlank(clientId) || StringUtils.isBlank(clientSecret)) { @@ -145,13 +144,13 @@ public class OpenAIRERestConnector { throw new IOException("Unable to grab the access token using provided service url, client id and secret"); } - return new OpenAIRERestToken(responseObject.get("access_token").toString(), + return new OpenaireRestToken(responseObject.get("access_token").toString(), Long.valueOf(responseObject.get("expires_in").toString())); } /** - * Perform a GET request to the OpenAIRE API + * Perform a GET request to the Openaire API * * @param file * @param accessToken @@ -218,12 +217,12 @@ public class OpenAIRERestConnector { } /** - * Perform an OpenAIRE Project Search By Keywords + * Perform an Openaire Project Search By Keywords * * @param page * @param size * @param keywords - * @return OpenAIRE Response + * @return Openaire Response */ public Response searchProjectByKeywords(int page, int size, String... keywords) { String path = "search/projects?keywords=" + String.join("+", keywords); @@ -231,13 +230,13 @@ public class OpenAIRERestConnector { } /** - * Perform an OpenAIRE Project Search By ID and by Funder + * Perform an Openaire Project Search By ID and by Funder * * @param projectID * @param projectFunder * @param page * @param size - * @return OpenAIRE Response + * @return Openaire Response */ public Response searchProjectByIDAndFunder(String projectID, String projectFunder, int page, int size) { String path = "search/projects?grantID=" + projectID + "&funder=" + projectFunder; @@ -245,12 +244,12 @@ public class OpenAIRERestConnector { } /** - * Perform an OpenAIRE Search request + * Perform an Openaire Search request * * @param path * @param page * @param size - * @return OpenAIRE Response + * @return Openaire Response */ public Response search(String path, int page, int size) { String[] queryStringPagination = { "page=" + page, "size=" + size }; @@ -278,7 +277,7 @@ public class OpenAIRERestConnector { if (result != null) { try { return OpenAIREHandler.unmarshal(result); - } catch (JAXBException e) { + } catch (Exception e) { log.error("Error extracting result from request: " + queryString); getGotError(e, path); } diff --git a/dspace-api/src/main/java/org/dspace/external/OpenAIRERestToken.java b/dspace-api/src/main/java/org/dspace/external/OpenaireRestToken.java similarity index 89% rename from dspace-api/src/main/java/org/dspace/external/OpenAIRERestToken.java rename to dspace-api/src/main/java/org/dspace/external/OpenaireRestToken.java index 203f09b3c6..f5dc2b27f8 100644 --- a/dspace-api/src/main/java/org/dspace/external/OpenAIRERestToken.java +++ b/dspace-api/src/main/java/org/dspace/external/OpenaireRestToken.java @@ -8,13 +8,13 @@ package org.dspace.external; /** - * OpenAIRE rest API token to be used when grabbing an accessToken.
    + * Openaire rest API token to be used when grabbing an accessToken.
    * Based on https://develop.openaire.eu/basic.html * * @author paulo-graca * */ -public class OpenAIRERestToken { +public class OpenaireRestToken { /** * Stored access token @@ -32,7 +32,7 @@ public class OpenAIRERestToken { * @param accessToken * @param expiresIn */ - public OpenAIRERestToken(String accessToken, Long expiresIn) { + public OpenaireRestToken(String accessToken, Long expiresIn) { this.accessToken = accessToken; this.setExpirationDate(expiresIn); } diff --git a/dspace-api/src/main/java/org/dspace/external/model/ExternalDataObject.java b/dspace-api/src/main/java/org/dspace/external/model/ExternalDataObject.java index eac9921df6..66b0b18fbc 100644 --- a/dspace-api/src/main/java/org/dspace/external/model/ExternalDataObject.java +++ b/dspace-api/src/main/java/org/dspace/external/model/ExternalDataObject.java @@ -9,7 +9,10 @@ package org.dspace.external.model; import java.util.ArrayList; import java.util.List; +import java.util.Objects; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.content.dto.MetadataValueDTO; /** @@ -38,6 +41,8 @@ public class ExternalDataObject { */ private String displayValue; + private Logger log = LogManager.getLogger(ExternalDataObject.class); + /** * Default constructor */ @@ -143,4 +148,63 @@ public class ExternalDataObject { public void setValue(String value) { this.value = value; } + + /** + * Sort metadata before printing, to help with comparison by eye + * @return + */ + @Override + public String toString() { + List thisMetadata = new ArrayList<>(this.metadata); + thisMetadata.sort(MetadataValueDTO.comparator()); + return "ExternalDataObject{" + + "id='" + id + '\'' + + ", value='" + value + '\'' + + ", source='" + source + '\'' + + ", displayValue='" + displayValue + '\'' + + ", metadata=" + thisMetadata + + '}'; + } + + /** + * Equality test for ExternalDataObject takes into account the fact that we might have + * lists of metadata values which are identical except for sort order, so we sort and compare these + * using a custom comparator. + * @param o The other object ("that") with which to compare this object ("this") + * @return true if objects are identical, false if not + */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ExternalDataObject that = (ExternalDataObject) o; + // Compare *sorted* lists + List thisMetadata = new ArrayList<>(this.metadata); + List thatMetadata = new ArrayList<>(that.metadata); + // Sort both lists using our custom comparator + thisMetadata.sort(MetadataValueDTO.comparator()); + thatMetadata.sort(MetadataValueDTO.comparator()); + + // Return straight comparisons of basic member variables + return Objects.equals(id, that.id) && + Objects.equals(value, that.value) && + Objects.equals(source, that.source) && + Objects.equals(displayValue, that.displayValue) && + // Compare the sorted lists rather than the raw stored metadata + Objects.equals(thisMetadata, thatMetadata); + } + + /** + * Explicit override of Object hashCode() + * @return + */ + @Override + public int hashCode() { + return Objects.hash(id, value, source, metadata, displayValue); + } + } diff --git a/dspace-api/src/main/java/org/dspace/external/provider/impl/OpenAIREFundingDataProvider.java b/dspace-api/src/main/java/org/dspace/external/provider/impl/OpenaireFundingDataProvider.java similarity index 94% rename from dspace-api/src/main/java/org/dspace/external/provider/impl/OpenAIREFundingDataProvider.java rename to dspace-api/src/main/java/org/dspace/external/provider/impl/OpenaireFundingDataProvider.java index 8ca5b7c0ea..62cef508c5 100644 --- a/dspace-api/src/main/java/org/dspace/external/provider/impl/OpenAIREFundingDataProvider.java +++ b/dspace-api/src/main/java/org/dspace/external/provider/impl/OpenaireFundingDataProvider.java @@ -31,7 +31,7 @@ import eu.openaire.oaf.model.base.Project; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.dspace.content.dto.MetadataValueDTO; -import org.dspace.external.OpenAIRERestConnector; +import org.dspace.external.OpenaireRestConnector; import org.dspace.external.model.ExternalDataObject; import org.dspace.external.provider.AbstractExternalDataProvider; import org.dspace.importer.external.metadatamapping.MetadataFieldConfig; @@ -39,13 +39,13 @@ import org.springframework.beans.factory.annotation.Autowired; /** * This class is the implementation of the ExternalDataProvider interface that - * will deal with the OpenAIRE External Data lookup + * will deal with the Openaire External Data lookup * * @author paulo-graca */ -public class OpenAIREFundingDataProvider extends AbstractExternalDataProvider { +public class OpenaireFundingDataProvider extends AbstractExternalDataProvider { - private static Logger log = org.apache.logging.log4j.LogManager.getLogger(OpenAIREFundingDataProvider.class); + private static Logger log = org.apache.logging.log4j.LogManager.getLogger(OpenaireFundingDataProvider.class); /** * GrantAgreement prefix @@ -75,7 +75,7 @@ public class OpenAIREFundingDataProvider extends AbstractExternalDataProvider { /** * Connector to handle token and requests */ - protected OpenAIRERestConnector connector; + protected OpenaireRestConnector connector; protected Map metadataFields; @@ -93,7 +93,7 @@ public class OpenAIREFundingDataProvider extends AbstractExternalDataProvider { // characters that must be escaped for the <:entry-id> String decodedId = new String(Base64.getDecoder().decode(id)); if (!isValidProjectURI(decodedId)) { - log.error("Invalid ID for OpenAIREFunding - " + id); + log.error("Invalid ID for OpenaireFunding - " + id); return Optional.empty(); } Response response = searchByProjectURI(decodedId); @@ -101,7 +101,7 @@ public class OpenAIREFundingDataProvider extends AbstractExternalDataProvider { try { if (response.getHeader() != null && Integer.parseInt(response.getHeader().getTotal()) > 0) { Project project = response.getResults().getResult().get(0).getMetadata().getEntity().getProject(); - ExternalDataObject externalDataObject = new OpenAIREFundingDataProvider + ExternalDataObject externalDataObject = new OpenaireFundingDataProvider .ExternalDataObjectBuilder(project) .setId(generateProjectURI(project)) .setSource(sourceIdentifier) @@ -123,7 +123,7 @@ public class OpenAIREFundingDataProvider extends AbstractExternalDataProvider { limit = LIMIT_DEFAULT; } - // OpenAIRE uses pages and first page starts with 1 + // Openaire uses pages and first page starts with 1 int page = (start / limit) + 1; // escaping query @@ -148,7 +148,7 @@ public class OpenAIREFundingDataProvider extends AbstractExternalDataProvider { if (projects.size() > 0) { return projects.stream() - .map(project -> new OpenAIREFundingDataProvider + .map(project -> new OpenaireFundingDataProvider .ExternalDataObjectBuilder(project) .setId(generateProjectURI(project)) .setSource(sourceIdentifier) @@ -176,24 +176,24 @@ public class OpenAIREFundingDataProvider extends AbstractExternalDataProvider { * Generic setter for the sourceIdentifier * * @param sourceIdentifier The sourceIdentifier to be set on this - * OpenAIREFunderDataProvider + * OpenaireFunderDataProvider */ @Autowired(required = true) public void setSourceIdentifier(String sourceIdentifier) { this.sourceIdentifier = sourceIdentifier; } - public OpenAIRERestConnector getConnector() { + public OpenaireRestConnector getConnector() { return connector; } /** - * Generic setter for OpenAIRERestConnector + * Generic setter for OpenaireRestConnector * * @param connector */ @Autowired(required = true) - public void setConnector(OpenAIRERestConnector connector) { + public void setConnector(OpenaireRestConnector connector) { this.connector = connector; } @@ -219,7 +219,7 @@ public class OpenAIREFundingDataProvider extends AbstractExternalDataProvider { } /** - * This method returns an URI based on OpenAIRE 3.0 guidelines + * This method returns an URI based on Openaire 3.0 guidelines * https://guidelines.openaire.eu/en/latest/literature/field_projectid.html that * can be used as an ID if is there any missing part, that part it will be * replaced by the character '+' @@ -281,7 +281,7 @@ public class OpenAIREFundingDataProvider extends AbstractExternalDataProvider { } /** - * OpenAIRE Funding External Data Builder Class + * Openaire Funding External Data Builder Class * * @author pgraca */ diff --git a/dspace-api/src/main/java/org/dspace/external/provider/impl/OrcidPublicationDataProvider.java b/dspace-api/src/main/java/org/dspace/external/provider/impl/OrcidPublicationDataProvider.java index 4fdf15a8a3..05dabe0a5b 100644 --- a/dspace-api/src/main/java/org/dspace/external/provider/impl/OrcidPublicationDataProvider.java +++ b/dspace-api/src/main/java/org/dspace/external/provider/impl/OrcidPublicationDataProvider.java @@ -28,6 +28,8 @@ import org.apache.commons.collections.CollectionUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.math.NumberUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.content.Item; import org.dspace.content.MetadataFieldName; import org.dspace.content.dto.MetadataValueDTO; @@ -63,13 +65,11 @@ import org.orcid.jaxb.model.v3.release.record.WorkTitle; import org.orcid.jaxb.model.v3.release.record.summary.WorkGroup; import org.orcid.jaxb.model.v3.release.record.summary.WorkSummary; import org.orcid.jaxb.model.v3.release.record.summary.Works; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; /** * Implementation of {@link ExternalDataProvider} that search for all the works - * of the profile with the given orcid id that hava a source other than DSpace. + * of the profile with the given orcid id that have a source other than DSpace. * The id of the external data objects returned by the methods of this class is * the concatenation of the orcid id and the put code associated with the * publication, separated by :: (example 0000-0000-0123-4567::123456) @@ -79,7 +79,7 @@ import org.springframework.beans.factory.annotation.Autowired; */ public class OrcidPublicationDataProvider extends AbstractExternalDataProvider { - private final static Logger LOGGER = LoggerFactory.getLogger(OrcidPublicationDataProvider.class); + private final static Logger LOGGER = LogManager.getLogger(); /** * Examples of valid ORCID IDs: @@ -335,7 +335,8 @@ public class OrcidPublicationDataProvider extends AbstractExternalDataProvider { try { addMetadataValuesFromCitation(externalDataObject, work.getWorkCitation()); } catch (Exception e) { - LOGGER.error("An error occurs reading the following citation: " + work.getWorkCitation().getCitation(), e); + LOGGER.error("An error occurs reading the following citation: {}", + work.getWorkCitation().getCitation(), e); } return externalDataObject; @@ -484,7 +485,7 @@ public class OrcidPublicationDataProvider extends AbstractExternalDataProvider { private boolean hasRole(Contributor contributor, ContributorRole role) { ContributorAttributes attributes = contributor.getContributorAttributes(); - return attributes != null ? role.equals(attributes.getContributorRole()) : false; + return attributes != null ? role.value().equals(attributes.getContributorRole()) : false; } private Optional getContributorName(Contributor contributor) { diff --git a/dspace-api/src/main/java/org/dspace/external/provider/impl/SHERPAv2JournalISSNDataProvider.java b/dspace-api/src/main/java/org/dspace/external/provider/impl/SHERPAv2JournalISSNDataProvider.java index 9e61b9ac2a..11d8f692af 100644 --- a/dspace-api/src/main/java/org/dspace/external/provider/impl/SHERPAv2JournalISSNDataProvider.java +++ b/dspace-api/src/main/java/org/dspace/external/provider/impl/SHERPAv2JournalISSNDataProvider.java @@ -26,7 +26,7 @@ import org.dspace.external.provider.AbstractExternalDataProvider; /** * This class is the implementation of the ExternalDataProvider interface that will deal with SherpaJournal External - * data lookups based on ISSN (to match functinoality offered by legacy SHERPASubmitService for policy lookups + * data lookups based on ISSN (to match functionality offered by legacy SHERPASubmitService for policy lookups * at the time of submission). * This provider is a refactored version of SherpaJournalDataPublisher, rewritten to work with SHERPA v2 API * diff --git a/dspace-api/src/main/java/org/dspace/external/provider/orcid/xml/Converter.java b/dspace-api/src/main/java/org/dspace/external/provider/orcid/xml/Converter.java index 756b8654f2..578db6c567 100644 --- a/dspace-api/src/main/java/org/dspace/external/provider/orcid/xml/Converter.java +++ b/dspace-api/src/main/java/org/dspace/external/provider/orcid/xml/Converter.java @@ -9,13 +9,13 @@ package org.dspace.external.provider.orcid.xml; import java.io.InputStream; import java.net.URISyntaxException; -import javax.xml.bind.JAXBContext; -import javax.xml.bind.JAXBException; -import javax.xml.bind.Unmarshaller; import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; +import jakarta.xml.bind.JAXBContext; +import jakarta.xml.bind.JAXBException; +import jakarta.xml.bind.Unmarshaller; import org.xml.sax.SAXException; /** diff --git a/dspace-api/src/main/java/org/dspace/external/service/ExternalDataService.java b/dspace-api/src/main/java/org/dspace/external/service/ExternalDataService.java index 53423395e3..4c75a778d1 100644 --- a/dspace-api/src/main/java/org/dspace/external/service/ExternalDataService.java +++ b/dspace-api/src/main/java/org/dspace/external/service/ExternalDataService.java @@ -58,7 +58,7 @@ public interface ExternalDataService { public List searchExternalDataObjects(String source, String query, int start, int limit); /** - * This method wil return the total amount of results that will be found for the given query in the given source + * This method will return the total amount of results that will be found for the given query in the given source * @param source The source in which the query will happen to return the number of results * @param query The query to be ran in this source to retrieve the total amount of results * @return The total amount of results that can be returned for this query in the given source diff --git a/dspace-api/src/main/java/org/dspace/external/service/impl/ExternalDataServiceImpl.java b/dspace-api/src/main/java/org/dspace/external/service/impl/ExternalDataServiceImpl.java index f91ea00cac..59cbe4f9d0 100644 --- a/dspace-api/src/main/java/org/dspace/external/service/impl/ExternalDataServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/external/service/impl/ExternalDataServiceImpl.java @@ -13,6 +13,8 @@ import java.util.Optional; import java.util.stream.Collectors; import org.apache.logging.log4j.Logger; +import org.dspace.app.suggestion.SuggestionProvider; +import org.dspace.app.suggestion.SuggestionService; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Collection; import org.dspace.content.Item; @@ -44,6 +46,9 @@ public class ExternalDataServiceImpl implements ExternalDataService { @Autowired private WorkspaceItemService workspaceItemService; + @Autowired + private SuggestionService suggestionService; + @Override public Optional getExternalDataObject(String source, String id) { ExternalDataProvider provider = getExternalDataProvider(source); @@ -105,6 +110,16 @@ public class ExternalDataServiceImpl implements ExternalDataService { log.info(LogHelper.getHeader(context, "create_item_from_externalDataObject", "Created item" + "with id: " + item.getID() + " from source: " + externalDataObject.getSource() + " with identifier: " + externalDataObject.getId())); + try { + List providers = suggestionService.getSuggestionProviders(); + if (providers != null) { + for (SuggestionProvider p : providers) { + p.flagRelatedSuggestionsAsProcessed(context, externalDataObject); + } + } + } catch (Exception e) { + log.error("Got problems with the solr suggestion storage service: " + e.getMessage(), e); + } return workspaceItem; } diff --git a/dspace-api/src/main/java/org/dspace/google/GoogleAsyncEventListener.java b/dspace-api/src/main/java/org/dspace/google/GoogleAsyncEventListener.java index c1c59acf4a..f5b27fcbd4 100644 --- a/dspace-api/src/main/java/org/dspace/google/GoogleAsyncEventListener.java +++ b/dspace-api/src/main/java/org/dspace/google/GoogleAsyncEventListener.java @@ -13,9 +13,9 @@ import java.util.Iterator; import java.util.List; import java.util.UUID; import java.util.stream.Collectors; -import javax.annotation.PostConstruct; -import javax.servlet.http.HttpServletRequest; +import jakarta.annotation.PostConstruct; +import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.collections.Buffer; import org.apache.commons.collections.BufferUtils; import org.apache.commons.collections.buffer.CircularFifoBuffer; @@ -37,7 +37,7 @@ import org.springframework.beans.factory.annotation.Autowired; /** * Notifies Google Analytics of Bitstream VIEW events. These events are stored in memory and then - * asynchronously processed by a single seperate thread. + * asynchronously processed by a single separate thread. * * @author April Herron * @author Luca Giamminonni @@ -142,7 +142,7 @@ public class GoogleAsyncEventListener extends AbstractUsageEventListener { /** * Client ID, should uniquely identify the user or device. If we have an * X-CORRELATION-ID header or a session ID for the user, then lets use it, - * othwerwise generate a UUID. + * otherwise generate a UUID. */ private String getClientId(UsageEvent usageEvent) { if (usageEvent.getRequest().getHeader("X-CORRELATION-ID") != null) { diff --git a/dspace-api/src/main/java/org/dspace/google/GoogleRecorderEventListener.java b/dspace-api/src/main/java/org/dspace/google/GoogleRecorderEventListener.java index 4159661b1c..fb4e9c04de 100644 --- a/dspace-api/src/main/java/org/dspace/google/GoogleRecorderEventListener.java +++ b/dspace-api/src/main/java/org/dspace/google/GoogleRecorderEventListener.java @@ -13,8 +13,8 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import java.util.UUID; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; import org.apache.http.NameValuePair; import org.apache.http.client.entity.UrlEncodedFormEntity; diff --git a/dspace-api/src/main/java/org/dspace/google/client/GoogleAnalyticsClientImpl.java b/dspace-api/src/main/java/org/dspace/google/client/GoogleAnalyticsClientImpl.java index b5ee1806cd..915bd25b06 100644 --- a/dspace-api/src/main/java/org/dspace/google/client/GoogleAnalyticsClientImpl.java +++ b/dspace-api/src/main/java/org/dspace/google/client/GoogleAnalyticsClientImpl.java @@ -19,9 +19,9 @@ 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.HttpClients; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.google.GoogleAnalyticsEvent; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * Implementation of {@link GoogleAnalyticsClient}. @@ -31,7 +31,7 @@ import org.slf4j.LoggerFactory; */ public class GoogleAnalyticsClientImpl implements GoogleAnalyticsClient { - private static final Logger LOGGER = LoggerFactory.getLogger(GoogleAnalyticsClientImpl.class); + private static final Logger LOGGER = LogManager.getLogger(); private final String keyPrefix; diff --git a/dspace-api/src/main/java/org/dspace/handle/Handle.java b/dspace-api/src/main/java/org/dspace/handle/Handle.java index c35511353a..29182ad56c 100644 --- a/dspace-api/src/main/java/org/dspace/handle/Handle.java +++ b/dspace-api/src/main/java/org/dspace/handle/Handle.java @@ -7,17 +7,16 @@ */ package org.dspace.handle; -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 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 org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.dspace.content.DSpaceObject; diff --git a/dspace-api/src/main/java/org/dspace/handle/HandleServiceImpl.java b/dspace-api/src/main/java/org/dspace/handle/HandleServiceImpl.java index aa730fe2b1..5f511875d1 100644 --- a/dspace-api/src/main/java/org/dspace/handle/HandleServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/handle/HandleServiceImpl.java @@ -312,7 +312,7 @@ public class HandleServiceImpl implements HandleService { Handle dbHandle = findHandleInternal(context, handle); if (dbHandle != null) { // Check if we have to remove the handle from the current handle list - // or if object is alreday deleted. + // or if object is already deleted. if (dbHandle.getDSpaceObject() != null) { // Remove the old handle from the current handle list dbHandle.getDSpaceObject().getHandles().remove(dbHandle); diff --git a/dspace-api/src/main/java/org/dspace/handle/dao/impl/HandleDAOImpl.java b/dspace-api/src/main/java/org/dspace/handle/dao/impl/HandleDAOImpl.java index 71bb798ae3..495f1f05a4 100644 --- a/dspace-api/src/main/java/org/dspace/handle/dao/impl/HandleDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/handle/dao/impl/HandleDAOImpl.java @@ -13,11 +13,11 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.Collections; import java.util.List; -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.content.DSpaceObject; import org.dspace.core.AbstractHibernateDAO; import org.dspace.core.Context; @@ -91,10 +91,9 @@ public class HandleDAOImpl extends AbstractHibernateDAO implements Handl @Override public long countHandlesByPrefix(Context context, String prefix) throws SQLException { CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); - CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Handle.class); + CriteriaQuery criteriaQuery = criteriaBuilder.createQuery(Long.class); Root handleRoot = criteriaQuery.from(Handle.class); - criteriaQuery.select(handleRoot); criteriaQuery.where(criteriaBuilder.like(handleRoot.get(Handle_.handle), prefix + "%")); return countLong(context, criteriaQuery, criteriaBuilder, handleRoot); } @@ -137,7 +136,7 @@ public class HandleDAOImpl extends AbstractHibernateDAO implements Handl // Find the next value in our sequence (based on DB dialect) try (PreparedStatement preparedStatement = connection - .prepareStatement(dialect.getSequenceNextValString(HANDLE_SEQUENCE))) { + .prepareStatement("SELECT nextval('" + HANDLE_SEQUENCE + "')")) { // Execute query and return results try (ResultSet resultSet = preparedStatement.executeQuery()) { if (resultSet.next()) { diff --git a/dspace-api/src/main/java/org/dspace/handle/hdlresolver/HdlResolverDTO.java b/dspace-api/src/main/java/org/dspace/handle/hdlresolver/HdlResolverDTO.java index fe50bba813..1973b92522 100644 --- a/dspace-api/src/main/java/org/dspace/handle/hdlresolver/HdlResolverDTO.java +++ b/dspace-api/src/main/java/org/dspace/handle/hdlresolver/HdlResolverDTO.java @@ -57,7 +57,7 @@ public class HdlResolverDTO { } /** - * Returns the splitted String of the resource-path + * Returns the split String of the resource-path * * @return */ diff --git a/dspace-api/src/main/java/org/dspace/harvest/HarvestThread.java b/dspace-api/src/main/java/org/dspace/harvest/HarvestThread.java index 52498558d4..5947953ef5 100644 --- a/dspace-api/src/main/java/org/dspace/harvest/HarvestThread.java +++ b/dspace-api/src/main/java/org/dspace/harvest/HarvestThread.java @@ -58,12 +58,12 @@ public class HarvestThread extends Thread { } catch (RuntimeException e) { log.error("Runtime exception in thread: " + this.toString()); log.error(e.getMessage() + " " + e.getCause()); - hc.setHarvestMessage("Runtime error occured while generating an OAI response"); + hc.setHarvestMessage("Runtime error occurred while generating an OAI response"); hc.setHarvestStatus(HarvestedCollection.STATUS_UNKNOWN_ERROR); } catch (Exception ex) { log.error("General exception in thread: " + this.toString()); log.error(ex.getMessage() + " " + ex.getCause()); - hc.setHarvestMessage("Error occured while generating an OAI response"); + hc.setHarvestMessage("Error occurred while generating an OAI response"); hc.setHarvestStatus(HarvestedCollection.STATUS_UNKNOWN_ERROR); } finally { try { diff --git a/dspace-api/src/main/java/org/dspace/harvest/HarvestedCollection.java b/dspace-api/src/main/java/org/dspace/harvest/HarvestedCollection.java index 9fe2bed67d..dc65ce2542 100644 --- a/dspace-api/src/main/java/org/dspace/harvest/HarvestedCollection.java +++ b/dspace-api/src/main/java/org/dspace/harvest/HarvestedCollection.java @@ -8,20 +8,20 @@ package org.dspace.harvest; 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.OneToOne; -import javax.persistence.SequenceGenerator; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; -import javax.persistence.Transient; +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.OneToOne; +import jakarta.persistence.SequenceGenerator; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; +import jakarta.persistence.Transient; import org.dspace.content.Collection; import org.dspace.core.Context; import org.dspace.core.ReloadableEntity; diff --git a/dspace-api/src/main/java/org/dspace/harvest/HarvestedItem.java b/dspace-api/src/main/java/org/dspace/harvest/HarvestedItem.java index 343347136b..943543f405 100644 --- a/dspace-api/src/main/java/org/dspace/harvest/HarvestedItem.java +++ b/dspace-api/src/main/java/org/dspace/harvest/HarvestedItem.java @@ -8,19 +8,19 @@ package org.dspace.harvest; 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.OneToOne; -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.OneToOne; +import jakarta.persistence.SequenceGenerator; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import org.dspace.content.Item; import org.dspace.core.Context; import org.dspace.core.ReloadableEntity; diff --git a/dspace-api/src/main/java/org/dspace/harvest/dao/impl/HarvestedCollectionDAOImpl.java b/dspace-api/src/main/java/org/dspace/harvest/dao/impl/HarvestedCollectionDAOImpl.java index 95a0bdf216..ab942a74ef 100644 --- a/dspace-api/src/main/java/org/dspace/harvest/dao/impl/HarvestedCollectionDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/harvest/dao/impl/HarvestedCollectionDAOImpl.java @@ -11,11 +11,11 @@ import java.sql.SQLException; import java.util.Date; import java.util.LinkedList; import java.util.List; -import javax.persistence.criteria.CriteriaBuilder; -import javax.persistence.criteria.CriteriaQuery; -import javax.persistence.criteria.Predicate; -import javax.persistence.criteria.Root; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Predicate; +import jakarta.persistence.criteria.Root; import org.dspace.content.Collection; import org.dspace.core.AbstractHibernateDAO; import org.dspace.core.Context; @@ -46,7 +46,7 @@ public class HarvestedCollectionDAOImpl extends AbstractHibernateDAO harvestedCollectionRoot = criteriaQuery.from(HarvestedCollection.class); criteriaQuery.select(harvestedCollectionRoot); - List orderList = new LinkedList<>(); + List orderList = new LinkedList<>(); orderList.add(criteriaBuilder.desc(harvestedCollectionRoot.get(HarvestedCollection_.lastHarvested))); criteriaQuery.orderBy(orderList); @@ -61,7 +61,7 @@ public class HarvestedCollectionDAOImpl extends AbstractHibernateDAO harvestedCollectionRoot = criteriaQuery.from(HarvestedCollection.class); criteriaQuery.select(harvestedCollectionRoot); - List orderList = new LinkedList<>(); + List orderList = new LinkedList<>(); orderList.add(criteriaBuilder.asc(harvestedCollectionRoot.get(HarvestedCollection_.lastHarvested))); criteriaQuery.orderBy(orderList); @@ -138,7 +138,7 @@ public class HarvestedCollectionDAOImpl extends AbstractHibernateDAO orderList = new LinkedList<>(); + List orderList = new LinkedList<>(); orderList.add(criteriaBuilder.asc(harvestedCollectionRoot.get(HarvestedCollection_.lastHarvested))); criteriaQuery.orderBy(orderList); @@ -150,11 +150,10 @@ public class HarvestedCollectionDAOImpl extends AbstractHibernateDAO criteriaQuery = criteriaBuilder.createQuery(Long.class); - Root harvestedCollectionRoot = criteriaQuery.from(HarvestedCollection.class); + criteriaQuery.select(criteriaBuilder.count(harvestedCollectionRoot)); return count(context, criteriaQuery, criteriaBuilder, harvestedCollectionRoot); } diff --git a/dspace-api/src/main/java/org/dspace/harvest/dao/impl/HarvestedItemDAOImpl.java b/dspace-api/src/main/java/org/dspace/harvest/dao/impl/HarvestedItemDAOImpl.java index 9e9838be6c..bb7ff0ee7c 100644 --- a/dspace-api/src/main/java/org/dspace/harvest/dao/impl/HarvestedItemDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/harvest/dao/impl/HarvestedItemDAOImpl.java @@ -8,11 +8,11 @@ package org.dspace.harvest.dao.impl; import java.sql.SQLException; -import javax.persistence.criteria.CriteriaBuilder; -import javax.persistence.criteria.CriteriaQuery; -import javax.persistence.criteria.Join; -import javax.persistence.criteria.Root; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Join; +import jakarta.persistence.criteria.Root; import org.dspace.content.Collection; import org.dspace.content.Item; import org.dspace.content.Item_; diff --git a/dspace-api/src/main/java/org/dspace/health/Report.java b/dspace-api/src/main/java/org/dspace/health/Report.java index ebb2ffd688..d083a45b13 100644 --- a/dspace-api/src/main/java/org/dspace/health/Report.java +++ b/dspace-api/src/main/java/org/dspace/health/Report.java @@ -15,8 +15,8 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map.Entry; import java.util.StringTokenizer; -import javax.mail.MessagingException; +import jakarta.mail.MessagingException; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.DefaultParser; import org.apache.commons.cli.HelpFormatter; diff --git a/dspace-api/src/main/java/org/dspace/identifier/DOI.java b/dspace-api/src/main/java/org/dspace/identifier/DOI.java index e99472e45c..2e699e990f 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/DOI.java +++ b/dspace-api/src/main/java/org/dspace/identifier/DOI.java @@ -8,17 +8,16 @@ package org.dspace.identifier; -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.OneToOne; -import javax.persistence.SequenceGenerator; -import javax.persistence.Table; - +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.OneToOne; +import jakarta.persistence.SequenceGenerator; +import jakarta.persistence.Table; import org.dspace.content.DSpaceObject; import org.dspace.core.Context; import org.dspace.core.ReloadableEntity; diff --git a/dspace-api/src/main/java/org/dspace/identifier/DOIIdentifierProvider.java b/dspace-api/src/main/java/org/dspace/identifier/DOIIdentifierProvider.java index b70eda960d..29c6a5bb8f 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/DOIIdentifierProvider.java +++ b/dspace-api/src/main/java/org/dspace/identifier/DOIIdentifierProvider.java @@ -14,6 +14,8 @@ import java.util.Arrays; import java.util.List; import java.util.Objects; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.authorize.AuthorizeException; import org.dspace.content.DSpaceObject; import org.dspace.content.Item; @@ -30,8 +32,6 @@ import org.dspace.identifier.doi.DOIIdentifierException; import org.dspace.identifier.doi.DOIIdentifierNotApplicableException; import org.dspace.identifier.service.DOIService; import org.dspace.services.factory.DSpaceServicesFactory; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; /** @@ -49,7 +49,7 @@ import org.springframework.beans.factory.annotation.Autowired; * @author Kim Shepherd */ public class DOIIdentifierProvider extends FilteredIdentifierProvider { - private static final Logger log = LoggerFactory.getLogger(DOIIdentifierProvider.class); + private static final Logger log = LogManager.getLogger(); /** * A DOIConnector connects the DOIIdentifierProvider to the API of the DOI @@ -286,8 +286,8 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { try { doiRow = loadOrCreateDOI(context, dso, doi, filter); } catch (SQLException ex) { - log.error("Error in databse connection: " + ex.getMessage()); - throw new RuntimeException("Error in database conncetion.", ex); + log.error("Error in database connection: {}", ex::getMessage); + throw new RuntimeException("Error in database connection.", ex); } if (DELETED.equals(doiRow.getStatus()) || @@ -473,7 +473,7 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { /** * Update metadata for a registered object - * If the DOI for hte item already exists, *always* skip the filter since it should only be used for + * If the DOI for the item already exists, *always* skip the filter since it should only be used for * allowing / disallowing reservation and registration, not metadata updates or deletions * * @param context - DSpace context @@ -492,7 +492,7 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { if (doiService.findDOIByDSpaceObject(context, dso) != null) { // We can skip the filter here since we know the DOI already exists for the item - log.debug("updateMetadata: found DOIByDSpaceObject: " + + log.debug("updateMetadata: found DOIByDSpaceObject: {}", doiService.findDOIByDSpaceObject(context, dso).getDoi()); updateFilter = DSpaceServicesFactory.getInstance().getServiceManager().getServiceByName( "always_true_filter", TrueFilter.class); @@ -501,7 +501,7 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { DOI doiRow = loadOrCreateDOI(context, dso, doi, updateFilter); if (PENDING.equals(doiRow.getStatus()) || MINTED.equals(doiRow.getStatus())) { - log.info("Not updating metadata for PENDING or MINTED doi: " + doi); + log.info("Not updating metadata for PENDING or MINTED doi: {}", doi); return; } @@ -525,7 +525,7 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { /** * Update metadata for a registered object in the DOI Connector to update the agency records - * If the DOI for hte item already exists, *always* skip the filter since it should only be used for + * If the DOI for the item already exists, *always* skip the filter since it should only be used for * allowing / disallowing reservation and registration, not metadata updates or deletions * * @param context - DSpace context @@ -611,8 +611,8 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { try { doi = getDOIByObject(context, dso); } catch (SQLException e) { - log.error("Error while attemping to retrieve information about a DOI for " - + contentServiceFactory.getDSpaceObjectService(dso).getTypeText(dso) + " with ID " + dso.getID() + "."); + log.error("Error while attempting to retrieve information about a DOI for {} with ID {}.", + contentServiceFactory.getDSpaceObjectService(dso).getTypeText(dso), dso.getID()); throw new RuntimeException("Error while attempting to retrieve " + "information about a DOI for " + contentServiceFactory.getDSpaceObjectService(dso).getTypeText(dso) + " with ID " + dso.getID() + ".", e); @@ -624,7 +624,7 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { } catch (SQLException e) { log.error("Error while creating new DOI for Object of " + - "ResourceType {} with id {}.", dso.getType(), dso.getID()); + "ResourceType {} with id {}.", dso::getType, dso::getID); throw new RuntimeException("Error while attempting to create a " + "new DOI for " + contentServiceFactory.getDSpaceObjectService(dso).getTypeText(dso) + " with ID " + dso.getID() + ".", e); @@ -709,9 +709,9 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { doi = getDOIByObject(context, dso); } } catch (SQLException ex) { - log.error("Error while attemping to retrieve information about a DOI for " + - contentServiceFactory.getDSpaceObjectService(dso).getTypeText(dso) + - " with ID " + dso.getID() + ".", ex); + log.error("Error while attempting to retrieve information about a DOI for {} with ID {}.", + contentServiceFactory.getDSpaceObjectService(dso).getTypeText(dso), + dso.getID(), ex); throw new RuntimeException("Error while attempting to retrieve " + "information about a DOI for " + contentServiceFactory.getDSpaceObjectService(dso).getTypeText(dso) + " with ID " + dso.getID() + ".", ex); @@ -726,17 +726,17 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { doi = getDOIOutOfObject(dso); } } catch (AuthorizeException ex) { - log.error("Error while removing a DOI out of the metadata of an " + - contentServiceFactory.getDSpaceObjectService(dso).getTypeText(dso) + - " with ID " + dso.getID() + ".", ex); + log.error("Error while removing a DOI out of the metadata of an {} with ID {}.", + contentServiceFactory.getDSpaceObjectService(dso).getTypeText(dso), + dso.getID(), ex); throw new RuntimeException("Error while removing a DOI out of the metadata of an " + contentServiceFactory.getDSpaceObjectService(dso).getTypeText(dso) + " with ID " + dso.getID() + ".", ex); } catch (SQLException ex) { - log.error("Error while removing a DOI out of the metadata of an " + - contentServiceFactory.getDSpaceObjectService(dso).getTypeText(dso) + - " with ID " + dso.getID() + ".", ex); + log.error("Error while removing a DOI out of the metadata of an {} with ID {}.", + contentServiceFactory.getDSpaceObjectService(dso).getTypeText(dso), + dso.getID(), ex); throw new RuntimeException("Error while removing a DOI out of the " + "metadata of an " + contentServiceFactory.getDSpaceObjectService(dso).getTypeText(dso) + " with ID " + dso.getID() + ".", ex); @@ -779,8 +779,8 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { throw new DOIIdentifierException("Not authorized to delete DOI.", ex, DOIIdentifierException.UNAUTHORIZED_METADATA_MANIPULATION); } catch (SQLException ex) { - log.error("SQLException occurred while deleting a DOI out of an item: " - + ex.getMessage()); + log.error("SQLException occurred while deleting a DOI out of an item: {}", + ex::getMessage); throw new RuntimeException("Error while deleting a DOI out of the " + "metadata of an Item " + dso.getID(), ex); } @@ -826,8 +826,9 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { DOIIdentifierException.DOI_DOES_NOT_EXIST); } if (!TO_BE_DELETED.equals(doiRow.getStatus())) { - log.error("This identifier: {} couldn't be deleted. Delete it first from metadata.", - DOI.SCHEME + doiRow.getDoi()); + log.error("This identifier: " + DOI.SCHEME + + "{} couldn't be deleted. Delete it first from metadata.", + doiRow::getDoi); throw new IllegalArgumentException("Couldn't delete this identifier:" + DOI.SCHEME + doiRow.getDoi() + ". Delete it first from metadata."); @@ -863,7 +864,7 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { } if (doiRow.getDSpaceObject() == null) { - log.error("Found DOI " + doi + " in database, but no assigned Object could be found."); + log.error("Found DOI {} in database, but no assigned Object could be found.", doi); throw new IllegalStateException("Found DOI " + doi + " in database, but no assigned Object could be found."); } @@ -890,8 +891,9 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { } if (doiRow.getDoi() == null) { - log.error("A DOI with an empty doi column was found in the database. DSO-Type: " + - contentServiceFactory.getDSpaceObjectService(dso).getTypeText(dso) + ", ID: " + dso.getID() + "."); + log.error("A DOI with an empty doi column was found in the database. DSO-Type: {}, ID: {}.", + contentServiceFactory.getDSpaceObjectService(dso).getTypeText(dso), + dso.getID()); throw new IllegalStateException("A DOI with an empty doi column was found in the database. DSO-Type: " + contentServiceFactory.getDSpaceObjectService(dso).getTypeText(dso) + ", ID: " + dso.getID() + "."); } @@ -1134,13 +1136,13 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { if (contentServiceFactory.getDSpaceObjectService(dso).getTypeText(dso).equals("ITEM")) { try { boolean result = filter.getResult(context, (Item) dso); - log.debug("Result of filter for " + dso.getHandle() + " is " + result); + log.debug("Result of filter for {} is {}", dso.getHandle(), result); if (!result) { throw new DOIIdentifierNotApplicableException("Item " + dso.getHandle() + " was evaluated as 'false' by the item filter, not minting"); } } catch (LogicalStatementException e) { - log.error("Error evaluating item with logical filter: " + e.getLocalizedMessage()); + log.error("Error evaluating item with logical filter: {}", e::getLocalizedMessage); throw new DOIIdentifierNotApplicableException(e); } } else { diff --git a/dspace-api/src/main/java/org/dspace/identifier/DOIServiceImpl.java b/dspace-api/src/main/java/org/dspace/identifier/DOIServiceImpl.java index 99643db33f..fbbb1062ca 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/DOIServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/identifier/DOIServiceImpl.java @@ -57,6 +57,16 @@ public class DOIServiceImpl implements DOIService { return doiDAO.create(context, new DOI()); } + @Override + public void delete(Context context, DOI doi) throws SQLException { + doiDAO.delete(context, doi); + } + + @Override + public List findAll(Context context) throws SQLException { + return doiDAO.findAll(context, DOI.class); + } + @Override public DOI findByDoi(Context context, String doi) throws SQLException { return doiDAO.findByDoi(context, doi); diff --git a/dspace-api/src/main/java/org/dspace/identifier/DataCiteXMLCreator.java b/dspace-api/src/main/java/org/dspace/identifier/DataCiteXMLCreator.java index ae2cd248d4..0160c8adca 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/DataCiteXMLCreator.java +++ b/dspace-api/src/main/java/org/dspace/identifier/DataCiteXMLCreator.java @@ -13,6 +13,8 @@ import java.sql.SQLException; import java.util.HashMap; import java.util.Map; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.authorize.AuthorizeException; import org.dspace.content.DSpaceObject; import org.dspace.content.crosswalk.CrosswalkException; @@ -25,8 +27,6 @@ import org.dspace.services.ConfigurationService; import org.dspace.utils.DSpace; import org.jdom2.Element; import org.jdom2.output.XMLOutputter; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * Provide XML based metadata crosswalk for EZID Identifier provider module. @@ -36,9 +36,9 @@ import org.slf4j.LoggerFactory; public class DataCiteXMLCreator { /** - * log4j category + * logging category */ - private static final Logger LOG = LoggerFactory.getLogger(DataCiteXMLCreator.class); + private static final Logger LOG = LogManager.getLogger(); /** * Name of crosswalk to convert metadata into DataCite Metadata Scheme. @@ -70,9 +70,8 @@ public class DataCiteXMLCreator { this.prepareXwalk(); if (!this.xwalk.canDisseminate(dso)) { - LOG.error("Crosswalk " + this.CROSSWALK_NAME - + " cannot disseminate DSO with type " + dso.getType() - + " and ID " + dso.getID() + "."); + LOG.error("Crosswalk {} cannot disseminate DSO with type {} and ID {}.", + this.CROSSWALK_NAME, dso.getType(), dso.getID()); return null; } @@ -98,8 +97,8 @@ public class DataCiteXMLCreator { try { root = xwalk.disseminateElement(context, dso, parameters); } catch (CrosswalkException | IOException | SQLException | AuthorizeException e) { - LOG.error("Exception while crosswalking DSO with type " - + dso.getType() + " and ID " + dso.getID() + ".", e); + LOG.error("Exception while crosswalking DSO with type {} and ID {}.", + dso.getType(), dso.getID(), e); return null; } diff --git a/dspace-api/src/main/java/org/dspace/identifier/EZIDIdentifierProvider.java b/dspace-api/src/main/java/org/dspace/identifier/EZIDIdentifierProvider.java index 78ddeb8f90..ba1688f635 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/EZIDIdentifierProvider.java +++ b/dspace-api/src/main/java/org/dspace/identifier/EZIDIdentifierProvider.java @@ -20,6 +20,8 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.authorize.AuthorizeException; import org.dspace.content.DSpaceObject; import org.dspace.content.Item; @@ -32,8 +34,6 @@ import org.dspace.identifier.ezid.EZIDRequest; import org.dspace.identifier.ezid.EZIDRequestFactory; import org.dspace.identifier.ezid.EZIDResponse; import org.dspace.identifier.ezid.Transform; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; /** @@ -83,7 +83,7 @@ import org.springframework.beans.factory.annotation.Autowired; */ public class EZIDIdentifierProvider extends IdentifierProvider { - private static final Logger log = LoggerFactory.getLogger(EZIDIdentifierProvider.class); + private static final Logger log = LogManager.getLogger(); // Configuration property names static final String CFG_SHOULDER = "identifier.doi.ezid.shoulder"; @@ -184,7 +184,8 @@ public class EZIDIdentifierProvider loadUser(), loadPassword()); response = request.create(identifier, crosswalkMetadata(context, object)); } catch (IdentifierException | IOException | URISyntaxException e) { - log.error("Identifier '{}' not registered: {}", identifier, e.getMessage()); + log.error("Identifier '{}' not registered: {}", + () -> identifier, e::getMessage); return; } @@ -201,7 +202,7 @@ public class EZIDIdentifierProvider } } else { log.error("Identifier '{}' not registered -- EZID returned: {}", - identifier, response.getEZIDStatusValue()); + () -> identifier, response::getEZIDStatusValue); } } @@ -218,7 +219,8 @@ public class EZIDIdentifierProvider metadata.put("_status", "reserved"); response = request.create(identifier, metadata); } catch (IOException | URISyntaxException e) { - log.error("Identifier '{}' not registered: {}", identifier, e.getMessage()); + log.error("Identifier '{}' not registered: {}", + () -> identifier, e::getMessage); return; } @@ -233,7 +235,7 @@ public class EZIDIdentifierProvider } } else { log.error("Identifier '{}' not registered -- EZID returned: {}", - identifier, response.getEZIDStatusValue()); + () -> identifier, response::getEZIDStatusValue); } } @@ -247,7 +249,7 @@ public class EZIDIdentifierProvider try { request = requestFactory.getInstance(loadAuthority(), loadUser(), loadPassword()); } catch (URISyntaxException ex) { - log.error(ex.getMessage()); + log.error(ex::getMessage); throw new IdentifierException("DOI request not sent: " + ex.getMessage()); } @@ -256,18 +258,16 @@ public class EZIDIdentifierProvider try { response = request.mint(crosswalkMetadata(context, dso)); } catch (IOException | URISyntaxException ex) { - log.error("Failed to send EZID request: {}", ex.getMessage()); + log.error("Failed to send EZID request: {}", ex::getMessage); throw new IdentifierException("DOI request not sent: " + ex.getMessage()); } // Good response? if (HttpURLConnection.HTTP_CREATED != response.getHttpStatusCode()) { log.error("EZID server responded: {} {}: {}", - new String[] { - String.valueOf(response.getHttpStatusCode()), - response.getHttpReasonPhrase(), - response.getEZIDStatusValue() - }); + response::getHttpStatusCode, + response::getHttpReasonPhrase, + response::getEZIDStatusValue); throw new IdentifierException("DOI not created: " + response.getHttpReasonPhrase() + ": " @@ -285,7 +285,7 @@ public class EZIDIdentifierProvider log.info("Created {}", doi); return doi; } else { - log.error("EZID responded: {}", response.getEZIDStatusValue()); + log.error("EZID responded: {}", response::getEZIDStatusValue); throw new IdentifierException("No DOI returned"); } } @@ -302,7 +302,7 @@ public class EZIDIdentifierProvider MD_SCHEMA, DOI_ELEMENT, DOI_QUALIFIER, idToDOI(identifier)); } catch (IdentifierException | SQLException | AuthorizeException | IOException ex) { - log.error(ex.getMessage()); + log.error(ex::getMessage); throw new IdentifierNotResolvableException(ex); } if (!found.hasNext()) { @@ -360,24 +360,24 @@ public class EZIDIdentifierProvider loadUser(), loadPassword()); response = request.delete(DOIToId(id.getValue())); } catch (URISyntaxException e) { - log.error("Bad URI in metadata value: {}", e.getMessage()); + log.error("Bad URI in metadata value: {}", e::getMessage); remainder.add(id.getValue()); skipped++; continue; } catch (IOException e) { - log.error("Failed request to EZID: {}", e.getMessage()); + log.error("Failed request to EZID: {}", e::getMessage); remainder.add(id.getValue()); skipped++; continue; } if (!response.isSuccess()) { - log.error("Unable to delete {} from DataCite: {}", id.getValue(), - response.getEZIDStatusValue()); + log.error("Unable to delete {} from DataCite: {}", id::getValue, + response::getEZIDStatusValue); remainder.add(id.getValue()); skipped++; continue; } - log.info("Deleted {}", id.getValue()); + log.info("Deleted {}", id::getValue); } // delete from item @@ -386,7 +386,7 @@ public class EZIDIdentifierProvider dsoService.addMetadata(context, dso, MD_SCHEMA, DOI_ELEMENT, DOI_QUALIFIER, null, remainder); dsoService.update(context, dso); } catch (SQLException | AuthorizeException e) { - log.error("Failed to re-add identifiers: {}", e.getMessage()); + log.error("Failed to re-add identifiers: {}", e::getMessage); } if (skipped > 0) { @@ -415,25 +415,25 @@ public class EZIDIdentifierProvider loadUser(), loadPassword()); response = request.delete(DOIToId(id.getValue())); } catch (URISyntaxException e) { - log.error("Bad URI in metadata value {}: {}", id.getValue(), e.getMessage()); + log.error("Bad URI in metadata value {}: {}", id::getValue, e::getMessage); remainder.add(id.getValue()); skipped++; continue; } catch (IOException e) { - log.error("Failed request to EZID: {}", e.getMessage()); + log.error("Failed request to EZID: {}", e::getMessage); remainder.add(id.getValue()); skipped++; continue; } if (!response.isSuccess()) { - log.error("Unable to delete {} from DataCite: {}", id.getValue(), - response.getEZIDStatusValue()); + log.error("Unable to delete {} from DataCite: {}", id::getValue, + response::getEZIDStatusValue); remainder.add(id.getValue()); skipped++; continue; } - log.info("Deleted {}", id.getValue()); + log.info("Deleted {}", id::getValue); } // delete from item @@ -442,7 +442,7 @@ public class EZIDIdentifierProvider dsoService.addMetadata(context, dso, MD_SCHEMA, DOI_ELEMENT, DOI_QUALIFIER, null, remainder); dsoService.update(context, dso); } catch (SQLException | AuthorizeException e) { - log.error("Failed to re-add identifiers: {}", e.getMessage()); + log.error("Failed to re-add identifiers: {}", e::getMessage); } if (skipped > 0) { @@ -544,12 +544,10 @@ public class EZIDIdentifierProvider mappedValue = xfrm.transform(value.getValue()); } catch (Exception ex) { log.error("Unable to transform '{}' from {} to {}: {}", - new String[] { - value.getValue(), - value.toString(), - key, - ex.getMessage() - }); + value::getValue, + value::toString, + () -> key, + ex::getMessage); continue; } } else { diff --git a/dspace-api/src/main/java/org/dspace/identifier/HandleIdentifierProvider.java b/dspace-api/src/main/java/org/dspace/identifier/HandleIdentifierProvider.java index 82358362da..54ab9f90a6 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/HandleIdentifierProvider.java +++ b/dspace-api/src/main/java/org/dspace/identifier/HandleIdentifierProvider.java @@ -27,7 +27,6 @@ import org.dspace.handle.service.HandleService; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; /** * The old DSpace handle identifier service, used to create handles or retrieve objects based on their handle @@ -36,7 +35,6 @@ import org.springframework.stereotype.Component; * @author Mark Diggory (markd at atmire dot com) * @author Ben Bosman (ben at atmire dot com) */ -@Component public class HandleIdentifierProvider extends IdentifierProvider { /** * log4j category diff --git a/dspace-api/src/main/java/org/dspace/identifier/VersionedDOIIdentifierProvider.java b/dspace-api/src/main/java/org/dspace/identifier/VersionedDOIIdentifierProvider.java index e5a90907c7..b970c9f06c 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/VersionedDOIIdentifierProvider.java +++ b/dspace-api/src/main/java/org/dspace/identifier/VersionedDOIIdentifierProvider.java @@ -137,8 +137,9 @@ public class VersionedDOIIdentifierProvider extends DOIIdentifierProvider implem loadOrCreateDOI(context, dso, versionedDOI, filter); } catch (SQLException ex) { log.error( - "A problem with the database connection occurd while processing DOI " + versionedDOI + ".", ex); - throw new RuntimeException("A problem with the database connection occured.", ex); + "A problem with the database connection occurred while processing DOI " + versionedDOI + ".", + ex); + throw new RuntimeException("A problem with the database connection occurred.", ex); } return versionedDOI; } @@ -350,14 +351,14 @@ public class VersionedDOIIdentifierProvider extends DOIIdentifierProvider implem changed = true; } } - // reset the metadata if neccessary. + // reset the metadata if necessary. if (changed) { try { itemService.clearMetadata(c, item, MD_SCHEMA, DOI_ELEMENT, DOI_QUALIFIER, Item.ANY); itemService.addMetadata(c, item, MD_SCHEMA, DOI_ELEMENT, DOI_QUALIFIER, null, newIdentifiers); itemService.update(c, item); } catch (SQLException ex) { - throw new RuntimeException("A problem with the database connection occured.", ex); + throw new RuntimeException("A problem with the database connection occurred.", ex); } } } diff --git a/dspace-api/src/main/java/org/dspace/identifier/VersionedHandleIdentifierProvider.java b/dspace-api/src/main/java/org/dspace/identifier/VersionedHandleIdentifierProvider.java index 4f9efd2206..4c8c785cbd 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/VersionedHandleIdentifierProvider.java +++ b/dspace-api/src/main/java/org/dspace/identifier/VersionedHandleIdentifierProvider.java @@ -37,7 +37,6 @@ import org.dspace.versioning.service.VersionHistoryService; import org.dspace.versioning.service.VersioningService; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; /** * @author Fabio Bolognesi (fabio at atmire dot com) @@ -45,7 +44,6 @@ import org.springframework.stereotype.Component; * @author Ben Bosman (ben at atmire dot com) * @author Pascal-Nicolas Becker (dspace at pascal dash becker dot de) */ -@Component public class VersionedHandleIdentifierProvider extends IdentifierProvider implements InitializingBean { /** * log4j category @@ -129,7 +127,7 @@ public class VersionedHandleIdentifierProvider extends IdentifierProvider implem try { versionNumber = Integer.valueOf(versionHandleMatcher.group(1)); } catch (NumberFormatException ex) { - throw new IllegalStateException("Cannot detect the interger value of a digit.", ex); + throw new IllegalStateException("Cannot detect the integer value of a digit.", ex); } // get history @@ -150,7 +148,7 @@ public class VersionedHandleIdentifierProvider extends IdentifierProvider implem try { versionHistoryService.getVersion(context, history, item); } catch (SQLException ex) { - throw new RuntimeException("Problem with the database connection occurd.", ex); + throw new RuntimeException("Problem with the database connection occurred.", ex); } // did we found a version? @@ -186,11 +184,11 @@ public class VersionedHandleIdentifierProvider extends IdentifierProvider implem } catch (SQLException | IOException ex) { throw new RuntimeException("Unable to restore a versioned " + "handle as there was a problem in creating a " - + "neccessary item version: ", ex); + + "necessary item version: ", ex); } catch (AuthorizeException ex) { throw new RuntimeException("Unable to restore a versioned " + "handle as the current user was not allowed to " - + "create a neccessary item version: ", ex); + + "create a necessary item version: ", ex); } return; } diff --git a/dspace-api/src/main/java/org/dspace/identifier/VersionedHandleIdentifierProviderWithCanonicalHandles.java b/dspace-api/src/main/java/org/dspace/identifier/VersionedHandleIdentifierProviderWithCanonicalHandles.java index 9993f78b4d..67278b4db8 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/VersionedHandleIdentifierProviderWithCanonicalHandles.java +++ b/dspace-api/src/main/java/org/dspace/identifier/VersionedHandleIdentifierProviderWithCanonicalHandles.java @@ -32,14 +32,12 @@ import org.dspace.versioning.service.VersionHistoryService; import org.dspace.versioning.service.VersioningService; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; /** * @author Fabio Bolognesi (fabio at atmire dot com) * @author Mark Diggory (markd at atmire dot com) * @author Ben Bosman (ben at atmire dot com) */ -@Component public class VersionedHandleIdentifierProviderWithCanonicalHandles extends IdentifierProvider implements InitializingBean { /** @@ -101,7 +99,7 @@ public class VersionedHandleIdentifierProviderWithCanonicalHandles extends Ident try { history = versionHistoryService.findByItem(context, item); } catch (SQLException ex) { - throw new RuntimeException("A problem with the database connection occured.", ex); + throw new RuntimeException("A problem with the database connection occurred.", ex); } if (history != null) { String canonical = getCanonical(context, item); @@ -110,7 +108,7 @@ public class VersionedHandleIdentifierProviderWithCanonicalHandles extends Ident try { handleService.modifyHandleDSpaceObject(context, canonical, item); } catch (SQLException ex) { - throw new RuntimeException("A problem with the database connection occured.", ex); + throw new RuntimeException("A problem with the database connection occurred.", ex); } Version version; @@ -125,7 +123,7 @@ public class VersionedHandleIdentifierProviderWithCanonicalHandles extends Ident previousItemHandle = handleService.findHandle(context, previous.getItem()); } } catch (SQLException ex) { - throw new RuntimeException("A problem with the database connection occured.", ex); + throw new RuntimeException("A problem with the database connection occurred.", ex); } // we have to ensure the previous item still has a handle @@ -133,8 +131,8 @@ public class VersionedHandleIdentifierProviderWithCanonicalHandles extends Ident if (previous != null) { try { // If we have a reviewer they might not have the - // rights to edit the metadata of thes previous item. - // Temporarly grant them: + // rights to edit the metadata of the previous item. + // Temporarily grant them: context.turnOffAuthorisationSystem(); // Check if our previous item hasn't got a handle anymore. @@ -153,9 +151,9 @@ public class VersionedHandleIdentifierProviderWithCanonicalHandles extends Ident // remove the canonical handle from the previous item's metadata modifyHandleMetadata(context, previous.getItem(), previousItemHandle); } catch (SQLException ex) { - throw new RuntimeException("A problem with the database connection occured.", ex); + throw new RuntimeException("A problem with the database connection occurred.", ex); } catch (AuthorizeException ex) { - // cannot occure, as the authorization system is turned of + // cannot occur, as the authorization system is turned of throw new IllegalStateException("Caught an " + "AuthorizeException while the " + "authorization system was turned off!", ex); @@ -168,7 +166,7 @@ public class VersionedHandleIdentifierProviderWithCanonicalHandles extends Ident // remove all handles from metadata and add the canonical one. modifyHandleMetadata(context, item, getCanonical(id)); } catch (SQLException ex) { - throw new RuntimeException("A problem with the database connection occured.", ex); + throw new RuntimeException("A problem with the database connection occurred.", ex); } catch (AuthorizeException ex) { throw new RuntimeException("The current user is not authorized to change this item.", ex); } @@ -250,7 +248,7 @@ public class VersionedHandleIdentifierProviderWithCanonicalHandles extends Ident Version latest = versionHistoryService.getLatestVersion(context, history); - // if restoring the lastest version: needed to move the canonical + // if restoring the latest version: needed to move the canonical if (latest.getVersionNumber() < versionNumber) { handleService.modifyHandleDSpaceObject(context, canonical, dso); } @@ -364,7 +362,7 @@ public class VersionedHandleIdentifierProviderWithCanonicalHandles extends Ident .getPrevious(context, history, versionHistoryService.getLatestVersion(context, history)) .getItem(); } catch (SQLException ex) { - throw new RuntimeException("A problem with our database connection occured.", ex); + throw new RuntimeException("A problem with our database connection occurred.", ex); } // Modify Canonical: 12345/100 will point to the new item @@ -430,7 +428,7 @@ public class VersionedHandleIdentifierProviderWithCanonicalHandles extends Ident try { previous = versionHistoryService.getPrevious(context, history, version); } catch (SQLException ex) { - throw new RuntimeException("A problem with our database connection occured."); + throw new RuntimeException("A problem with our database connection occurred."); } String canonical = getCanonical(context, previous.getItem()); diff --git a/dspace-api/src/main/java/org/dspace/identifier/dao/impl/DOIDAOImpl.java b/dspace-api/src/main/java/org/dspace/identifier/dao/impl/DOIDAOImpl.java index 784fec1d88..b045061a5f 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/dao/impl/DOIDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/identifier/dao/impl/DOIDAOImpl.java @@ -10,11 +10,11 @@ package org.dspace.identifier.dao.impl; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; -import javax.persistence.criteria.CriteriaBuilder; -import javax.persistence.criteria.CriteriaQuery; -import javax.persistence.criteria.Predicate; -import javax.persistence.criteria.Root; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Predicate; +import jakarta.persistence.criteria.Root; import org.dspace.content.DSpaceObject; import org.dspace.core.AbstractHibernateDAO; import org.dspace.core.Context; diff --git a/dspace-api/src/main/java/org/dspace/identifier/doi/DOIIdentifierException.java b/dspace-api/src/main/java/org/dspace/identifier/doi/DOIIdentifierException.java index 61f738d7cb..5fc3384b7d 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/doi/DOIIdentifierException.java +++ b/dspace-api/src/main/java/org/dspace/identifier/doi/DOIIdentifierException.java @@ -113,7 +113,7 @@ public class DOIIdentifierException extends IdentifierException { case DOI_IS_DELETED: return "DELETED"; default: - return "UNKOWN"; + return "UNKNOWN"; } } diff --git a/dspace-api/src/main/java/org/dspace/identifier/doi/DOIOrganiser.java b/dspace-api/src/main/java/org/dspace/identifier/doi/DOIOrganiser.java index 088e2b1cbc..b03af68b42 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/doi/DOIOrganiser.java +++ b/dspace-api/src/main/java/org/dspace/identifier/doi/DOIOrganiser.java @@ -17,8 +17,8 @@ import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.UUID; -import javax.mail.MessagingException; +import jakarta.mail.MessagingException; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.DefaultParser; @@ -689,7 +689,7 @@ public class DOIOrganiser { DOI doiRow = null; String doi = null; - // detect it identifer is ItemID, handle or DOI. + // detect it identifier is ItemID, handle or DOI. // try to detect ItemID if (identifier .matches("[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[34][0-9a-fA-F]{3}-[89ab][0-9a-fA-F]{3}-[0-9a-fA-F]{12}")) { diff --git a/dspace-api/src/main/java/org/dspace/identifier/doi/DataCiteConnector.java b/dspace-api/src/main/java/org/dspace/identifier/doi/DataCiteConnector.java index 57136d6143..23af904f2c 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/doi/DataCiteConnector.java +++ b/dspace-api/src/main/java/org/dspace/identifier/doi/DataCiteConnector.java @@ -7,6 +7,10 @@ */ package org.dspace.identifier.doi; +import static org.dspace.identifier.DOIIdentifierProvider.DOI_ELEMENT; +import static org.dspace.identifier.DOIIdentifierProvider.DOI_QUALIFIER; +import static org.dspace.identifier.DOIIdentifierProvider.MD_SCHEMA; + import java.io.ByteArrayInputStream; import java.io.IOException; import java.net.URISyntaxException; @@ -15,6 +19,7 @@ import java.util.HashMap; import java.util.Iterator; import java.util.Map; +import org.apache.commons.lang.StringUtils; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.StatusLine; @@ -33,6 +38,8 @@ import org.apache.http.impl.client.BasicCredentialsProvider; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.util.EntityUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.authorize.AuthorizeException; import org.dspace.content.DSpaceObject; import org.dspace.content.crosswalk.CrosswalkException; @@ -52,8 +59,6 @@ import org.jdom2.filter.ElementFilter; import org.jdom2.input.SAXBuilder; import org.jdom2.output.Format; import org.jdom2.output.XMLOutputter; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; /** @@ -62,7 +67,7 @@ import org.springframework.beans.factory.annotation.Autowired; public class DataCiteConnector implements DOIConnector { - private static final Logger log = LoggerFactory.getLogger(DataCiteConnector.class); + private static final Logger log = LogManager.getLogger(); // Configuration property names static final String CFG_USER = "identifier.doi.user"; @@ -356,10 +361,8 @@ public class DataCiteConnector .getDSpaceObjectService(dso); if (!this.xwalk.canDisseminate(dso)) { - log.error("Crosswalk " + this.CROSSWALK_NAME - + " cannot disseminate DSO with type " + dso.getType() - + " and ID " + dso.getID() + ". Giving up reserving the DOI " - + doi + "."); + log.error("Crosswalk {} cannot disseminate DSO with type {} and ID {}. Giving up reserving the DOI {}.", + this.CROSSWALK_NAME, dso.getType(), dso.getID(), doi); throw new DOIIdentifierException("Cannot disseminate " + dSpaceObjectService.getTypeText(dso) + "/" + dso.getID() + " using crosswalk " + this.CROSSWALK_NAME + ".", @@ -385,24 +388,28 @@ public class DataCiteConnector parameters.put("hostinginstitution", configurationService.getProperty(CFG_HOSTINGINSTITUTION)); } + parameters.put("mdSchema", MD_SCHEMA); + parameters.put("mdElement", DOI_ELEMENT); + // Pass an empty string for qualifier if the metadata field doesn't have any + parameters.put("mdQualifier", DOI_QUALIFIER); Element root = null; try { root = xwalk.disseminateElement(context, dso, parameters); } catch (AuthorizeException ae) { - log.error("Caught an AuthorizeException while disseminating DSO " - + "with type " + dso.getType() + " and ID " + dso.getID() - + ". Giving up to reserve DOI " + doi + ".", ae); - throw new DOIIdentifierException("AuthorizeException occured while " + log.error("Caught an AuthorizeException while disseminating DSO" + + " with type {} and ID {}. Giving up to reserve DOI {}.", + dso.getType(), dso.getID(), doi, ae); + throw new DOIIdentifierException("AuthorizeException occurred while " + "converting " + dSpaceObjectService.getTypeText(dso) + "/" + dso .getID() + " using crosswalk " + this.CROSSWALK_NAME + ".", ae, DOIIdentifierException.CONVERSION_ERROR); } catch (CrosswalkException ce) { - log.error("Caught an CrosswalkException while reserving a DOI (" - + doi + ") for DSO with type " + dso.getType() + " and ID " - + dso.getID() + ". Won't reserve the doi.", ce); - throw new DOIIdentifierException("CrosswalkException occured while " + log.error("Caught a CrosswalkException while reserving a DOI ({})" + + " for DSO with type {} and ID {}. Won't reserve the doi.", + doi, dso.getType(), dso.getID(), ce); + throw new DOIIdentifierException("CrosswalkException occurred while " + "converting " + dSpaceObjectService.getTypeText(dso) + "/" + dso .getID() + " using crosswalk " + this.CROSSWALK_NAME + ".", ce, @@ -412,7 +419,7 @@ public class DataCiteConnector } String metadataDOI = extractDOI(root); - if (null == metadataDOI) { + if (StringUtils.isBlank(metadataDOI)) { // The DOI will be saved as metadata of dso after successful // registration. To register a doi it has to be part of the metadata // sent to DataCite. So we add it to the XML we'll send to DataCite @@ -421,10 +428,9 @@ public class DataCiteConnector } else if (!metadataDOI.equals(doi.substring(DOI.SCHEME.length()))) { log.error("While reserving a DOI, the " + "crosswalk to generate the metadata used another DOI than " - + "the DOI we're reserving. Cannot reserve DOI " + doi - + " for " + dSpaceObjectService.getTypeText(dso) + " " - + dso.getID() + "."); - throw new IllegalStateException("An internal error occured while " + + "the DOI we're reserving. Cannot reserve DOI {} for {} {}.", + doi, dSpaceObjectService.getTypeText(dso), dso.getID()); + throw new IllegalStateException("An internal error occurred while " + "generating the metadata. Unable to reserve doi, see logs " + "for further information."); } @@ -440,12 +446,12 @@ public class DataCiteConnector // 400 -> invalid XML case (400): { log.warn("DataCite was unable to understand the XML we send."); - log.warn("DataCite Metadata API returned a http status code " - + "400: " + resp.getContent()); + log.warn("DataCite Metadata API returned a http status code" + + " 400: {}", resp::getContent); Format format = Format.getCompactFormat(); format.setEncoding("UTF-8"); XMLOutputter xout = new XMLOutputter(format); - log.info("We send the following XML:\n" + xout.outputString(root)); + log.info("We send the following XML:\n{}", xout.outputString(root)); throw new DOIIdentifierException("Unable to reserve DOI " + doi + ". Please inform your administrator or take a look " + " into the log files.", DOIIdentifierException.BAD_REQUEST); @@ -479,8 +485,8 @@ public class DataCiteConnector resp = this.sendDOIPostRequest(doi, handleService.resolveToURL(context, dso.getHandle())); } catch (SQLException e) { - log.error("Caught SQL-Exception while resolving handle to URL: " - + e.getMessage()); + log.error("Caught SQL-Exception while resolving handle to URL: {}", + e::getMessage); throw new RuntimeException(e); } @@ -492,7 +498,7 @@ public class DataCiteConnector // 400 -> wrong domain, wrong prefix, wrong request body case (400): { log.warn("We send an irregular request to DataCite. While " - + "registering a DOI they told us: " + resp.getContent()); + + "registering a DOI they told us: {}", resp::getContent); throw new DOIIdentifierException("Currently we cannot register " + "DOIs. Please inform the administrator or take a look " + " in the DSpace log file.", @@ -501,8 +507,8 @@ public class DataCiteConnector // 412 Precondition failed: DOI was not reserved before registration! case (412): { log.error("We tried to register a DOI {} that has not been reserved " - + "before! The registration agency told us: {}.", doi, - resp.getContent()); + + "before! The registration agency told us: {}.", + () -> doi, resp::getContent); throw new DOIIdentifierException("There was an error in handling " + "of DOIs. The DOI we wanted to register had not been " + "reserved in advance. Please contact the administrator " @@ -511,7 +517,7 @@ public class DataCiteConnector } // Catch all other http status code in case we forgot one. default: { - log.warn("While registration of DOI {}, we got a http status code " + log.warn("While registering DOI {}, we got a http status code " + "{} and the message \"{}\".", doi, Integer.toString(resp.statusCode), resp.getContent()); throw new DOIIdentifierException("Unable to parse an answer from " @@ -564,8 +570,8 @@ public class DataCiteConnector try { EntityUtils.consume(reqEntity); } catch (IOException ioe) { - log.info("Caught an IOException while releasing a HTTPEntity:" - + ioe.getMessage()); + log.info("Caught an IOException while releasing a HTTPEntity: {}", + ioe::getMessage); } } } @@ -668,8 +674,8 @@ public class DataCiteConnector try { EntityUtils.consume(reqEntity); } catch (IOException ioe) { - log.info("Caught an IOException while releasing an HTTPEntity:" - + ioe.getMessage()); + log.info("Caught an IOException while releasing an HTTPEntity: {}", + ioe::getMessage); } } } @@ -768,7 +774,7 @@ public class DataCiteConnector // 500 is documented and signals an internal server error case (500): { log.warn("Caught an http status code 500 while managing DOI " - + "{}. Message was: " + content); + + "{}. Message was: {}", doi, content); throw new DOIIdentifierException("DataCite API has an internal error. " + "It is temporarily impossible to manage DOIs. " + "Further information can be found in DSpace log file.", @@ -781,7 +787,7 @@ public class DataCiteConnector return new DataCiteResponse(statusCode, content); } catch (IOException e) { - log.warn("Caught an IOException: " + e.getMessage()); + log.warn("Caught an IOException: {}", e::getMessage); throw new RuntimeException(e); } finally { try { @@ -790,7 +796,7 @@ public class DataCiteConnector EntityUtils.consume(entity); } } catch (IOException e) { - log.warn("Can't release HTTP-Entity: " + e.getMessage()); + log.warn("Can't release HTTP-Entity: {}", e::getMessage); } } } diff --git a/dspace-api/src/main/java/org/dspace/identifier/ezid/EZIDRequest.java b/dspace-api/src/main/java/org/dspace/identifier/ezid/EZIDRequest.java index 525ad46b25..bf46c3bf59 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/ezid/EZIDRequest.java +++ b/dspace-api/src/main/java/org/dspace/identifier/ezid/EZIDRequest.java @@ -27,10 +27,10 @@ import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.BasicCredentialsProvider; 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.identifier.DOI; import org.dspace.identifier.IdentifierException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * A request to EZID concerning a given (or expected) identifier. @@ -38,7 +38,7 @@ import org.slf4j.LoggerFactory; * @author Mark H. Wood */ public class EZIDRequest { - private static final Logger log = LoggerFactory.getLogger(EZIDRequest.class); + private static final Logger log = LogManager.getLogger(); private static final String ID_PATH = "/id/" + DOI.SCHEME; @@ -149,7 +149,7 @@ public class EZIDRequest { // GET path HttpGet request; URI uri = new URI(scheme, host, path + ID_PATH + authority + name, null); - log.debug("EZID lookup {}", uri.toASCIIString()); + log.debug("EZID lookup {}", uri::toASCIIString); request = new HttpGet(uri); HttpResponse response = client.execute(request, httpContext); return new EZIDResponse(response); @@ -172,7 +172,7 @@ public class EZIDRequest { // PUT path [+metadata] HttpPut request; URI uri = new URI(scheme, host, path + ID_PATH + authority + '/' + name, null); - log.debug("EZID create {}", uri.toASCIIString()); + log.debug("EZID create {}", uri::toASCIIString); request = new HttpPut(uri); if (null != metadata) { request.setEntity(new StringEntity(formatMetadata(metadata), UTF_8)); @@ -196,7 +196,7 @@ public class EZIDRequest { // POST path [+metadata] HttpPost request; URI uri = new URI(scheme, host, path + SHOULDER_PATH + authority, null); - log.debug("EZID mint {}", uri.toASCIIString()); + log.debug("EZID mint {}", uri::toASCIIString); request = new HttpPost(uri); if (null != metadata) { request.setEntity(new StringEntity(formatMetadata(metadata), UTF_8)); @@ -225,7 +225,7 @@ public class EZIDRequest { // POST path +metadata HttpPost request; URI uri = new URI(scheme, host, path + ID_PATH + authority + name, null); - log.debug("EZID modify {}", uri.toASCIIString()); + log.debug("EZID modify {}", uri::toASCIIString); request = new HttpPost(uri); request.setEntity(new StringEntity(formatMetadata(metadata), UTF_8)); HttpResponse response = client.execute(request, httpContext); @@ -246,7 +246,7 @@ public class EZIDRequest { // DELETE path HttpDelete request; URI uri = new URI(scheme, host, path + ID_PATH + authority + name, null); - log.debug("EZID delete {}", uri.toASCIIString()); + log.debug("EZID delete {}", uri::toASCIIString); request = new HttpDelete(uri); HttpResponse response = client.execute(request, httpContext); return new EZIDResponse(response); diff --git a/dspace-api/src/main/java/org/dspace/identifier/ezid/EZIDResponse.java b/dspace-api/src/main/java/org/dspace/identifier/ezid/EZIDResponse.java index 9c5ad90484..4ac975b2db 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/ezid/EZIDResponse.java +++ b/dspace-api/src/main/java/org/dspace/identifier/ezid/EZIDResponse.java @@ -19,15 +19,15 @@ import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.ParseException; import org.apache.http.util.EntityUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.identifier.IdentifierException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * Decoded response data evoked by a request made to EZID. */ public class EZIDResponse { - private static final Logger log = LoggerFactory.getLogger(EZIDResponse.class); + private static final Logger log = LogManager.getLogger(); private static final String UTF_8 = "UTF-8"; @@ -35,7 +35,7 @@ public class EZIDResponse { private final String statusValue; - private final Map attributes = new HashMap(); + private final Map attributes = new HashMap<>(); private final HttpResponse response; @@ -49,12 +49,8 @@ public class EZIDResponse { String body; try { body = EntityUtils.toString(responseBody, UTF_8); - } catch (IOException ex) { - log.error(ex.getMessage()); - throw new IdentifierException("EZID response not understood: " - + ex.getMessage()); - } catch (ParseException ex) { - log.error(ex.getMessage()); + } catch (IOException | ParseException ex) { + log.error(ex::getMessage); throw new IdentifierException("EZID response not understood: " + ex.getMessage()); } @@ -124,7 +120,7 @@ public class EZIDResponse { * @return all keys found in the response. */ public List getKeys() { - List keys = new ArrayList(); + List keys = new ArrayList<>(); for (String key : attributes.keySet()) { keys.add(key); } diff --git a/dspace-api/src/main/java/org/dspace/identifier/service/DOIService.java b/dspace-api/src/main/java/org/dspace/identifier/service/DOIService.java index 5bd68a9061..e815441b2a 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/service/DOIService.java +++ b/dspace-api/src/main/java/org/dspace/identifier/service/DOIService.java @@ -43,6 +43,23 @@ public interface DOIService { */ public DOI create(Context context) throws SQLException; + /** + * Deletes the given {@link DOI}. + * + * @param context current DSpace session. + * @throws SQLException passed through. + */ + void delete(Context context, DOI doi) throws SQLException; + + /** + * Retrieves the full list of {@link DOI}s currently in storage. + * + * @param context current DSpace session. + * @return The list of all DOIs currently in storage. + * @throws SQLException passed through. + */ + List findAll(Context context) throws SQLException; + /** * Find a specific DOI in storage. * diff --git a/dspace-api/src/main/java/org/dspace/importer/external/MultipleParallelImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/MultipleParallelImportMetadataSourceServiceImpl.java new file mode 100644 index 0000000000..a4033ee0c2 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/MultipleParallelImportMetadataSourceServiceImpl.java @@ -0,0 +1,186 @@ +/** + * 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.importer.external; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +import jakarta.el.MethodNotFoundException; +import org.dspace.content.Item; +import org.dspace.importer.external.datamodel.ImportRecord; +import org.dspace.importer.external.datamodel.Query; +import org.dspace.importer.external.exception.MetadataSourceException; +import org.dspace.importer.external.service.components.QuerySource; + +/** + * Implements a data source for querying multiple external data sources in parallel + * + * optional Affiliation information are not part of the API request. + * + * @author Johanna Staudinger (johanna.staudinger@uni-bamberg.de) + * + */ +public class MultipleParallelImportMetadataSourceServiceImpl implements QuerySource { + private final List innerProviders; + private final ExecutorService executorService; + + private final String sourceName; + public MultipleParallelImportMetadataSourceServiceImpl(List innerProviders, String sourceName) { + super(); + this.innerProviders = innerProviders; + this.executorService = Executors.newFixedThreadPool(innerProviders.size()); + this.sourceName = sourceName; + } + + @Override + public String getImportSource() { + return sourceName; + } + + @Override + public ImportRecord getRecord(String recordId) throws MetadataSourceException { + List> futureList = new ArrayList<>(); + ImportRecord result = null; + for (QuerySource innerProvider : innerProviders) { + futureList.add(executorService.submit(() -> innerProvider.getRecord(recordId))); + } + for (Future future: futureList) { + try { + ImportRecord importRecord = future.get(); + if (!Objects.isNull(importRecord)) { + result = importRecord; + } + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + } + return result; + } + + @Override + public int getRecordsCount(String query) throws MetadataSourceException { + List> futureList = new ArrayList<>(); + int result = 0; + for (QuerySource innerProvider : innerProviders) { + futureList.add(executorService.submit(() -> innerProvider.getRecordsCount(query))); + } + for (Future future: futureList) { + try { + Integer count = future.get(); + result += Objects.isNull(count) ? 0 : count; + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + } + return result; + } + + @Override + public int getRecordsCount(Query query) throws MetadataSourceException { + List> futureList = new ArrayList<>(); + int result = 0; + for (QuerySource innerProvider : innerProviders) { + futureList.add(executorService.submit(() -> innerProvider.getRecordsCount(query))); + } + for (Future future: futureList) { + try { + Integer count = future.get(); + result += Objects.isNull(count) ? 0 : count; + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + } + return result; + } + + + @Override + public Collection getRecords(String query, int start, int count) throws MetadataSourceException { + List>> futureList = new ArrayList<>(); + List result = new ArrayList<>(); + for (QuerySource innerProvider : innerProviders) { + futureList.add(executorService.submit(() -> innerProvider.getRecords(query, start, count))); + } + for (Future> future: futureList) { + try { + Collection importRecords = future.get(); + result.addAll(Objects.isNull(importRecords) ? Collections.emptyList() : importRecords); + } catch (InterruptedException | ExecutionException e) { + // + } + } + return result; + } + + @Override + public Collection getRecords(Query query) throws MetadataSourceException { + List>> futureList = new ArrayList<>(); + List result = new ArrayList<>(); + for (QuerySource innerProvider : innerProviders) { + futureList.add(executorService.submit(() -> innerProvider.getRecords(query))); + } + for (Future> future: futureList) { + try { + Collection importRecords = future.get(); + result.addAll(Objects.isNull(importRecords) ? Collections.emptyList() : importRecords); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + } + return result; + } + + @Override + public ImportRecord getRecord(Query query) throws MetadataSourceException { + throw new MethodNotFoundException("This method is not implemented for multiple external data sources"); + } + + @Override + public Collection findMatchingRecords(Query query) throws MetadataSourceException { + List>> futureList = new ArrayList<>(); + List result = new ArrayList<>(); + for (QuerySource innerProvider : innerProviders) { + futureList.add(executorService.submit(() -> innerProvider.findMatchingRecords(query))); + } + for (Future> future: futureList) { + try { + Collection importRecords = future.get(); + result.addAll(Objects.isNull(importRecords) ? Collections.emptyList() : importRecords); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + } + return result; + } + + + @Override + public Collection findMatchingRecords(Item item) throws MetadataSourceException { + List>> futureList = new ArrayList<>(); + List result = new ArrayList<>(); + for (QuerySource innerProvider : innerProviders) { + futureList.add(executorService.submit(() -> innerProvider.findMatchingRecords(item))); + } + for (Future> future: futureList) { + try { + Collection importRecords = future.get(); + result.addAll(Objects.isNull(importRecords) ? Collections.emptyList() : importRecords); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + } + return result; + } +} diff --git a/dspace-api/src/main/java/org/dspace/importer/external/ads/ADSFieldMapping.java b/dspace-api/src/main/java/org/dspace/importer/external/ads/ADSFieldMapping.java index e7d2d3398b..0f05e9bb4c 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/ads/ADSFieldMapping.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/ads/ADSFieldMapping.java @@ -8,8 +8,8 @@ package org.dspace.importer.external.ads; import java.util.Map; -import javax.annotation.Resource; +import jakarta.annotation.Resource; import org.dspace.importer.external.metadatamapping.AbstractMetadataFieldMapping; /** diff --git a/dspace-api/src/main/java/org/dspace/importer/external/ads/ADSImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/ads/ADSImportMetadataSourceServiceImpl.java index 8fbe4ef2cf..4c72c46732 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/ads/ADSImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/ads/ADSImportMetadataSourceServiceImpl.java @@ -17,11 +17,11 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.concurrent.Callable; -import javax.el.MethodNotFoundException; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.el.MethodNotFoundException; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.http.client.utils.URIBuilder; diff --git a/dspace-api/src/main/java/org/dspace/importer/external/arxiv/metadatamapping/ArXivFieldMapping.java b/dspace-api/src/main/java/org/dspace/importer/external/arxiv/metadatamapping/ArXivFieldMapping.java index 272b149015..48e7df89b3 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/arxiv/metadatamapping/ArXivFieldMapping.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/arxiv/metadatamapping/ArXivFieldMapping.java @@ -8,8 +8,8 @@ package org.dspace.importer.external.arxiv.metadatamapping; import java.util.Map; -import javax.annotation.Resource; +import jakarta.annotation.Resource; import org.dspace.importer.external.metadatamapping.AbstractMetadataFieldMapping; /** diff --git a/dspace-api/src/main/java/org/dspace/importer/external/arxiv/service/ArXivImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/arxiv/service/ArXivImportMetadataSourceServiceImpl.java index 96689e62ba..a1df4a7f40 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/arxiv/service/ArXivImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/arxiv/service/ArXivImportMetadataSourceServiceImpl.java @@ -14,14 +14,14 @@ import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.concurrent.Callable; -import javax.el.MethodNotFoundException; -import javax.ws.rs.client.Client; -import javax.ws.rs.client.ClientBuilder; -import javax.ws.rs.client.Invocation; -import javax.ws.rs.client.WebTarget; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; +import jakarta.el.MethodNotFoundException; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.client.Invocation; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; import org.apache.commons.lang3.StringUtils; import org.dspace.content.Item; import org.dspace.importer.external.datamodel.ImportRecord; diff --git a/dspace-api/src/main/java/org/dspace/importer/external/bibtex/service/BibtexImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/bibtex/service/BibtexImportMetadataSourceServiceImpl.java index 0014088c86..7dcc8d64c7 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/bibtex/service/BibtexImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/bibtex/service/BibtexImportMetadataSourceServiceImpl.java @@ -16,8 +16,8 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Map.Entry; -import javax.annotation.Resource; +import jakarta.annotation.Resource; import org.dspace.importer.external.exception.FileSourceException; import org.dspace.importer.external.service.components.AbstractPlainMetadataSource; import org.dspace.importer.external.service.components.dto.PlainMetadataKeyValueItem; diff --git a/dspace-api/src/main/java/org/dspace/importer/external/cinii/CiniiFieldMapping.java b/dspace-api/src/main/java/org/dspace/importer/external/cinii/CiniiFieldMapping.java index f266ff3d85..aad756fbb0 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/cinii/CiniiFieldMapping.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/cinii/CiniiFieldMapping.java @@ -8,8 +8,8 @@ package org.dspace.importer.external.cinii; import java.util.Map; -import javax.annotation.Resource; +import jakarta.annotation.Resource; import org.dspace.importer.external.metadatamapping.AbstractMetadataFieldMapping; /** diff --git a/dspace-api/src/main/java/org/dspace/importer/external/cinii/CiniiImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/cinii/CiniiImportMetadataSourceServiceImpl.java index 587ad5b258..66572f9a3d 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/cinii/CiniiImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/cinii/CiniiImportMetadataSourceServiceImpl.java @@ -19,8 +19,8 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.concurrent.Callable; -import javax.el.MethodNotFoundException; +import jakarta.el.MethodNotFoundException; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang.StringUtils; import org.apache.http.HttpException; diff --git a/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefAuthorMetadataProcessor.java b/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefAuthorMetadataProcessor.java index abf84f52d0..b9b384f8ed 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefAuthorMetadataProcessor.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefAuthorMetadataProcessor.java @@ -42,8 +42,8 @@ public class CrossRefAuthorMetadataProcessor implements JsonPathMetadataProcesso JsonNode author = authors.next(); String givenName = author.at("/given").textValue(); String familyName = author.at("/family").textValue(); - if (StringUtils.isNoneBlank(givenName) && StringUtils.isNoneBlank(familyName)) { - values.add(givenName + " " + familyName); + if (StringUtils.isNotBlank(givenName) && StringUtils.isNotBlank(familyName)) { + values.add(familyName.trim() + ", " + givenName.trim()); } } return values; @@ -64,4 +64,4 @@ public class CrossRefAuthorMetadataProcessor implements JsonPathMetadataProcesso this.pathToArray = pathToArray; } -} \ No newline at end of file +} diff --git a/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefDateMetadataProcessor.java b/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefDateMetadataProcessor.java index dec0b050f3..c83abbf2b2 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefDateMetadataProcessor.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefDateMetadataProcessor.java @@ -7,7 +7,8 @@ */ package org.dspace.importer.external.crossref; -import java.text.SimpleDateFormat; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; @@ -18,12 +19,11 @@ import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.importer.external.metadatamapping.contributor.JsonPathMetadataProcessor; -import org.joda.time.LocalDate; /** * This class is used for CrossRef's Live-Import to extract * issued attribute. - * Beans are configured in the crossref-integration.xml file. + * Beans are configured in the {@code crossref-integration.xml} file. * * @author Francesco Pio Scognamiglio (francescopio.scognamiglio at 4science.com) */ @@ -41,22 +41,25 @@ public class CrossRefDateMetadataProcessor implements JsonPathMetadataProcessor while (dates.hasNext()) { JsonNode date = dates.next(); LocalDate issuedDate = null; - SimpleDateFormat issuedDateFormat = null; + DateTimeFormatter issuedDateFormat = null; if (date.has(0) && date.has(1) && date.has(2)) { - issuedDate = new LocalDate( + issuedDate = LocalDate.of( date.get(0).numberValue().intValue(), date.get(1).numberValue().intValue(), date.get(2).numberValue().intValue()); - issuedDateFormat = new SimpleDateFormat("yyyy-MM-dd"); + issuedDateFormat = DateTimeFormatter.ISO_LOCAL_DATE; } else if (date.has(0) && date.has(1)) { - issuedDate = new LocalDate().withYear(date.get(0).numberValue().intValue()) - .withMonthOfYear(date.get(1).numberValue().intValue()); - issuedDateFormat = new SimpleDateFormat("yyyy-MM"); + issuedDate = LocalDate.of(date.get(0).numberValue().intValue(), + date.get(1).numberValue().intValue(), + 1); + issuedDateFormat = DateTimeFormatter.ofPattern("yyyy-MM"); } else if (date.has(0)) { - issuedDate = new LocalDate().withYear(date.get(0).numberValue().intValue()); - issuedDateFormat = new SimpleDateFormat("yyyy"); + issuedDate = LocalDate.of(date.get(0).numberValue().intValue(), + 1, + 1); + issuedDateFormat = DateTimeFormatter.ofPattern("yyyy"); } - values.add(issuedDateFormat.format(issuedDate.toDate())); + values.add(issuedDate.format(issuedDateFormat)); } return values; } diff --git a/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefFieldMapping.java b/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefFieldMapping.java index 5e879b4d26..81571ed7bb 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefFieldMapping.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefFieldMapping.java @@ -8,8 +8,8 @@ package org.dspace.importer.external.crossref; import java.util.Map; -import javax.annotation.Resource; +import jakarta.annotation.Resource; import org.dspace.importer.external.metadatamapping.AbstractMetadataFieldMapping; /** diff --git a/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefImportMetadataSourceServiceImpl.java index 419f6ca8a0..37e613d9c5 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefImportMetadataSourceServiceImpl.java @@ -16,11 +16,11 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.concurrent.Callable; -import javax.el.MethodNotFoundException; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.el.MethodNotFoundException; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.http.client.utils.URIBuilder; diff --git a/dspace-api/src/main/java/org/dspace/importer/external/datacite/DataCiteFieldMapping.java b/dspace-api/src/main/java/org/dspace/importer/external/datacite/DataCiteFieldMapping.java index f8540307b9..a67b73480d 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/datacite/DataCiteFieldMapping.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/datacite/DataCiteFieldMapping.java @@ -8,8 +8,8 @@ package org.dspace.importer.external.datacite; import java.util.Map; -import javax.annotation.Resource; +import jakarta.annotation.Resource; import org.dspace.importer.external.metadatamapping.AbstractMetadataFieldMapping; /** diff --git a/dspace-api/src/main/java/org/dspace/importer/external/datacite/DataCiteImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/datacite/DataCiteImportMetadataSourceServiceImpl.java index 6c65d96b37..e00b2e2cea 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/datacite/DataCiteImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/datacite/DataCiteImportMetadataSourceServiceImpl.java @@ -13,11 +13,11 @@ import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; -import javax.el.MethodNotFoundException; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.el.MethodNotFoundException; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -36,7 +36,7 @@ import org.springframework.beans.factory.annotation.Autowired; * Implements a data source for querying Datacite * Mainly copied from CrossRefImportMetadataSourceServiceImpl. * - * optional Affiliation informations are not part of the API request. + * optional Affiliation information are not part of the API request. * https://support.datacite.org/docs/can-i-see-more-detailed-affiliation-information-in-the-rest-api * * @author Pasquale Cavallo (pasquale.cavallo at 4science dot it) diff --git a/dspace-api/src/main/java/org/dspace/importer/external/datamodel/ImportRecord.java b/dspace-api/src/main/java/org/dspace/importer/external/datamodel/ImportRecord.java index 3fc34dc511..cbd4bc1245 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/datamodel/ImportRecord.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/datamodel/ImportRecord.java @@ -11,7 +11,9 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Optional; +import org.dspace.content.MetadataFieldName; import org.dspace.importer.external.metadatamapping.MetadatumDTO; /** @@ -94,6 +96,31 @@ public class ImportRecord { return values; } + /** + * Returns an {@code Optional} representing the value + * of the metadata {@code field} found inside the {@code valueList}. + * @param field String of the MetadataField to search + * @return {@code Optional} non empty if found. + */ + public Optional getSingleValue(String field) { + MetadataFieldName metadataFieldName = new MetadataFieldName(field); + return getSingleValue(metadataFieldName.schema, metadataFieldName.element, metadataFieldName.qualifier); + } + + /** + * Retrieves a single value for the given schema, element, and qualifier. + * + * @param schema the schema for the value + * @param element the element for the value + * @param qualifier the qualifier for the value + * @return an optional containing the single value, if present + */ + public Optional getSingleValue(String schema, String element, String qualifier) { + return getValue(schema, element, qualifier).stream() + .map(MetadatumDTO::getValue) + .findFirst(); + } + /** * Add a value to the valueList * diff --git a/dspace-api/src/main/java/org/dspace/importer/external/epo/service/EpoFieldMapping.java b/dspace-api/src/main/java/org/dspace/importer/external/epo/service/EpoFieldMapping.java index 64ec53ffb9..955550ce0f 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/epo/service/EpoFieldMapping.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/epo/service/EpoFieldMapping.java @@ -8,8 +8,8 @@ package org.dspace.importer.external.epo.service; import java.util.Map; -import javax.annotation.Resource; +import jakarta.annotation.Resource; import org.dspace.importer.external.metadatamapping.AbstractMetadataFieldMapping; diff --git a/dspace-api/src/main/java/org/dspace/importer/external/epo/service/EpoImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/epo/service/EpoImportMetadataSourceServiceImpl.java index fbae302bca..70f726c5f9 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/epo/service/EpoImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/epo/service/EpoImportMetadataSourceServiceImpl.java @@ -106,7 +106,7 @@ public class EpoImportMetadataSourceServiceImpl extends AbstractImportMetadataSo } /** - * Set the costumer epo secret + * Set the customer epo secret * @param consumerSecret the customer epo secret */ public void setConsumerSecret(String consumerSecret) { diff --git a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/ArrayElementAttributeProcessor.java b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/ArrayElementAttributeProcessor.java index b938a290c2..7a74551d80 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/ArrayElementAttributeProcessor.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/ArrayElementAttributeProcessor.java @@ -20,7 +20,7 @@ import org.apache.logging.log4j.Logger; /** * This Processor allows to extract attribute values of an array. - * For exaple to extract all values of secondAttribute, + * For example to extract all values of secondAttribute, * "array":[ * { * "firstAttribute":"first value", diff --git a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/EnhancedSimpleMetadataContributor.java b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/EnhancedSimpleMetadataContributor.java index b3953f60c5..7aaa5aa0b3 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/EnhancedSimpleMetadataContributor.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/EnhancedSimpleMetadataContributor.java @@ -95,8 +95,8 @@ public class EnhancedSimpleMetadataContributor extends SimpleMetadataContributor values = new LinkedList<>(); for (PlainMetadataKeyValueItem metadatum : t.getMetadata()) { if (getKey().equals(metadatum.getKey())) { - String[] splitted = splitToRecord(metadatum.getValue()); - for (String value : splitted) { + String[] split = splitToRecord(metadatum.getValue()); + for (String value : split) { MetadatumDTO dcValue = new MetadatumDTO(); dcValue.setValue(value); dcValue.setElement(getField().getElement()); @@ -120,7 +120,7 @@ public class EnhancedSimpleMetadataContributor extends SimpleMetadataContributor com.opencsv.CSVReader csvReader = new CSVReaderBuilder(inputReader).withCSVParser(parser).build()) { rows = csvReader.readAll(); } catch (IOException | CsvException e) { - //fallback, use the inpu as value + //fallback, use the input as value return new String[] { value }; } //must be one row diff --git a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/EpoIdMetadataContributor.java b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/EpoIdMetadataContributor.java index e32f45a4d5..64d2deaca3 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/EpoIdMetadataContributor.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/EpoIdMetadataContributor.java @@ -13,8 +13,8 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Objects; -import javax.annotation.Resource; +import jakarta.annotation.Resource; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.dspace.importer.external.metadatamapping.MetadataFieldConfig; @@ -28,7 +28,7 @@ import org.jdom2.Text; import org.jdom2.filter.Filters; import org.jdom2.xpath.XPathExpression; import org.jdom2.xpath.XPathFactory; -import org.springframework.beans.factory.annotation.Required; +import org.springframework.beans.factory.annotation.Autowired; /** * Custom MetadataContributor to manage Epo ID. @@ -77,6 +77,7 @@ public class EpoIdMetadataContributor implements MetadataContributor { * * @param metadataFieldMapping the new mapping. */ + @Override public void setMetadataFieldMapping( MetadataFieldMapping> metadataFieldMapping) { this.metadataFieldMapping = metadataFieldMapping; @@ -95,7 +96,8 @@ public class EpoIdMetadataContributor implements MetadataContributor { protected Map prefixToNamespaceMapping; /** - * Initialize EpoIdMetadataContributor with a query, prefixToNamespaceMapping and MetadataFieldConfig + * Initialize EpoIdMetadataContributor with all required fields: a query, prefixToNamespaceMapping + * and MetadataFieldConfig * * @param query query string * @param prefixToNamespaceMapping metadata prefix to namespace mapping @@ -113,7 +115,6 @@ public class EpoIdMetadataContributor implements MetadataContributor { * Empty constructor for EpoIdMetadataContributor */ public EpoIdMetadataContributor() { - } protected String query; @@ -132,7 +133,7 @@ public class EpoIdMetadataContributor implements MetadataContributor { * * @param field MetadataFieldConfig used while retrieving MetadatumDTO */ - @Required + @Autowired(required = true) public void setField(MetadataFieldConfig field) { this.field = field; } @@ -146,7 +147,11 @@ public class EpoIdMetadataContributor implements MetadataContributor { return query; } - @Required + /** + * Setting the query + * @param query query used + */ + @Autowired(required = true) public void setQuery(String query) { this.query = query; } diff --git a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/MatrixElementProcessor.java b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/MatrixElementProcessor.java index c8e93971f4..e4d42d2292 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/MatrixElementProcessor.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/MatrixElementProcessor.java @@ -21,7 +21,7 @@ import org.apache.logging.log4j.Logger; /** * This Processor allows to extract all values of a matrix. * Only need to configure the path to the matrix in "pathToMatrix" - * For exaple to extract all values + * For example to extract all values * "matrix": [ * [ * "first", diff --git a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/RorParentOrgUnitMetadataContributor.java b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/RorParentOrgUnitMetadataContributor.java new file mode 100644 index 0000000000..52c6422554 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/RorParentOrgUnitMetadataContributor.java @@ -0,0 +1,130 @@ +/** + * 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.importer.external.metadatamapping.contributor; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.dspace.importer.external.metadatamapping.MetadatumDTO; + +/** + * A ROR JsonPath Metadata processor that should be configured inside the {@code ror-integration.xml} file. + * This allows the extraction of a given contributor with a specific mappings from the ROR JSON response. + * + * @author Vincenzo Mecca (vins01-4science - vincenzo.mecca at 4science.com) + */ +public class RorParentOrgUnitMetadataContributor extends SimpleJsonPathMetadataContributor { + + /** + * Determines which field of the JSON detains the {@code type} of this + * specific node (that needs to be mapped). + * + */ + private String typeField; + + /** + * Determines which is the type of the main parent node that needs to be mapped. + * It should match the value of the {@code typeField} of the JSON node. + * + */ + private String parentType; + + /** + * Determines which is the field of the JSON that contains the value + * that needs to be mapped into a {@code MetadatumDTO}. + */ + private String labelField; + + /** + * Creates a {@code MetadatumDTO} for each correctly mapped JSON node + * of the ROR response. + * Partial / Unmatched parent-type metadatum will be ignored from this mapping. + * + * @param fullJson ROR response + * @return a collection of read ROR metadata. + */ + @Override + public Collection contributeMetadata(String fullJson) { + + Collection metadata = new ArrayList<>(); + Collection metadataValue = new ArrayList<>(); + + JsonNode jsonNode = convertStringJsonToJsonNode(fullJson); + JsonNode array = jsonNode.at(getQuery()); + if (!array.isArray()) { + return metadata; + } + + Iterator nodes = array.iterator(); + while (nodes.hasNext()) { + JsonNode node = nodes.next(); + + if (!node.has(labelField)) { + continue; + } + + String type = node.has(typeField) ? node.get(typeField).asText() : null; + String label = node.get(labelField).asText(); + + if (parentType.equalsIgnoreCase(type)) { + metadataValue.add(label); + } + + } + + for (String value : metadataValue) { + MetadatumDTO metadatumDto = new MetadatumDTO(); + metadatumDto.setValue(value); + metadatumDto.setElement(getField().getElement()); + metadatumDto.setQualifier(getField().getQualifier()); + metadatumDto.setSchema(getField().getSchema()); + metadata.add(metadatumDto); + } + return metadata; + } + + private JsonNode convertStringJsonToJsonNode(String json) { + ObjectMapper mapper = new ObjectMapper(); + JsonNode body = null; + try { + body = mapper.readTree(json); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + return body; + } + + public String getTypeField() { + return typeField; + } + + public void setTypeField(String typeField) { + this.typeField = typeField; + } + + public String getLabelField() { + return labelField; + } + + public void setLabelField(String labelField) { + this.labelField = labelField; + } + + public String getParentType() { + return parentType; + } + + public void setParentType(String parentType) { + this.parentType = parentType; + } + +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleConcatContributor.java b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleConcatContributor.java index d84bc65701..9a2aa242c6 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleConcatContributor.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleConcatContributor.java @@ -26,7 +26,7 @@ import org.jdom2.xpath.XPathFactory; * This contributor is able to concat multi value. * Given a certain path, if it contains several nodes, * the values of nodes will be concatenated into a single one. - * The concrete example we can see in the file wos-responce.xml in the node, + * The concrete example we can see in the file wos-response.xml in the node, * which may contain several

    paragraphs, * this Contributor allows concatenating all

    paragraphs. to obtain a single one. * diff --git a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleJsonPathMetadataContributor.java b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleJsonPathMetadataContributor.java index 590fc63283..03f3efced0 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleJsonPathMetadataContributor.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleJsonPathMetadataContributor.java @@ -45,7 +45,7 @@ public class SimpleJsonPathMetadataContributor implements MetadataContributorMetadataFieldConfig */ public SimpleJsonPathMetadataContributor(String query, MetadataFieldConfig field) { diff --git a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleMetadataContributor.java b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleMetadataContributor.java index 1b9007f23c..70c6e0f385 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleMetadataContributor.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleMetadataContributor.java @@ -55,7 +55,7 @@ public class SimpleMetadataContributor implements MetadataContributor + implements QuerySource { + + @Autowired(required = true) + protected ConfigurationService configurationService; + + private String baseAddress; + + private WebTarget webTarget; + + private String queryParam; + + @Override + public String getImportSource() { + return "openaire"; + } + + /** + * The string that identifies this import implementation. Preferable a URI + * + * @return the identifying uri + */ + @Override + public ImportRecord getRecord(String id) throws MetadataSourceException { + return retry(new SearchByIdCallable(id)); + } + + /** + * The string that identifies this import implementation. Preferable a URI + * + * @return the identifying uri + */ + @Override + public ImportRecord getRecord(Query query) throws MetadataSourceException { + return retry(new SearchByIdCallable(query)); + } + + + /** + * Find the number of records matching a query; + * + * @param query a query string to base the search on. + * @return the sum of the matching records over this import source + * @throws MetadataSourceException if the underlying methods throw any exception. + */ + @Override + public int getRecordsCount(String query) throws MetadataSourceException { + return retry(new CountByQueryCallable(query)); + } + + /** + * Find the number of records matching a query; + * + * @param query a query object to base the search on. + * @return the sum of the matching records over this import source + * @throws MetadataSourceException if the underlying methods throw any exception. + */ + @Override + public int getRecordsCount(Query query) throws MetadataSourceException { + return retry(new CountByQueryCallable(query)); + } + + /** + * Find the number of records matching a string query. Supports pagination + * + * @param query a query string to base the search on. + * @param start offset to start at + * @param count number of records to retrieve. + * @return a set of records. Fully transformed. + * @throws MetadataSourceException if the underlying methods throw any exception. + */ + @Override + public Collection getRecords(String query, int start, int count) throws MetadataSourceException { + return retry(new SearchByQueryCallable(query, start, count)); + } + + + /** + * Find records based on a object query. + * + * @param query a query object to base the search on. + * @return a set of records. Fully transformed. + * @throws MetadataSourceException if the underlying methods throw any exception. + */ + @Override + public Collection getRecords(Query query) throws MetadataSourceException { + return retry(new SearchByQueryCallable(query)); + } + + @Override + public Collection findMatchingRecords(Query query) throws MetadataSourceException { + throw new MethodNotFoundException("This method is not implemented for OpenAIRE"); + } + + @Override + public Collection findMatchingRecords(Item item) throws MetadataSourceException { + throw new MethodNotFoundException("This method is not implemented for OpenAIRE"); + } + + /** + * Set the baseAddress to this object + * + * @param baseAddress The String object that represents the baseAddress of this object + */ + public void setBaseAddress(String baseAddress) { + this.baseAddress = baseAddress; + } + + /** + * Return the baseAddress set to this object + * + * @return The String object that represents the baseAddress of this object + */ + public String getBaseAddress() { + return baseAddress; + } + + /** + * Set the name of the query param, this correspond to the index used (title, author) + * + * @param queryParam on which index make the query + */ + public void setQueryParam(String queryParam) { + this.queryParam = queryParam; + } + + /** + * Get the name of the query param for the rest call + * + * @return the name of the query param, i.e. the index (title, author) to use + */ + public String getQueryParam() { + return queryParam; + } + /** + * Initialize the class + * + * @throws Exception on generic exception + */ + @Override + public void init() throws Exception { + Client client = ClientBuilder.newClient(); + if (baseAddress == null) { + baseAddress = configurationService.getProperty("openaire.search.url", + "https://api.openaire.eu/search/publications"); + } + if (queryParam == null) { + queryParam = "title"; + } + webTarget = client.target(baseAddress); + } + + public class SearchByIdCallable implements Callable { + + String id = null; + + public SearchByIdCallable(String id) { + this.id = id; + } + + public SearchByIdCallable(Query query) { + this.id = query.getParameterAsClass("id", String.class); + } + + @Override + public ImportRecord call() throws Exception { + List results = new ArrayList(); + WebTarget localTarget = webTarget.queryParam("openairePublicationID", id); + Invocation.Builder invocationBuilder = localTarget.request(); + Response response = invocationBuilder.get(); + if (response.getStatus() == 200) { + String responseString = response.readEntity(String.class); + List omElements = splitToRecords(responseString); + if (omElements != null) { + for (Element record : omElements) { + results.add(filterMultipleTitles(transformSourceRecords(record))); + } + } + return results != null ? results.get(0) : null; + } else { + return null; + } + } + } + + public class CountByQueryCallable implements Callable { + + String q; + + public CountByQueryCallable(String query) { + q = query; + } + + public CountByQueryCallable(Query query) { + q = query.getParameterAsClass("query", String.class); + } + + @Override + public Integer call() throws Exception { + WebTarget localTarget = webTarget.queryParam(queryParam, q); + Invocation.Builder invocationBuilder = localTarget.request(); + Response response = invocationBuilder.get(); + if (response.getStatus() == 200) { + String responseString = response.readEntity(String.class); + + SAXBuilder saxBuilder = new SAXBuilder(); + Document document = saxBuilder.build(new StringReader(responseString)); + Element root = document.getRootElement(); + + XPathExpression xpath = XPathFactory.instance().compile("//header/total", + Filters.element(), null); + + Element totalItem = xpath.evaluateFirst(root); + return totalItem != null ? Integer.parseInt(totalItem.getText()) : null; + + } else { + return 0; + } + } + } + + public class SearchByQueryCallable implements Callable> { + + String q; + int page; + int count; + + public SearchByQueryCallable(String query, int start, int count) { + this.q = query; + this.page = start / count; + this.count = count; + } + + public SearchByQueryCallable(Query query) { + this.q = query.getParameterAsClass("query", String.class); + this.page = query.getParameterAsClass("start", Integer.class) / + query.getParameterAsClass("count", Integer.class); + this.count = query.getParameterAsClass("count", Integer.class); + } + + @Override + public List call() throws Exception { + WebTarget localTarget = webTarget.queryParam(queryParam, q); + localTarget = localTarget.queryParam("page", page + 1); + localTarget = localTarget.queryParam("size", count); + List results = new ArrayList(); + Invocation.Builder invocationBuilder = localTarget.request(); + Response response = invocationBuilder.get(); + if (response.getStatus() == 200) { + String responseString = response.readEntity(String.class); + List omElements = splitToRecords(responseString); + if (omElements != null) { + for (Element record : omElements) { + results.add(filterMultipleTitles(transformSourceRecords(record))); + } + } + } + return results; + } + } + + /** + * This method remove multiple titles occurrences + * + * @param transformSourceRecords + * @return ImportRecord with one or zero title + */ + private ImportRecord filterMultipleTitles(ImportRecord transformSourceRecords) { + List metadata = (List)transformSourceRecords.getValueList(); + ArrayList nextSourceRecord = new ArrayList<>(); + boolean found = false; + for (MetadatumDTO dto : metadata) { + if ("dc".equals(dto.getSchema()) && "title".equals(dto.getElement()) && dto.getQualifier() == null) { + if (!found) { + nextSourceRecord.add(dto); + found = true; + } + } else { + nextSourceRecord.add(dto); + } + } + return new ImportRecord(nextSourceRecord); + } + + private List splitToRecords(String recordsSrc) { + + try { + SAXBuilder saxBuilder = new SAXBuilder(); + Document document = saxBuilder.build(new StringReader(recordsSrc)); + Element root = document.getRootElement(); + + List namespaces = Arrays.asList( + Namespace.getNamespace("dri", "http://www.driver-repository.eu/namespace/dri"), + Namespace.getNamespace("oaf", "http://namespace.openaire.eu/oaf"), + Namespace.getNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance")); + XPathExpression xpath = XPathFactory.instance().compile("//results/result", + Filters.element(), null, namespaces); + + List recordsList = xpath.evaluate(root); + return recordsList; + } catch (JDOMException | IOException e) { + return null; + } + } + + + +} diff --git a/dspace-api/src/main/java/org/dspace/importer/external/pubmed/metadatamapping/PubmedFieldMapping.java b/dspace-api/src/main/java/org/dspace/importer/external/pubmed/metadatamapping/PubmedFieldMapping.java index 2d31537766..0d4dcf0c1e 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/pubmed/metadatamapping/PubmedFieldMapping.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/pubmed/metadatamapping/PubmedFieldMapping.java @@ -9,8 +9,8 @@ package org.dspace.importer.external.pubmed.metadatamapping; import java.util.Map; -import javax.annotation.Resource; +import jakarta.annotation.Resource; import org.dspace.importer.external.metadatamapping.AbstractMetadataFieldMapping; /** diff --git a/dspace-api/src/main/java/org/dspace/importer/external/pubmedeurope/PubmedEuropeFieldMapping.java b/dspace-api/src/main/java/org/dspace/importer/external/pubmedeurope/PubmedEuropeFieldMapping.java index 8c8e23fe98..35d10af584 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/pubmedeurope/PubmedEuropeFieldMapping.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/pubmedeurope/PubmedEuropeFieldMapping.java @@ -8,8 +8,8 @@ package org.dspace.importer.external.pubmedeurope; import java.util.Map; -import javax.annotation.Resource; +import jakarta.annotation.Resource; import org.dspace.importer.external.metadatamapping.AbstractMetadataFieldMapping; /** diff --git a/dspace-api/src/main/java/org/dspace/importer/external/pubmedeurope/PubmedEuropeMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/pubmedeurope/PubmedEuropeMetadataSourceServiceImpl.java index 92d7d9fbd3..5aae8ca8cf 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/pubmedeurope/PubmedEuropeMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/pubmedeurope/PubmedEuropeMetadataSourceServiceImpl.java @@ -17,8 +17,8 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.concurrent.Callable; -import javax.el.MethodNotFoundException; +import jakarta.el.MethodNotFoundException; import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpException; import org.apache.http.client.ClientProtocolException; diff --git a/dspace-api/src/main/java/org/dspace/importer/external/ris/service/RisImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/ris/service/RisImportMetadataSourceServiceImpl.java index 1f460c19e6..e4e027016e 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/ris/service/RisImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/ris/service/RisImportMetadataSourceServiceImpl.java @@ -17,8 +17,8 @@ import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; -import javax.annotation.Resource; +import jakarta.annotation.Resource; import org.dspace.importer.external.exception.FileSourceException; import org.dspace.importer.external.service.components.AbstractPlainMetadataSource; import org.dspace.importer.external.service.components.dto.PlainMetadataKeyValueItem; diff --git a/dspace-api/src/main/java/org/dspace/importer/external/ror/service/RorFieldMapping.java b/dspace-api/src/main/java/org/dspace/importer/external/ror/service/RorFieldMapping.java new file mode 100644 index 0000000000..3096a92f95 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/ror/service/RorFieldMapping.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.importer.external.ror.service; + +import java.util.Map; + +import jakarta.annotation.Resource; +import org.dspace.importer.external.metadatamapping.AbstractMetadataFieldMapping; + + +/** + * An implementation of {@link AbstractMetadataFieldMapping} + * Responsible for defining the mapping of the ROR metadatum fields on the DSpace metadatum fields + * + * @author Vincenzo Mecca (vins01-4science - vincenzo.mecca at 4science.com) + */ +public class RorFieldMapping extends AbstractMetadataFieldMapping { + + /** + * Defines which metadatum is mapped on which metadatum. Note that while the key must be unique it + * only matters here for postprocessing of the value. The mapped MetadatumContributor has full control over + * what metadatafield is generated. + * + * @param metadataFieldMap The map containing the link between retrieve metadata and metadata that will be set to + * the item. + */ + @Override + @Resource(name = "rorMetadataFieldMap") + public void setMetadataFieldMap(Map metadataFieldMap) { + super.setMetadataFieldMap(metadataFieldMap); + } + +} diff --git a/dspace-api/src/main/java/org/dspace/importer/external/ror/service/RorImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/ror/service/RorImportMetadataSourceServiceImpl.java new file mode 100644 index 0000000000..aa11ac0bb7 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/ror/service/RorImportMetadataSourceServiceImpl.java @@ -0,0 +1,278 @@ +/** + * 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.importer.external.ror.service; + +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.el.MethodNotFoundException; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.http.client.utils.URIBuilder; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.dspace.content.Item; +import org.dspace.importer.external.datamodel.ImportRecord; +import org.dspace.importer.external.datamodel.Query; +import org.dspace.importer.external.exception.MetadataSourceException; +import org.dspace.importer.external.liveimportclient.service.LiveImportClient; +import org.dspace.importer.external.service.AbstractImportMetadataSourceService; +import org.dspace.importer.external.service.components.QuerySource; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Implements a {@code AbstractImportMetadataSourceService} for querying ROR services. + * + * @author Vincenzo Mecca (vins01-4science - vincenzo.mecca at 4science.com) + */ +public class RorImportMetadataSourceServiceImpl extends AbstractImportMetadataSourceService + implements QuerySource { + + private final static Logger log = LogManager.getLogger(); + protected static final String ROR_IDENTIFIER_PREFIX = "https://ror.org/"; + + private String url; + + private int timeout = 1000; + + @Autowired + private LiveImportClient liveImportClient; + + @Override + public String getImportSource() { + return "ror"; + } + + @Override + public ImportRecord getRecord(String id) throws MetadataSourceException { + List records = retry(new SearchByIdCallable(id)); + return CollectionUtils.isEmpty(records) ? null : records.get(0); + } + + @Override + public int getRecordsCount(String query) throws MetadataSourceException { + return retry(new CountByQueryCallable(query)); + } + + @Override + public int getRecordsCount(Query query) throws MetadataSourceException { + return retry(new CountByQueryCallable(query)); + } + + @Override + public Collection getRecords(String query, int start, int count) throws MetadataSourceException { + return retry(new SearchByQueryCallable(query)); + } + + @Override + public Collection getRecords(Query query) throws MetadataSourceException { + return retry(new SearchByQueryCallable(query)); + } + + @Override + public ImportRecord getRecord(Query query) throws MetadataSourceException { + List records = retry(new SearchByIdCallable(query)); + return CollectionUtils.isEmpty(records) ? null : records.get(0); + } + + @Override + public Collection findMatchingRecords(Query query) throws MetadataSourceException { + throw new MethodNotFoundException("This method is not implemented for ROR"); + } + + @Override + public Collection findMatchingRecords(Item item) throws MetadataSourceException { + throw new MethodNotFoundException("This method is not implemented for ROR"); + } + + @Override + public void init() throws Exception { + } + + /** + * This class is a Callable implementation to get ROR entries based on query + * object. This Callable use as query value the string queryString passed to + * constructor. If the object will be construct through Query.class instance, a + * Query's map entry with key "query" will be used. Pagination is supported too, + * using the value of the Query's map with keys "start" and "count". + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com) + */ + private class SearchByQueryCallable implements Callable> { + + private Query query; + + private SearchByQueryCallable(String queryString) { + query = new Query(); + query.addParameter("query", queryString); + } + + private SearchByQueryCallable(Query query) { + this.query = query; + } + + @Override + public List call() throws Exception { + return search(query.getParameterAsClass("query", String.class)); + } + } + + /** + * This class is a Callable implementation to get an ROR entry using bibcode The + * bibcode to use can be passed through the constructor as a String or as + * Query's map entry, with the key "id". + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com) + */ + private class SearchByIdCallable implements Callable> { + private Query query; + + private SearchByIdCallable(Query query) { + this.query = query; + } + + private SearchByIdCallable(String id) { + this.query = new Query(); + query.addParameter("id", id); + } + + @Override + public List call() throws Exception { + return searchById(query.getParameterAsClass("id", String.class)); + } + } + + /** + * This class is a Callable implementation to count the number of entries for a + * ROR query. This Callable uses as query value to ROR the string queryString + * passed to constructor. If the object will be construct through {@code Query} + * instance, the value of the Query's map with the key "query" will be used. + * + * @author Vincenzo Mecca (vins01-4science - vincenzo.mecca at 4science.com) + */ + private class CountByQueryCallable implements Callable { + private Query query; + + private CountByQueryCallable(String queryString) { + query = new Query(); + query.addParameter("query", queryString); + } + + private CountByQueryCallable(Query query) { + this.query = query; + } + + @Override + public Integer call() throws Exception { + return count(query.getParameterAsClass("query", String.class)); + } + } + + /** + * Counts the number of results for the given query. + * + * @param query the query string to count results for + * @return the number of results for the given query + */ + public Integer count(String query) { + try { + Map> params = new HashMap>(); + + URIBuilder uriBuilder = new URIBuilder(this.url); + uriBuilder.addParameter("query", query); + + String resp = liveImportClient.executeHttpGetRequest(timeout, uriBuilder.toString(), params); + if (StringUtils.isEmpty(resp)) { + return 0; + } + JsonNode jsonNode = convertStringJsonToJsonNode(resp); + return jsonNode.at("/number_of_results").asInt(); + } catch (URISyntaxException e) { + e.printStackTrace(); + } + return 0; + } + + private List searchById(String id) { + + List importResults = new ArrayList<>(); + + id = StringUtils.removeStart(id, ROR_IDENTIFIER_PREFIX); + + try { + Map> params = new HashMap>(); + + URIBuilder uriBuilder = new URIBuilder(this.url + "/" + id); + + String resp = liveImportClient.executeHttpGetRequest(timeout, uriBuilder.toString(), params); + if (StringUtils.isEmpty(resp)) { + return importResults; + } + + JsonNode jsonNode = convertStringJsonToJsonNode(resp); + importResults.add(transformSourceRecords(jsonNode.toString())); + + } catch (URISyntaxException e) { + e.printStackTrace(); + } + return importResults; + } + + private List search(String query) { + List importResults = new ArrayList<>(); + try { + Map> params = new HashMap>(); + + URIBuilder uriBuilder = new URIBuilder(this.url); + uriBuilder.addParameter("query", query); + + String resp = liveImportClient.executeHttpGetRequest(timeout, uriBuilder.toString(), params); + if (StringUtils.isEmpty(resp)) { + return importResults; + } + + JsonNode jsonNode = convertStringJsonToJsonNode(resp); + JsonNode docs = jsonNode.at("/items"); + if (docs.isArray()) { + Iterator nodes = docs.elements(); + while (nodes.hasNext()) { + JsonNode node = nodes.next(); + importResults.add(transformSourceRecords(node.toString())); + } + } else { + importResults.add(transformSourceRecords(docs.toString())); + } + } catch (URISyntaxException e) { + e.printStackTrace(); + } + return importResults; + } + + private JsonNode convertStringJsonToJsonNode(String json) { + try { + return new ObjectMapper().readTree(json); + } catch (JsonProcessingException e) { + log.error("Unable to process json response.", e); + } + return null; + } + + public void setUrl(String url) { + this.url = url; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/importer/external/scielo/service/ScieloFieldMapping.java b/dspace-api/src/main/java/org/dspace/importer/external/scielo/service/ScieloFieldMapping.java index 0d7183a1f0..f8c4f93a17 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/scielo/service/ScieloFieldMapping.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/scielo/service/ScieloFieldMapping.java @@ -7,8 +7,8 @@ */ package org.dspace.importer.external.scielo.service; import java.util.Map; -import javax.annotation.Resource; +import jakarta.annotation.Resource; import org.dspace.importer.external.metadatamapping.AbstractMetadataFieldMapping; /** diff --git a/dspace-api/src/main/java/org/dspace/importer/external/scielo/service/ScieloImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/scielo/service/ScieloImportMetadataSourceServiceImpl.java index 4f83ffe978..ce0c20435e 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/scielo/service/ScieloImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/scielo/service/ScieloImportMetadataSourceServiceImpl.java @@ -20,9 +20,9 @@ import java.util.Objects; import java.util.concurrent.Callable; import java.util.regex.Matcher; import java.util.regex.Pattern; -import javax.el.MethodNotFoundException; -import javax.ws.rs.BadRequestException; +import jakarta.el.MethodNotFoundException; +import jakarta.ws.rs.BadRequestException; import org.apache.commons.collections4.CollectionUtils; import org.apache.http.client.utils.URIBuilder; import org.dspace.content.Item; diff --git a/dspace-api/src/main/java/org/dspace/importer/external/scopus/service/ScopusFieldMapping.java b/dspace-api/src/main/java/org/dspace/importer/external/scopus/service/ScopusFieldMapping.java index c8143339b4..93e9753c42 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/scopus/service/ScopusFieldMapping.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/scopus/service/ScopusFieldMapping.java @@ -8,8 +8,8 @@ package org.dspace.importer.external.scopus.service; import java.util.Map; -import javax.annotation.Resource; +import jakarta.annotation.Resource; import org.dspace.importer.external.metadatamapping.AbstractMetadataFieldMapping; diff --git a/dspace-api/src/main/java/org/dspace/importer/external/scopus/service/ScopusImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/scopus/service/ScopusImportMetadataSourceServiceImpl.java index e61ca05286..39b2be7ad5 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/scopus/service/ScopusImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/scopus/service/ScopusImportMetadataSourceServiceImpl.java @@ -22,8 +22,8 @@ import java.util.Objects; import java.util.concurrent.Callable; import java.util.regex.Matcher; import java.util.regex.Pattern; -import javax.el.MethodNotFoundException; +import jakarta.el.MethodNotFoundException; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; diff --git a/dspace-api/src/main/java/org/dspace/importer/external/service/components/AbstractPlainMetadataSource.java b/dspace-api/src/main/java/org/dspace/importer/external/service/components/AbstractPlainMetadataSource.java index 5d83b9a7cc..6e0a2d9f7a 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/service/components/AbstractPlainMetadataSource.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/service/components/AbstractPlainMetadataSource.java @@ -23,8 +23,8 @@ import org.dspace.importer.external.service.components.dto.PlainMetadataSourceDt /** * This class is an abstract implementation of {@link MetadataSource} useful in cases * of plain metadata sources. - * It provides the methot to mapping metadata to DSpace Format when source is a file - * whit a list of strings. + * It provides the method to mapping metadata to DSpace Format when source is a file + * with a list of strings. * * @author Pasquale Cavallo (pasquale.cavallo at 4science dot it) */ diff --git a/dspace-api/src/main/java/org/dspace/importer/external/service/components/AbstractRemoteMetadataSource.java b/dspace-api/src/main/java/org/dspace/importer/external/service/components/AbstractRemoteMetadataSource.java index 29801433e3..e15ffff6e6 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/service/components/AbstractRemoteMetadataSource.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/service/components/AbstractRemoteMetadataSource.java @@ -14,8 +14,8 @@ import java.util.Map; import java.util.UUID; import java.util.concurrent.Callable; import java.util.concurrent.locks.ReentrantLock; -import javax.annotation.Resource; +import jakarta.annotation.Resource; import org.apache.logging.log4j.Logger; import org.dspace.importer.external.exception.MetadataSourceException; import org.dspace.importer.external.exception.SourceExceptionHandler; @@ -167,9 +167,9 @@ public abstract class AbstractRemoteMetadataSource { } catch (Exception e) { throwSourceException(retry, e, operationId); } - log.info("operation " + operationId + " started"); + log.debug("Operation {} started. Calling {}", operationId, callable.getClass().getName()); T response = callable.call(); - log.info("operation " + operationId + " successful"); + log.debug("Operation {} successful", operationId); return response; } catch (Exception e) { this.error = e; @@ -180,7 +180,8 @@ public abstract class AbstractRemoteMetadataSource { // No MetadataSourceException has interrupted the loop retry++; - log.warn("Error in trying operation " + operationId + " " + retry + " " + warning + ", retrying !", e); + log.warn("Error in calling {} in operation {} {} {}, retrying!", callable.getClass().getName(), + operationId, retry, warning, e); } finally { this.lastRequest = System.currentTimeMillis(); diff --git a/dspace-api/src/main/java/org/dspace/importer/external/vufind/VuFindImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/vufind/VuFindImportMetadataSourceServiceImpl.java index a4f90fa5ba..8933569a06 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/vufind/VuFindImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/vufind/VuFindImportMetadataSourceServiceImpl.java @@ -15,11 +15,11 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.concurrent.Callable; -import javax.el.MethodNotFoundException; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.el.MethodNotFoundException; import org.apache.commons.lang3.StringUtils; import org.apache.http.client.utils.URIBuilder; import org.apache.logging.log4j.LogManager; diff --git a/dspace-api/src/main/java/org/dspace/importer/external/vufind/metadatamapping/VuFindFieldMapping.java b/dspace-api/src/main/java/org/dspace/importer/external/vufind/metadatamapping/VuFindFieldMapping.java index b14927a14c..d96265372f 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/vufind/metadatamapping/VuFindFieldMapping.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/vufind/metadatamapping/VuFindFieldMapping.java @@ -8,8 +8,8 @@ package org.dspace.importer.external.vufind.metadatamapping; import java.util.Map; -import javax.annotation.Resource; +import jakarta.annotation.Resource; import org.dspace.importer.external.metadatamapping.AbstractMetadataFieldMapping; /** diff --git a/dspace-api/src/main/java/org/dspace/importer/external/wos/service/WOSFieldMapping.java b/dspace-api/src/main/java/org/dspace/importer/external/wos/service/WOSFieldMapping.java index be4acfbcea..6cf58fdd7b 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/wos/service/WOSFieldMapping.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/wos/service/WOSFieldMapping.java @@ -7,8 +7,8 @@ */ package org.dspace.importer.external.wos.service; import java.util.Map; -import javax.annotation.Resource; +import jakarta.annotation.Resource; import org.dspace.importer.external.metadatamapping.AbstractMetadataFieldMapping; /** diff --git a/dspace-api/src/main/java/org/dspace/importer/external/wos/service/WOSImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/wos/service/WOSImportMetadataSourceServiceImpl.java index f550b65995..c7b5aaa49e 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/wos/service/WOSImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/wos/service/WOSImportMetadataSourceServiceImpl.java @@ -21,8 +21,8 @@ import java.util.Map; import java.util.concurrent.Callable; import java.util.regex.Matcher; import java.util.regex.Pattern; -import javax.el.MethodNotFoundException; +import jakarta.el.MethodNotFoundException; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; diff --git a/dspace-api/src/main/java/org/dspace/orcid/OrcidHistory.java b/dspace-api/src/main/java/org/dspace/orcid/OrcidHistory.java index 07a79384c7..4665fe4428 100644 --- a/dspace-api/src/main/java/org/dspace/orcid/OrcidHistory.java +++ b/dspace-api/src/main/java/org/dspace/orcid/OrcidHistory.java @@ -8,24 +8,23 @@ package org.dspace.orcid; import java.util.Date; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.JoinColumn; -import javax.persistence.Lob; -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.EnumType; +import jakarta.persistence.Enumerated; +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.Item; import org.dspace.core.ReloadableEntity; -import org.hibernate.annotations.Type; +import org.hibernate.Length; /** * The ORCID history entity that it contains information relating to an attempt @@ -79,18 +78,14 @@ public class OrcidHistory implements ReloadableEntity { /** * A description of the synchronized resource. */ - @Lob - @Type(type = "org.hibernate.type.TextType") - @Column(name = "description") + @Column(name = "description", length = Length.LONG32) private String description; /** * The signature of the synchronized metadata. This is used when the entity is * the owner itself. */ - @Lob - @Type(type = "org.hibernate.type.TextType") - @Column(name = "metadata") + @Column(name = "metadata", length = Length.LONG32) private String metadata; /** @@ -103,9 +98,7 @@ public class OrcidHistory implements ReloadableEntity { /** * The response message incoming from ORCID. */ - @Lob - @Type(type = "org.hibernate.type.TextType") - @Column(name = "response_message") + @Column(name = "response_message", length = Length.LONG32) private String responseMessage; /** diff --git a/dspace-api/src/main/java/org/dspace/orcid/OrcidQueue.java b/dspace-api/src/main/java/org/dspace/orcid/OrcidQueue.java index 65b66cd20c..dfcb531401 100644 --- a/dspace-api/src/main/java/org/dspace/orcid/OrcidQueue.java +++ b/dspace-api/src/main/java/org/dspace/orcid/OrcidQueue.java @@ -11,22 +11,21 @@ import static org.apache.commons.lang3.StringUtils.isEmpty; import static org.apache.commons.lang3.StringUtils.isNotEmpty; import java.util.Objects; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.JoinColumn; -import javax.persistence.Lob; -import javax.persistence.ManyToOne; -import javax.persistence.SequenceGenerator; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +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; -import org.hibernate.annotations.Type; +import org.hibernate.Length; /** * Entity that model a record on the ORCID synchronization queue. Each record in @@ -64,9 +63,7 @@ public class OrcidQueue implements ReloadableEntity { /** * A description of the resource to be synchronized. */ - @Lob - @Type(type = "org.hibernate.type.TextType") - @Column(name = "description") + @Column(name = "description", length = Length.LONG32) private String description; /** @@ -87,9 +84,7 @@ public class OrcidQueue implements ReloadableEntity { * The signature of the metadata to be synchronized. This is used when the * entity is the owner itself. */ - @Lob - @Column(name = "metadata") - @Type(type = "org.hibernate.type.TextType") + @Column(name = "metadata", length = Length.LONG32) private String metadata; /** diff --git a/dspace-api/src/main/java/org/dspace/orcid/OrcidToken.java b/dspace-api/src/main/java/org/dspace/orcid/OrcidToken.java index def289daf4..3e54a68a23 100644 --- a/dspace-api/src/main/java/org/dspace/orcid/OrcidToken.java +++ b/dspace-api/src/main/java/org/dspace/orcid/OrcidToken.java @@ -7,17 +7,16 @@ */ package org.dspace.orcid; -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.OneToOne; -import javax.persistence.SequenceGenerator; -import javax.persistence.Table; - +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.OneToOne; +import jakarta.persistence.SequenceGenerator; +import jakarta.persistence.Table; import org.dspace.content.Item; import org.dspace.core.ReloadableEntity; import org.dspace.eperson.EPerson; diff --git a/dspace-api/src/main/java/org/dspace/orcid/client/OrcidClientImpl.java b/dspace-api/src/main/java/org/dspace/orcid/client/OrcidClientImpl.java index 3e7ca7b210..2dc1d591fd 100644 --- a/dspace-api/src/main/java/org/dspace/orcid/client/OrcidClientImpl.java +++ b/dspace-api/src/main/java/org/dspace/orcid/client/OrcidClientImpl.java @@ -21,14 +21,14 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; -import javax.xml.bind.JAXBContext; -import javax.xml.bind.JAXBException; -import javax.xml.bind.Marshaller; -import javax.xml.bind.Unmarshaller; import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLStreamReader; import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.xml.bind.JAXBContext; +import jakarta.xml.bind.JAXBException; +import jakarta.xml.bind.Marshaller; +import jakarta.xml.bind.Unmarshaller; import org.apache.commons.io.IOUtils; import org.apache.http.Header; import org.apache.http.HttpEntity; @@ -261,7 +261,7 @@ public class OrcidClientImpl implements OrcidClient { * OrcidClientException * @param clazz the class to be used for the content unmarshall * @return the response body - * @throws OrcidClientException if the incoming response is not successfull + * @throws OrcidClientException if the incoming response is not successful */ private T executeAndUnmarshall(HttpUriRequest httpUriRequest, boolean handleNotFoundAsNull, Class clazz) { diff --git a/dspace-api/src/main/java/org/dspace/orcid/consumer/OrcidQueueConsumer.java b/dspace-api/src/main/java/org/dspace/orcid/consumer/OrcidQueueConsumer.java index d177e61607..97da341fb8 100644 --- a/dspace-api/src/main/java/org/dspace/orcid/consumer/OrcidQueueConsumer.java +++ b/dspace-api/src/main/java/org/dspace/orcid/consumer/OrcidQueueConsumer.java @@ -22,6 +22,8 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.content.DSpaceObject; import org.dspace.content.Item; import org.dspace.content.MetadataFieldName; @@ -45,8 +47,6 @@ import org.dspace.orcid.service.OrcidTokenService; import org.dspace.profile.OrcidProfileSyncPreference; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * The consumer to fill the ORCID queue. The addition to the queue is made for @@ -56,7 +56,7 @@ import org.slf4j.LoggerFactory; * be synchronized (based on the preferences set by the user) *

  • are publications/fundings related to profile items linked to orcid (based * on the preferences set by the user)
  • - * + * * * * @author Luca Giamminonni (luca.giamminonni at 4science.it) @@ -64,7 +64,7 @@ import org.slf4j.LoggerFactory; */ public class OrcidQueueConsumer implements Consumer { - private static final Logger LOGGER = LoggerFactory.getLogger(OrcidQueueConsumer.class); + private static final Logger LOGGER = LogManager.getLogger(); private OrcidQueueService orcidQueueService; @@ -82,7 +82,7 @@ public class OrcidQueueConsumer implements Consumer { private RelationshipService relationshipService; - private List alreadyConsumedItems = new ArrayList<>(); + private final List alreadyConsumedItems = new ArrayList<>(); @Override public void initialize() throws Exception { @@ -263,7 +263,7 @@ public class OrcidQueueConsumer implements Consumer { if (StringUtils.isBlank(putCode)) { LOGGER.warn("The orcid history record with id {} should have a not blank put code", - historyRecord.getID()); + historyRecord::getID); continue; } diff --git a/dspace-api/src/main/java/org/dspace/orcid/dao/impl/OrcidHistoryDAOImpl.java b/dspace-api/src/main/java/org/dspace/orcid/dao/impl/OrcidHistoryDAOImpl.java index 0b2c7099ff..fe300751d1 100644 --- a/dspace-api/src/main/java/org/dspace/orcid/dao/impl/OrcidHistoryDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/orcid/dao/impl/OrcidHistoryDAOImpl.java @@ -10,8 +10,8 @@ package org.dspace.orcid.dao.impl; import java.sql.SQLException; import java.util.List; import java.util.UUID; -import javax.persistence.Query; +import jakarta.persistence.Query; import org.dspace.content.Item; import org.dspace.core.AbstractHibernateDAO; import org.dspace.core.Context; diff --git a/dspace-api/src/main/java/org/dspace/orcid/dao/impl/OrcidQueueDAOImpl.java b/dspace-api/src/main/java/org/dspace/orcid/dao/impl/OrcidQueueDAOImpl.java index 2114b25357..c8e48e3f17 100644 --- a/dspace-api/src/main/java/org/dspace/orcid/dao/impl/OrcidQueueDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/orcid/dao/impl/OrcidQueueDAOImpl.java @@ -10,8 +10,8 @@ package org.dspace.orcid.dao.impl; import java.sql.SQLException; import java.util.List; import java.util.UUID; -import javax.persistence.Query; +import jakarta.persistence.Query; import org.dspace.content.Item; import org.dspace.core.AbstractHibernateDAO; import org.dspace.core.Context; diff --git a/dspace-api/src/main/java/org/dspace/orcid/dao/impl/OrcidTokenDAOImpl.java b/dspace-api/src/main/java/org/dspace/orcid/dao/impl/OrcidTokenDAOImpl.java index 01b03fc354..f94f5ad3a4 100644 --- a/dspace-api/src/main/java/org/dspace/orcid/dao/impl/OrcidTokenDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/orcid/dao/impl/OrcidTokenDAOImpl.java @@ -8,8 +8,8 @@ package org.dspace.orcid.dao.impl; import java.sql.SQLException; -import javax.persistence.Query; +import jakarta.persistence.Query; import org.dspace.content.Item; import org.dspace.core.AbstractHibernateDAO; import org.dspace.core.Context; diff --git a/dspace-api/src/main/java/org/dspace/orcid/model/factory/OrcidCommonObjectFactory.java b/dspace-api/src/main/java/org/dspace/orcid/model/factory/OrcidCommonObjectFactory.java index 4ca36c2169..9a821bef8d 100644 --- a/dspace-api/src/main/java/org/dspace/orcid/model/factory/OrcidCommonObjectFactory.java +++ b/dspace-api/src/main/java/org/dspace/orcid/model/factory/OrcidCommonObjectFactory.java @@ -35,7 +35,7 @@ public interface OrcidCommonObjectFactory { * represent a date with a supported format. * * @param metadataValue the metadata value - * @return the FuzzyDate istance, if any + * @return the FuzzyDate instance, if any */ public Optional createFuzzyDate(MetadataValue metadataValue); diff --git a/dspace-api/src/main/java/org/dspace/orcid/model/factory/impl/OrcidCommonObjectFactoryImpl.java b/dspace-api/src/main/java/org/dspace/orcid/model/factory/impl/OrcidCommonObjectFactoryImpl.java index 2f47aa53d6..1195d27c4f 100644 --- a/dspace-api/src/main/java/org/dspace/orcid/model/factory/impl/OrcidCommonObjectFactoryImpl.java +++ b/dspace-api/src/main/java/org/dspace/orcid/model/factory/impl/OrcidCommonObjectFactoryImpl.java @@ -172,7 +172,7 @@ public class OrcidCommonObjectFactoryImpl implements OrcidCommonObjectFactory { private ContributorAttributes getContributorAttributes(MetadataValue metadataValue, ContributorRole role) { ContributorAttributes attributes = new ContributorAttributes(); - attributes.setContributorRole(role != null ? role : null); + attributes.setContributorRole(role != null ? role.value() : null); attributes.setContributorSequence(metadataValue.getPlace() == 0 ? FIRST : ADDITIONAL); return attributes; } @@ -191,7 +191,7 @@ public class OrcidCommonObjectFactoryImpl implements OrcidCommonObjectFactory { private FundingContributorAttributes getFundingContributorAttributes(MetadataValue metadataValue, FundingContributorRole role) { FundingContributorAttributes attributes = new FundingContributorAttributes(); - attributes.setContributorRole(role != null ? role : null); + attributes.setContributorRole(role != null ? role.value() : null); return attributes; } diff --git a/dspace-api/src/main/java/org/dspace/orcid/model/factory/impl/OrcidFundingFactory.java b/dspace-api/src/main/java/org/dspace/orcid/model/factory/impl/OrcidFundingFactory.java index 890b54f12b..fd2669935c 100644 --- a/dspace-api/src/main/java/org/dspace/orcid/model/factory/impl/OrcidFundingFactory.java +++ b/dspace-api/src/main/java/org/dspace/orcid/model/factory/impl/OrcidFundingFactory.java @@ -18,6 +18,8 @@ import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.Stream; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.content.Item; import org.dspace.content.MetadataValue; import org.dspace.content.Relationship; @@ -44,8 +46,6 @@ import org.orcid.jaxb.model.v3.release.record.Funding; import org.orcid.jaxb.model.v3.release.record.FundingContributor; import org.orcid.jaxb.model.v3.release.record.FundingContributors; import org.orcid.jaxb.model.v3.release.record.FundingTitle; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; /** @@ -57,7 +57,7 @@ import org.springframework.beans.factory.annotation.Autowired; */ public class OrcidFundingFactory implements OrcidEntityFactory { - private static final Logger LOGGER = LoggerFactory.getLogger(OrcidFundingFactory.class); + private static final Logger LOGGER = LogManager.getLogger(); @Autowired private ItemService itemService; diff --git a/dspace-api/src/main/java/org/dspace/orcid/model/factory/impl/OrcidWorkFactory.java b/dspace-api/src/main/java/org/dspace/orcid/model/factory/impl/OrcidWorkFactory.java index 53b46d8256..7018c221ba 100644 --- a/dspace-api/src/main/java/org/dspace/orcid/model/factory/impl/OrcidWorkFactory.java +++ b/dspace-api/src/main/java/org/dspace/orcid/model/factory/impl/OrcidWorkFactory.java @@ -19,6 +19,8 @@ import java.util.Optional; import java.util.stream.Collectors; import org.apache.commons.lang3.EnumUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.content.Item; import org.dspace.content.MetadataValue; import org.dspace.content.service.ItemService; @@ -42,8 +44,6 @@ import org.orcid.jaxb.model.v3.release.record.ExternalIDs; import org.orcid.jaxb.model.v3.release.record.Work; import org.orcid.jaxb.model.v3.release.record.WorkContributors; import org.orcid.jaxb.model.v3.release.record.WorkTitle; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; /** @@ -55,7 +55,7 @@ import org.springframework.beans.factory.annotation.Autowired; */ public class OrcidWorkFactory implements OrcidEntityFactory { - private static final Logger LOGGER = LoggerFactory.getLogger(OrcidWorkFactory.class); + private static final Logger LOGGER = LogManager.getLogger(); @Autowired private ItemService itemService; @@ -164,7 +164,7 @@ public class OrcidWorkFactory implements OrcidEntityFactory { */ private List getWorkSelfExternalIds(Context context, Item item) { - List selfExternalIds = new ArrayList(); + List selfExternalIds = new ArrayList<>(); Map externalIdentifierFields = fieldMapping.getExternalIdentifierFields(); @@ -205,7 +205,7 @@ public class OrcidWorkFactory implements OrcidEntityFactory { } /** - * Creates an instance of WorkType from the given item, taking the value fom the + * Creates an instance of WorkType from the given item, taking the value from the * configured metadata field (orcid.mapping.work.type). */ private WorkType getWorkType(Context context, Item item) { diff --git a/dspace-api/src/main/java/org/dspace/orcid/script/OrcidBulkPush.java b/dspace-api/src/main/java/org/dspace/orcid/script/OrcidBulkPush.java index 0e6f856bfc..7dceb473cc 100644 --- a/dspace-api/src/main/java/org/dspace/orcid/script/OrcidBulkPush.java +++ b/dspace-api/src/main/java/org/dspace/orcid/script/OrcidBulkPush.java @@ -20,6 +20,8 @@ import java.util.stream.Collectors; import org.apache.commons.cli.ParseException; import org.apache.commons.lang3.exception.ExceptionUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.content.Item; import org.dspace.core.Context; import org.dspace.eperson.EPerson; @@ -36,8 +38,6 @@ import org.dspace.scripts.DSpaceRunnable; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.utils.DSpace; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * Script that perform the bulk synchronization with ORCID registry of all the @@ -48,7 +48,7 @@ import org.slf4j.LoggerFactory; */ public class OrcidBulkPush extends DSpaceRunnable> { - private static final Logger LOGGER = LoggerFactory.getLogger(OrcidBulkPush.class); + private static final Logger LOGGER = LogManager.getLogger(); private OrcidQueueService orcidQueueService; @@ -63,7 +63,7 @@ public class OrcidBulkPush extends DSpaceRunnable synchronizationModeByProfileItem = new HashMap<>(); + private final Map synchronizationModeByProfileItem = new HashMap<>(); private boolean ignoreMaxAttempts = false; @@ -105,7 +105,7 @@ public class OrcidBulkPush extends DSpaceRunnable findLastPutCodes(Context context, Item entity) throws SQLException { - Map profileItemAndPutCodeMap = new HashMap(); + Map profileItemAndPutCodeMap = new HashMap<>(); List orcidHistoryRecords = findByEntity(context, entity); for (OrcidHistory orcidHistoryRecord : orcidHistoryRecords) { @@ -187,10 +187,12 @@ public class OrcidHistoryServiceImpl implements OrcidHistoryService { } catch (OrcidValidationException ex) { throw ex; } catch (OrcidClientException ex) { - LOGGER.error("An error occurs during the orcid synchronization of ORCID queue " + orcidQueue, ex); + LOGGER.error("An error occurs during the orcid synchronization of ORCID queue {}", + orcidQueue, ex); return createHistoryRecordFromOrcidError(context, orcidQueue, operation, ex); } catch (RuntimeException ex) { - LOGGER.warn("An unexpected error occurs during the orcid synchronization of ORCID queue " + orcidQueue, ex); + LOGGER.warn("An unexpected error occurs during the orcid synchronization of ORCID queue {}", + orcidQueue, ex); return createHistoryRecordFromGenericError(context, orcidQueue, operation, ex); } diff --git a/dspace-api/src/main/java/org/dspace/orcid/service/impl/OrcidSynchronizationServiceImpl.java b/dspace-api/src/main/java/org/dspace/orcid/service/impl/OrcidSynchronizationServiceImpl.java index 97d832d3de..59e4dea641 100644 --- a/dspace-api/src/main/java/org/dspace/orcid/service/impl/OrcidSynchronizationServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/orcid/service/impl/OrcidSynchronizationServiceImpl.java @@ -118,6 +118,10 @@ public class OrcidSynchronizationServiceImpl implements OrcidSynchronizationServ itemService.clearMetadata(context, profile, "dspace", "orcid", "scope", Item.ANY); itemService.clearMetadata(context, profile, "dspace", "orcid", "authenticated", Item.ANY); + if (!configurationService.getBooleanProperty("orcid.disconnection.remain-sync", false)) { + clearSynchronizationSettings(context, profile); + } + orcidTokenService.deleteByProfileItem(context, profile); updateItem(context, profile); @@ -267,6 +271,17 @@ public class OrcidSynchronizationServiceImpl implements OrcidSynchronizationServ } + private void clearSynchronizationSettings(Context context, Item profile) + throws SQLException { + itemService.clearMetadata(context, profile, "dspace", "orcid", "sync-mode", Item.ANY); + itemService.clearMetadata(context, profile, "dspace", "orcid", "sync-profile", Item.ANY); + + for (OrcidEntityType entityType : OrcidEntityType.values()) { + itemService.clearMetadata(context, profile, "dspace", "orcid", + "sync-" + entityType.name().toLowerCase() + "s", Item.ANY); + } + } + private boolean containsSameValues(List firstList, List secondList) { return new HashSet<>(firstList).equals(new HashSet<>(secondList)); } diff --git a/dspace-api/src/main/java/org/dspace/profile/ResearcherProfileServiceImpl.java b/dspace-api/src/main/java/org/dspace/profile/ResearcherProfileServiceImpl.java index 80bbd68fd1..5de1ffa4ac 100644 --- a/dspace-api/src/main/java/org/dspace/profile/ResearcherProfileServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/profile/ResearcherProfileServiceImpl.java @@ -23,10 +23,12 @@ import java.util.Iterator; import java.util.List; import java.util.Optional; import java.util.UUID; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.app.exception.ResourceAlreadyExistsException; import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.service.AuthorizeService; @@ -53,8 +55,6 @@ import org.dspace.profile.service.AfterResearcherProfileCreationAction; import org.dspace.profile.service.ResearcherProfileService; import org.dspace.services.ConfigurationService; import org.dspace.util.UUIDUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.Assert; @@ -66,7 +66,7 @@ import org.springframework.util.Assert; */ public class ResearcherProfileServiceImpl implements ResearcherProfileService { - private static Logger log = LoggerFactory.getLogger(ResearcherProfileServiceImpl.class); + private static final Logger log = LogManager.getLogger(); @Autowired private ItemService itemService; @@ -310,7 +310,7 @@ public class ResearcherProfileServiceImpl implements ResearcherProfileService { if (isNotProfileCollection(collection)) { log.warn("The configured researcher-profile.collection.uuid " - + "has an invalid entity type, expected " + getProfileType()); + + "has an invalid entity type, expected {}", this::getProfileType); return Optional.empty(); } diff --git a/dspace-api/src/main/java/org/dspace/profile/service/ResearcherProfileService.java b/dspace-api/src/main/java/org/dspace/profile/service/ResearcherProfileService.java index 9e52402f77..de01760f9f 100644 --- a/dspace-api/src/main/java/org/dspace/profile/service/ResearcherProfileService.java +++ b/dspace-api/src/main/java/org/dspace/profile/service/ResearcherProfileService.java @@ -67,7 +67,7 @@ public interface ResearcherProfileService { /** * Changes the visibility of the given profile using the given new visible - * value. The visiblity controls whether the Profile is Anonymous READ or not. + * value. The visibility controls whether the Profile is Anonymous READ or not. * * @param context the relevant DSpace Context. * @param profile the researcher profile to update diff --git a/dspace-api/src/main/java/org/dspace/qaevent/AutomaticProcessingAction.java b/dspace-api/src/main/java/org/dspace/qaevent/AutomaticProcessingAction.java new file mode 100644 index 0000000000..771650746d --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/qaevent/AutomaticProcessingAction.java @@ -0,0 +1,17 @@ +/** + * 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.qaevent; + +/** + * Enumeration of possible actions to perform over a {@link org.dspace.content.QAEvent} + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + */ +public enum AutomaticProcessingAction { + REJECT, ACCEPT, IGNORE +} diff --git a/dspace-api/src/main/java/org/dspace/qaevent/QAEventAutomaticProcessingEvaluation.java b/dspace-api/src/main/java/org/dspace/qaevent/QAEventAutomaticProcessingEvaluation.java new file mode 100644 index 0000000000..d7c8f3681e --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/qaevent/QAEventAutomaticProcessingEvaluation.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.qaevent; + +import org.dspace.content.QAEvent; +import org.dspace.core.Context; + +/** + * This interface allows the implemnetation of Automation Processing rules + * defining which {@link AutomaticProcessingAction} should be eventually + * performed on a specific {@link QAEvent} + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + */ +public interface QAEventAutomaticProcessingEvaluation { + + /** + * Evaluate a {@link QAEvent} to decide which, if any, {@link AutomaticProcessingAction} should be performed + * + * @param context the DSpace context + * @param qaEvent the quality assurance event + * @return an action of {@link AutomaticProcessingAction} or null if no automatic action should be performed + */ + AutomaticProcessingAction evaluateAutomaticProcessing(Context context, QAEvent qaEvent); + +} diff --git a/dspace-api/src/main/java/org/dspace/qaevent/QAEventsDeleteCascadeConsumer.java b/dspace-api/src/main/java/org/dspace/qaevent/QAEventsDeleteCascadeConsumer.java new file mode 100644 index 0000000000..6460c360ec --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/qaevent/QAEventsDeleteCascadeConsumer.java @@ -0,0 +1,50 @@ +/** + * 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.qaevent; + +import org.dspace.core.Constants; +import org.dspace.core.Context; +import org.dspace.event.Consumer; +import org.dspace.event.Event; +import org.dspace.qaevent.service.QAEventService; +import org.dspace.utils.DSpace; + +/** + * Consumer to delete qaevents from solr due to the target item deletion + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + * + */ +public class QAEventsDeleteCascadeConsumer implements Consumer { + + private QAEventService qaEventService; + + @Override + public void initialize() throws Exception { + qaEventService = new DSpace().getSingletonService(QAEventService.class); + } + + @Override + public void finish(Context context) throws Exception { + + } + + @Override + public void consume(Context context, Event event) throws Exception { + if (event.getEventType() == Event.DELETE) { + if (event.getSubjectType() == Constants.ITEM && event.getSubjectID() != null) { + qaEventService.deleteEventsByTargetId(event.getSubjectID()); + } + } + } + + public void end(Context context) throws Exception { + } + +} diff --git a/dspace-api/src/main/java/org/dspace/qaevent/QANotifyPatterns.java b/dspace-api/src/main/java/org/dspace/qaevent/QANotifyPatterns.java new file mode 100644 index 0000000000..ede1990569 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/qaevent/QANotifyPatterns.java @@ -0,0 +1,30 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.qaevent; + +/** + * Constants for Quality Assurance configurations to be used into cfg and xml spring. + * + * @author Francesco Bacchelli (francesco.bacchelli at 4science.it) + */ +public class QANotifyPatterns { + + public static final String TOPIC_ENRICH_MORE_PROJECT = "ENRICH/MORE/PROJECT"; + public static final String TOPIC_ENRICH_MISSING_PROJECT = "ENRICH/MISSING/PROJECT"; + public static final String TOPIC_ENRICH_MISSING_ABSTRACT = "ENRICH/MISSING/ABSTRACT"; + public static final String TOPIC_ENRICH_MORE_REVIEW = "ENRICH/MORE/REVIEW"; + public static final String TOPIC_ENRICH_MORE_ENDORSEMENT = "ENRICH/MORE/ENDORSEMENT"; + public static final String TOPIC_ENRICH_MORE_PID = "ENRICH/MORE/PID"; + public static final String TOPIC_ENRICH_MISSING_PID = "ENRICH/MISSING/PID"; + public static final String TOPIC_ENRICH_MORE_LINK = "ENRICH/MORE/LINK"; + + /** + * Default constructor + */ + private QANotifyPatterns() { } +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/qaevent/QAScoreAutomaticProcessingEvaluation.java b/dspace-api/src/main/java/org/dspace/qaevent/QAScoreAutomaticProcessingEvaluation.java new file mode 100644 index 0000000000..f685222d3d --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/qaevent/QAScoreAutomaticProcessingEvaluation.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.qaevent; + +import java.sql.SQLException; +import java.util.UUID; + +import org.dspace.content.Item; +import org.dspace.content.QAEvent; +import org.dspace.content.logic.LogicalStatement; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * A configurable implementation of {@link QAEventAutomaticProcessingEvaluation} allowing to define thresholds for + * automatic acceptance, rejection or ignore of {@link QAEvent} matching a specific, optional, item filter + * {@link LogicalStatement}. If the item filter is not defined only the score threshold will be used. + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + */ +public class QAScoreAutomaticProcessingEvaluation implements QAEventAutomaticProcessingEvaluation { + /** + * The minimum score of QAEvent to be considered for automatic approval (trust must be greater or equals to that) + */ + private double scoreToApprove; + + /** + * The threshold under which QAEvent are considered for automatic ignore (trust must be less or equals to that) + */ + private double scoreToIgnore; + + /** + * The threshold under which QAEvent are considered for automatic rejection (trust must be less or equals to that) + */ + private double scoreToReject; + + /** + * The optional logical statement that must pass for item target of a QAEvent to be considered for automatic + * approval + */ + private LogicalStatement itemFilterToApprove; + + /** + * The optional logical statement that must pass for item target of a QAEvent to be considered for automatic + * ignore + */ + private LogicalStatement itemFilterToIgnore; + + /** + * The optional logical statement that must pass for item target of a QAEvent to be considered for automatic + * rejection + */ + private LogicalStatement itemFilterToReject; + + @Autowired + private ItemService itemService; + + @Override + public AutomaticProcessingAction evaluateAutomaticProcessing(Context context, QAEvent qaEvent) { + Item item = findItem(context, qaEvent.getTarget()); + + if (shouldReject(context, qaEvent.getTrust(), item)) { + return AutomaticProcessingAction.REJECT; + } else if (shouldIgnore(context, qaEvent.getTrust(), item)) { + return AutomaticProcessingAction.IGNORE; + } else if (shouldApprove(context, qaEvent.getTrust(), item)) { + return AutomaticProcessingAction.ACCEPT; + } else { + return null; + } + + } + + private Item findItem(Context context, String uuid) { + try { + return itemService.find(context, UUID.fromString(uuid)); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + private boolean shouldReject(Context context, double trust, Item item) { + return trust <= scoreToReject && + (itemFilterToReject == null || itemFilterToReject.getResult(context, item)); + } + + private boolean shouldIgnore(Context context, double trust, Item item) { + return trust <= scoreToIgnore && + (itemFilterToIgnore == null || itemFilterToIgnore.getResult(context, item)); + } + + private boolean shouldApprove(Context context, double trust, Item item) { + return trust >= scoreToApprove && + (itemFilterToApprove == null || itemFilterToApprove.getResult(context, item)); + } + + public double getScoreToApprove() { + return scoreToApprove; + } + + public void setScoreToApprove(double scoreToApprove) { + this.scoreToApprove = scoreToApprove; + } + + public double getScoreToIgnore() { + return scoreToIgnore; + } + + public void setScoreToIgnore(double scoreToIgnore) { + this.scoreToIgnore = scoreToIgnore; + } + + public double getScoreToReject() { + return scoreToReject; + } + + public void setScoreToReject(double scoreToReject) { + this.scoreToReject = scoreToReject; + } + + public LogicalStatement getItemFilterToApprove() { + return itemFilterToApprove; + } + + public void setItemFilterToApprove(LogicalStatement itemFilterToApprove) { + this.itemFilterToApprove = itemFilterToApprove; + } + + public LogicalStatement getItemFilterToIgnore() { + return itemFilterToIgnore; + } + + public void setItemFilterToIgnore(LogicalStatement itemFilterToIgnore) { + this.itemFilterToIgnore = itemFilterToIgnore; + } + + public LogicalStatement getItemFilterToReject() { + return itemFilterToReject; + } + + public void setItemFilterToReject(LogicalStatement itemFilterToReject) { + this.itemFilterToReject = itemFilterToReject; + } +} + diff --git a/dspace-api/src/main/java/org/dspace/qaevent/QASource.java b/dspace-api/src/main/java/org/dspace/qaevent/QASource.java new file mode 100644 index 0000000000..10849b47fc --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/qaevent/QASource.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.qaevent; + +import java.util.Date; +import java.util.UUID; + +/** + * This model class represent the source/provider of the QA events (as Openaire). + * + * @author Luca Giamminonni (luca.giamminonni at 4Science) + * + */ +public class QASource { + + /** + * The focus attributes specify if the QASource object is describing the status of a specific + * quality assurance source for the whole repository (focus = null) or for a specific + * DSpaceObject (focus = uuid of the DSpaceObject). This would mostly affect the totalEvents attribute below. + */ + private UUID focus; + private String name; + private Date lastEvent; + private long totalEvents; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public long getTotalEvents() { + return totalEvents; + } + + public void setTotalEvents(long totalEvents) { + this.totalEvents = totalEvents; + } + + public Date getLastEvent() { + return lastEvent; + } + + public void setLastEvent(Date lastEvent) { + this.lastEvent = lastEvent; + } + + public UUID getFocus() { + return focus; + } + + public void setFocus(UUID focus) { + this.focus = focus; + } + + @Override + public String toString() { + return name + focus + totalEvents; + } +} diff --git a/dspace-api/src/main/java/org/dspace/qaevent/QATopic.java b/dspace-api/src/main/java/org/dspace/qaevent/QATopic.java new file mode 100644 index 0000000000..92fe3737f4 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/qaevent/QATopic.java @@ -0,0 +1,73 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.qaevent; + +import java.util.Date; +import java.util.UUID; + +/** + * This model class represent the quality assurance broker topic concept. A + * topic represents a type of event and is therefore used to group events. + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + */ +public class QATopic { + + /** + * The focus attributes specify if the QATopic object is describing the status of a specific + * quality assurance topic for the whole repository (focus = null) or for a specific + * DSpaceObject (focus = uuid of the DSpaceObject). This would mostly affect the totalEvents attribute below. + */ + private UUID focus; + private String key; + /** + * The source attributes contains the name of the QA source like: OpenAIRE, DSpaceUsers + */ + private String source; + private Date lastEvent; + private long totalEvents; + + public String getSource() { + return source; + } + + public void setSource(String source) { + this.source = source; + } + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public void setFocus(UUID focus) { + this.focus = focus; + } + + public UUID getFocus() { + return focus; + } + + public long getTotalEvents() { + return totalEvents; + } + + public void setTotalEvents(long totalEvents) { + this.totalEvents = totalEvents; + } + + public Date getLastEvent() { + return lastEvent; + } + + public void setLastEvent(Date lastEvent) { + this.lastEvent = lastEvent; + } +} diff --git a/dspace-api/src/main/java/org/dspace/qaevent/QualityAssuranceAction.java b/dspace-api/src/main/java/org/dspace/qaevent/QualityAssuranceAction.java new file mode 100644 index 0000000000..f2aebba799 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/qaevent/QualityAssuranceAction.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.qaevent; + +import org.dspace.content.Item; +import org.dspace.core.Context; +import org.dspace.qaevent.service.dto.QAMessageDTO; + +/** + * Interface for classes that perform a correction on the given item. + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + * + */ +public interface QualityAssuranceAction { + + /** + * Perform a correction on the given item. + * + * @param context the DSpace context + * @param item the item to correct + * @param relatedItem the related item, if any + * @param message the message with the correction details + */ + public void applyCorrection(Context context, Item item, Item relatedItem, QAMessageDTO message); +} diff --git a/dspace-api/src/main/java/org/dspace/qaevent/action/AMetadataMapAction.java b/dspace-api/src/main/java/org/dspace/qaevent/action/AMetadataMapAction.java new file mode 100644 index 0000000000..ee81988f63 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/qaevent/action/AMetadataMapAction.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.qaevent.action; + +import java.sql.SQLException; +import java.util.Map; + +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.Item; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.dspace.qaevent.QualityAssuranceAction; +import org.dspace.qaevent.service.dto.QAMessageDTO; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Implementation of {@link QualityAssuranceAction} that add a specific metadata on the given + * item based on the child class implementation. + * + * @author Francesco Bacchelli (francesco.bacchelli at 4science.it) + * + */ +public abstract class AMetadataMapAction implements QualityAssuranceAction { + public static final String DEFAULT = "default"; + + private Map types; + @Autowired + private ItemService itemService; + + public void setItemService(ItemService itemService) { + this.itemService = itemService; + } + + public Map getTypes() { + return types; + } + + public void setTypes(Map types) { + this.types = types; + } + + public abstract String extractMetadataType(QAMessageDTO message); + public abstract String extractMetadataValue(QAMessageDTO message); + + /** + * Apply the correction on one metadata field of the given item based on the + * openaire message type. + */ + @Override + public void applyCorrection(Context context, Item item, Item relatedItem, QAMessageDTO message) { + + try { + String targetMetadata = types.get(extractMetadataType(message)); + if (targetMetadata == null) { + targetMetadata = types.get(DEFAULT); + } + String[] metadata = splitMetadata(targetMetadata); + itemService.addMetadata(context, item, metadata[0], metadata[1], metadata[2], null, + extractMetadataValue(message)); + itemService.update(context, item); + } catch (SQLException | AuthorizeException e) { + throw new RuntimeException(e); + } + + } + + public String[] splitMetadata(String metadata) { + String[] result = new String[3]; + String[] split = metadata.split("\\."); + result[0] = split[0]; + result[1] = split[1]; + if (split.length == 3) { + result[2] = split[2]; + } + return result; + } +} diff --git a/dspace-api/src/main/java/org/dspace/qaevent/action/ASimpleMetadataAction.java b/dspace-api/src/main/java/org/dspace/qaevent/action/ASimpleMetadataAction.java new file mode 100644 index 0000000000..3acaa726e0 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/qaevent/action/ASimpleMetadataAction.java @@ -0,0 +1,65 @@ +/** + * 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.qaevent.action; + +import java.sql.SQLException; + +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.Item; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.dspace.qaevent.QualityAssuranceAction; +import org.dspace.qaevent.service.dto.QAMessageDTO; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Abstract class for Simple metadata action. + * + * @author Francesco Bacchelli (francesco.bacchelli at 4science.it) + * + */ +public abstract class ASimpleMetadataAction implements QualityAssuranceAction { + private String metadata; + private String metadataSchema; + private String metadataElement; + private String metadataQualifier; + @Autowired + private ItemService itemService; + + public void setItemService(ItemService itemService) { + this.itemService = itemService; + } + + public String getMetadata() { + return metadata; + } + + public void setMetadata(String metadata) { + this.metadata = metadata; + String[] split = metadata.split("\\."); + this.metadataSchema = split[0]; + this.metadataElement = split[1]; + if (split.length == 3) { + this.metadataQualifier = split[2]; + } + } + + public abstract String extractMetadataValue(QAMessageDTO message); + + @Override + public void applyCorrection(Context context, Item item, Item relatedItem, QAMessageDTO message) { + try { + String metadataValue = extractMetadataValue(message); + itemService.addMetadata(context, item, metadataSchema, metadataElement, metadataQualifier, null, + metadataValue); + itemService.update(context, item); + } catch (SQLException | AuthorizeException e) { + throw new RuntimeException(e); + } + } +} diff --git a/dspace-api/src/main/java/org/dspace/qaevent/action/QAEntityOpenaireMetadataAction.java b/dspace-api/src/main/java/org/dspace/qaevent/action/QAEntityOpenaireMetadataAction.java new file mode 100644 index 0000000000..f244418dd0 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/qaevent/action/QAEntityOpenaireMetadataAction.java @@ -0,0 +1,180 @@ +/** + * 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.qaevent.action; + +import java.sql.SQLException; +import java.util.Map; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.Collection; +import org.dspace.content.EntityType; +import org.dspace.content.Item; +import org.dspace.content.RelationshipType; +import org.dspace.content.WorkspaceItem; +import org.dspace.content.service.CollectionService; +import org.dspace.content.service.EntityTypeService; +import org.dspace.content.service.InstallItemService; +import org.dspace.content.service.ItemService; +import org.dspace.content.service.RelationshipService; +import org.dspace.content.service.RelationshipTypeService; +import org.dspace.content.service.WorkspaceItemService; +import org.dspace.core.Context; +import org.dspace.qaevent.QualityAssuranceAction; +import org.dspace.qaevent.service.dto.OpenaireMessageDTO; +import org.dspace.qaevent.service.dto.QAMessageDTO; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Implementation of {@link QualityAssuranceAction} that handle the relationship between the + * item to correct and a related item. + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + * + */ +public class QAEntityOpenaireMetadataAction implements QualityAssuranceAction { + private String relation; + private String entityType; + private Map entityMetadata; + + @Autowired + private InstallItemService installItemService; + + @Autowired + private ItemService itemService; + + @Autowired + private EntityTypeService entityTypeService; + + @Autowired + private RelationshipService relationshipService; + + @Autowired + private RelationshipTypeService relationshipTypeService; + + @Autowired + private WorkspaceItemService workspaceItemService; + + @Autowired + private CollectionService collectionService; + + public void setItemService(ItemService itemService) { + this.itemService = itemService; + } + + public String getRelation() { + return relation; + } + + public void setRelation(String relation) { + this.relation = relation; + } + + public String[] splitMetadata(String metadata) { + String[] result = new String[3]; + String[] split = metadata.split("\\."); + result[0] = split[0]; + result[1] = split[1]; + if (split.length == 3) { + result[2] = split[2]; + } + return result; + } + + public String getEntityType() { + return entityType; + } + + public void setEntityType(String entityType) { + this.entityType = entityType; + } + + public Map getEntityMetadata() { + return entityMetadata; + } + + public void setEntityMetadata(Map entityMetadata) { + this.entityMetadata = entityMetadata; + } + + @Override + public void applyCorrection(Context context, Item item, Item relatedItem, QAMessageDTO message) { + try { + if (relatedItem != null) { + link(context, item, relatedItem); + } else { + + Collection collection = collectionService.retrieveCollectionWithSubmitByEntityType(context, + item, entityType); + if (collection == null) { + throw new IllegalStateException("No collection found by entity type: " + collection); + } + + WorkspaceItem workspaceItem = workspaceItemService.create(context, collection, true); + relatedItem = workspaceItem.getItem(); + + for (String key : entityMetadata.keySet()) { + String value = getValue(message, key); + if (StringUtils.isNotBlank(value)) { + String[] targetMetadata = splitMetadata(entityMetadata.get(key)); + itemService.addMetadata(context, relatedItem, targetMetadata[0], targetMetadata[1], + targetMetadata[2], null, value); + } + } + installItemService.installItem(context, workspaceItem); + itemService.update(context, relatedItem); + link(context, item, relatedItem); + } + } catch (SQLException | AuthorizeException e) { + throw new RuntimeException(e); + } + } + + /** + * Create a new relationship between the two given item, based on the configured + * relation. + */ + private void link(Context context, Item item, Item relatedItem) throws SQLException, AuthorizeException { + EntityType project = entityTypeService.findByEntityType(context, entityType); + RelationshipType relType = relationshipTypeService.findByEntityType(context, project).stream() + .filter(r -> StringUtils.equals(r.getRightwardType(), relation)).findFirst() + .orElseThrow(() -> new IllegalStateException("No relationshipType named " + relation + + " was found for the entity type " + entityType + + ". A proper configuration is required to use the QAEntitiyMetadataAction." + + " If you don't manage funding in your repository please skip this topic in" + + " the qaevents.cfg")); + // Create the relationship + relationshipService.create(context, item, relatedItem, relType, -1, -1); + } + + private String getValue(QAMessageDTO message, String key) { + if (!(message instanceof OpenaireMessageDTO)) { + return null; + } + + OpenaireMessageDTO openaireMessage = (OpenaireMessageDTO) message; + + if (StringUtils.equals(key, "acronym")) { + return openaireMessage.getAcronym(); + } else if (StringUtils.equals(key, "code")) { + return openaireMessage.getCode(); + } else if (StringUtils.equals(key, "funder")) { + return openaireMessage.getFunder(); + } else if (StringUtils.equals(key, "fundingProgram")) { + return openaireMessage.getFundingProgram(); + } else if (StringUtils.equals(key, "jurisdiction")) { + return openaireMessage.getJurisdiction(); + } else if (StringUtils.equals(key, "openaireId")) { + return openaireMessage.getOpenaireId(); + } else if (StringUtils.equals(key, "title")) { + return openaireMessage.getTitle(); + } + + return null; + } +} diff --git a/dspace-api/src/main/java/org/dspace/qaevent/action/QANotifyMetadataMapAction.java b/dspace-api/src/main/java/org/dspace/qaevent/action/QANotifyMetadataMapAction.java new file mode 100644 index 0000000000..a85a386550 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/qaevent/action/QANotifyMetadataMapAction.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.qaevent.action; + +import org.dspace.qaevent.service.dto.NotifyMessageDTO; +import org.dspace.qaevent.service.dto.QAMessageDTO; + +/** + * Notify Implementation {@link AMetadataMapAction} + * + * @author Francesco Bacchelli (francesco.bacchelli at 4science.it) + * + */ +public class QANotifyMetadataMapAction extends AMetadataMapAction { + + @Override + public String extractMetadataType(QAMessageDTO message) { + return ((NotifyMessageDTO)message).getRelationship(); + } + + @Override + public String extractMetadataValue(QAMessageDTO message) { + return ((NotifyMessageDTO)message).getHref(); + } + +} diff --git a/dspace-api/src/main/java/org/dspace/qaevent/action/QANotifySimpleMetadataAction.java b/dspace-api/src/main/java/org/dspace/qaevent/action/QANotifySimpleMetadataAction.java new file mode 100644 index 0000000000..ffb70fce66 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/qaevent/action/QANotifySimpleMetadataAction.java @@ -0,0 +1,26 @@ +/** + * 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.qaevent.action; + +import org.dspace.qaevent.QualityAssuranceAction; +import org.dspace.qaevent.service.dto.NotifyMessageDTO; +import org.dspace.qaevent.service.dto.QAMessageDTO; + +/** + * Implementation of {@link QualityAssuranceAction} that add a simple metadata to the given + * item. + * + * @author Francesco Bacchelli (francesco.bacchelli at 4science.it) + * + */ +public class QANotifySimpleMetadataAction extends ASimpleMetadataAction { + + public String extractMetadataValue(QAMessageDTO message) { + return ((NotifyMessageDTO) message).getHref(); + } +} diff --git a/dspace-api/src/main/java/org/dspace/qaevent/action/QAOpenaireMetadataMapAction.java b/dspace-api/src/main/java/org/dspace/qaevent/action/QAOpenaireMetadataMapAction.java new file mode 100644 index 0000000000..427ad2bfde --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/qaevent/action/QAOpenaireMetadataMapAction.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.qaevent.action; + +import org.dspace.qaevent.service.dto.OpenaireMessageDTO; +import org.dspace.qaevent.service.dto.QAMessageDTO; + +/** + * Openaire Implementation {@link AMetadataMapAction} + * + * @author Francesco Bacchelli (francesco.bacchelli at 4science.it) + * + */ +public class QAOpenaireMetadataMapAction extends AMetadataMapAction { + + @Override + public String extractMetadataType(QAMessageDTO message) { + return ((OpenaireMessageDTO)message).getType(); + } + + @Override + public String extractMetadataValue(QAMessageDTO message) { + return ((OpenaireMessageDTO)message).getValue(); + } + +} diff --git a/dspace-api/src/main/java/org/dspace/qaevent/action/QAOpenaireSimpleMetadataAction.java b/dspace-api/src/main/java/org/dspace/qaevent/action/QAOpenaireSimpleMetadataAction.java new file mode 100644 index 0000000000..3baa95eced --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/qaevent/action/QAOpenaireSimpleMetadataAction.java @@ -0,0 +1,26 @@ +/** + * 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.qaevent.action; + +import org.dspace.qaevent.QualityAssuranceAction; +import org.dspace.qaevent.service.dto.OpenaireMessageDTO; +import org.dspace.qaevent.service.dto.QAMessageDTO; + +/** + * Implementation of {@link QualityAssuranceAction} that add a simple metadata to the given + * item. + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + * + */ +public class QAOpenaireSimpleMetadataAction extends ASimpleMetadataAction { + + public String extractMetadataValue(QAMessageDTO message) { + return ((OpenaireMessageDTO) message).getAbstracts(); + } +} diff --git a/dspace-api/src/main/java/org/dspace/qaevent/action/QAReinstateRequestAction.java b/dspace-api/src/main/java/org/dspace/qaevent/action/QAReinstateRequestAction.java new file mode 100644 index 0000000000..7fa08dc6ec --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/qaevent/action/QAReinstateRequestAction.java @@ -0,0 +1,44 @@ +/** + * 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.qaevent.action; + +import java.sql.SQLException; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.Item; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.dspace.qaevent.QualityAssuranceAction; +import org.dspace.qaevent.service.dto.QAMessageDTO; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * QAReinstateRequestAction is an implementation of the QualityAssuranceAction interface. + * It is responsible for applying a correction to reinstate a specified item. + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com) + */ +public class QAReinstateRequestAction implements QualityAssuranceAction { + + private static final Logger log = LogManager.getLogger(); + + @Autowired + private ItemService itemService; + + @Override + public void applyCorrection(Context context, Item item, Item relatedItem, QAMessageDTO message) { + try { + itemService.reinstate(context, item); + } catch (SQLException | AuthorizeException e) { + log.error(e.getMessage(), e); + } + } + +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/qaevent/action/QAWithdrawnRequestAction.java b/dspace-api/src/main/java/org/dspace/qaevent/action/QAWithdrawnRequestAction.java new file mode 100644 index 0000000000..a0463fdb18 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/qaevent/action/QAWithdrawnRequestAction.java @@ -0,0 +1,44 @@ +/** + * 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.qaevent.action; + +import java.sql.SQLException; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.Item; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.dspace.qaevent.QualityAssuranceAction; +import org.dspace.qaevent.service.dto.QAMessageDTO; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * QAWithdrawnRequestAction is an implementation of the QualityAssuranceAction interface. + * It is responsible for applying a correction to withdraw a specified item. + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com) + */ +public class QAWithdrawnRequestAction implements QualityAssuranceAction { + + private static final Logger log = LogManager.getLogger(); + + @Autowired + private ItemService itemService; + + @Override + public void applyCorrection(Context context, Item item, Item relatedItem, QAMessageDTO message) { + try { + itemService.withdraw(context, item); + } catch (SQLException | AuthorizeException e) { + log.error(e.getMessage(), e); + } + } + +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/qaevent/dao/QAEventsDAO.java b/dspace-api/src/main/java/org/dspace/qaevent/dao/QAEventsDAO.java new file mode 100644 index 0000000000..98c38ca3f5 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/qaevent/dao/QAEventsDAO.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.qaevent.dao; + +import java.sql.SQLException; +import java.util.List; + +import org.dspace.content.Item; +import org.dspace.content.QAEventProcessed; +import org.dspace.core.Context; +import org.dspace.core.GenericDAO; +import org.dspace.eperson.EPerson; + +/** + * DAO that handle processed QA Events. + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + * + */ +public interface QAEventsDAO extends GenericDAO { + + /** + * Returns all the stored QAEventProcessed entities. + * + * @param context the DSpace context + * @return the found entities + * @throws SQLException if an SQL error occurs + */ + public List findAll(Context context) throws SQLException; + + /** + * Returns the stored QAEventProcessed entities by item. + * + * @param context the DSpace context + * @param item the item to search for + * @return the found entities + * @throws SQLException if an SQL error occurs + */ + public List findByItem(Context context, Item item) throws SQLException; + + /** + * Returns the stored QAEventProcessed entities by eperson. + * + * @param context the DSpace context + * @param ePerson the ePerson to search for + * @return the found entities + * @throws SQLException if an SQL error occurs + */ + public List findByEPerson(Context context, EPerson ePerson) throws SQLException; + + /** + * Search a page of quality assurance broker events by notification ID. + * + * @param context the DSpace context + * @param eventId the event id + * @param start the start index + * @param size the size to be applied + * @return the processed events + * @throws SQLException if an SQL error occurs + */ + public List searchByEventId(Context context, String eventId, Integer start, Integer size) + throws SQLException; + + /** + * Check if an event with the given checksum is already stored. + * + * @param context the DSpace context + * @param checksum the checksum to search for + * @return true if the given checksum is related to an already + * stored event, false otherwise + * @throws SQLException if an SQL error occurs + */ + public boolean isEventStored(Context context, String checksum) throws SQLException; + + /** + * Store an event related to the given checksum. + * + * @param context the DSpace context + * @param checksum the checksum of the event to be store + * @param eperson the eperson who handle the event + * @param item the item related to the event + * @return true if the creation is completed with success, false + * otherwise + */ + boolean storeEvent(Context context, String checksum, EPerson eperson, Item item); + +} diff --git a/dspace-api/src/main/java/org/dspace/qaevent/dao/impl/QAEventsDAOImpl.java b/dspace-api/src/main/java/org/dspace/qaevent/dao/impl/QAEventsDAOImpl.java new file mode 100644 index 0000000000..2dbc476920 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/qaevent/dao/impl/QAEventsDAOImpl.java @@ -0,0 +1,90 @@ +/** + * 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.qaevent.dao.impl; + +import java.sql.SQLException; +import java.util.Date; +import java.util.List; + +import jakarta.persistence.Query; +import org.dspace.content.Item; +import org.dspace.content.QAEventProcessed; +import org.dspace.core.AbstractHibernateDAO; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.dspace.qaevent.dao.QAEventsDAO; + +/** + * Implementation of {@link QAEventsDAO} that store processed events using an + * SQL DBMS. + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + * + */ +public class QAEventsDAOImpl extends AbstractHibernateDAO implements QAEventsDAO { + + @Override + public List findAll(Context context) throws SQLException { + return findAll(context, QAEventProcessed.class); + } + + @Override + public boolean storeEvent(Context context, String checksum, EPerson eperson, Item item) { + QAEventProcessed qaEvent = new QAEventProcessed(); + qaEvent.setEperson(eperson); + qaEvent.setEventId(checksum); + qaEvent.setItem(item); + qaEvent.setEventTimestamp(new Date()); + try { + create(context, qaEvent); + return true; + } catch (SQLException e) { + return false; + } + } + + @Override + public boolean isEventStored(Context context, String checksum) throws SQLException { + Query query = createQuery(context, + "SELECT count(eventId) FROM QAEventProcessed qaevent WHERE qaevent.eventId = :event_id "); + query.setParameter("event_id", checksum); + return count(query) != 0; + } + + @Override + public List searchByEventId(Context context, String eventId, Integer start, Integer size) + throws SQLException { + Query query = createQuery(context, + "SELECT * FROM QAEventProcessed qaevent WHERE qaevent.qaevent_id = :event_id "); + query.setFirstResult(start); + query.setMaxResults(size); + query.setParameter("event_id", eventId); + return findMany(context, query); + } + + @Override + public List findByItem(Context context, Item item) throws SQLException { + Query query = createQuery(context, "" + + " SELECT qaevent " + + " FROM QAEventProcessed qaevent " + + " WHERE qaevent.item = :item "); + query.setParameter("item", item); + return findMany(context, query); + } + + @Override + public List findByEPerson(Context context, EPerson ePerson) throws SQLException { + Query query = createQuery(context, "" + + " SELECT qaevent " + + " FROM QAEventProcessed qaevent " + + " WHERE qaevent.eperson = :eperson "); + query.setParameter("eperson", ePerson); + return findMany(context, query); + } + +} diff --git a/dspace-api/src/main/java/org/dspace/qaevent/script/OpenaireEventsImport.java b/dspace-api/src/main/java/org/dspace/qaevent/script/OpenaireEventsImport.java new file mode 100644 index 0000000000..48f83d413d --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/qaevent/script/OpenaireEventsImport.java @@ -0,0 +1,355 @@ +/** + * 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.qaevent.script; + +import static org.apache.commons.lang3.StringUtils.isNotBlank; +import static org.apache.commons.lang3.StringUtils.substringAfter; +import static org.dspace.core.Constants.ITEM; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.sql.SQLException; +import java.util.List; +import java.util.UUID; + +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 eu.dnetlib.broker.BrokerClient; +import org.apache.commons.cli.ParseException; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.dspace.content.DSpaceObject; +import org.dspace.content.Item; +import org.dspace.content.QAEvent; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.factory.EPersonServiceFactory; +import org.dspace.handle.HandleServiceImpl; +import org.dspace.handle.service.HandleService; +import org.dspace.qaevent.service.OpenaireClientFactory; +import org.dspace.qaevent.service.QAEventService; +import org.dspace.scripts.DSpaceRunnable; +import org.dspace.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; +import org.dspace.utils.DSpace; + +/** + * Implementation of {@link DSpaceRunnable} to perform a QAEvents import from a + * json file. The JSON file contains an array of JSON Events, where each event + * has the following structure. The message attribute follows the structure + * documented at + * @see see + * + *
    + * {
    + * "originalId": "oai:www.openstarts.units.it:10077/21838",
    + * "title": "Egypt, crossroad of translations and literary interweavings",
    + * "topic": "ENRICH/MORE/PROJECT",
    + * "trust": 1.0,
    + * "message": {
    + * "projects[0].acronym": "PAThs",
    + * "projects[0].code": "687567",
    + * "projects[0].funder": "EC",
    + * "projects[0].fundingProgram": "H2020",
    + * "projects[0].jurisdiction": "EU",
    + * "projects[0].openaireId": "40|corda__h2020::6e32f5eb912688f2424c68b851483ea4",
    + * "projects[0].title": "Tracking Papyrus and Parchment Paths"
    + * }
    + * } + *
    + * + * @author Alessandro Martelli (alessandro.martelli at 4science.it) + * @author Luca Giamminonni (luca.giamminonni at 4Science.it) + * + */ +public class OpenaireEventsImport + extends DSpaceRunnable> { + + private HandleService handleService; + + private QAEventService qaEventService; + + private String[] topicsToImport; + + private ConfigurationService configurationService; + + private BrokerClient brokerClient; + + private ObjectMapper jsonMapper; + + private URL openaireBrokerURL; + + private String fileLocation; + + private String email; + + private Context context; + + @Override + @SuppressWarnings({ "rawtypes" }) + public OpenaireEventsImportScriptConfiguration getScriptConfiguration() { + OpenaireEventsImportScriptConfiguration configuration = new DSpace().getServiceManager() + .getServiceByName("import-openaire-events", OpenaireEventsImportScriptConfiguration.class); + return configuration; + } + + @Override + public void setup() throws ParseException { + + jsonMapper = new JsonMapper(); + jsonMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + + DSpace dspace = new DSpace(); + handleService = dspace.getSingletonService(HandleServiceImpl.class); + qaEventService = dspace.getSingletonService(QAEventService.class); + configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); + brokerClient = OpenaireClientFactory.getInstance().getBrokerClient(); + + topicsToImport = configurationService.getArrayProperty("qaevents.openaire.import.topic"); + openaireBrokerURL = getOpenaireBrokerUri(); + + fileLocation = commandLine.getOptionValue("f"); + email = commandLine.getOptionValue("e"); + + } + + @Override + public void internalRun() throws Exception { + + if (StringUtils.isAllBlank(fileLocation, email)) { + throw new IllegalArgumentException("One parameter between the location of the file and the email " + + "must be entered to proceed with the import."); + } + + if (StringUtils.isNoneBlank(fileLocation, email)) { + throw new IllegalArgumentException("Only one parameter between the location of the file and the email " + + "must be entered to proceed with the import."); + } + + context = new Context(); + assignCurrentUserInContext(); + + try { + importOpenaireEvents(); + } catch (Exception ex) { + handler.logError("A not recoverable error occurs during OPENAIRE events import: " + getMessage(ex), ex); + throw ex; + } + + } + + /** + * Read the OPENAIRE events from the given JSON file or directly from the + * OPENAIRE broker and try to store them. + */ + private void importOpenaireEvents() throws Exception { + + if (StringUtils.isNotBlank(fileLocation)) { + handler.logInfo("Trying to read the QA events from the provided file"); + importOpenaireEventsFromFile(); + } else { + handler.logInfo("Trying to read the QA events from the OPENAIRE broker"); + importOpenaireEventsFromBroker(); + } + + } + + /** + * Read the OPENAIRE events from the given file location and try to store them. + */ + private void importOpenaireEventsFromFile() throws Exception { + + InputStream eventsFileInputStream = getQAEventsFileInputStream(); + List qaEvents = readOpenaireQAEventsFromJson(eventsFileInputStream); + + handler.logInfo("Found " + qaEvents.size() + " events in the given file"); + + storeOpenaireQAEvents(qaEvents); + + } + + /** + * Import the OPENAIRE events from the Broker using the subscription related to + * the given email and try to store them. + */ + private void importOpenaireEventsFromBroker() { + + List subscriptionIds = listEmailSubscriptions(); + + handler.logInfo("Found " + subscriptionIds.size() + " subscriptions related to the given email"); + + for (String subscriptionId : subscriptionIds) { + + List events = readOpenaireQAEventsFromBroker(subscriptionId); + + handler.logInfo("Found " + events.size() + " events from the subscription " + subscriptionId); + + storeOpenaireQAEvents(events); + + } + } + + /** + * Obtain an InputStream from the runnable instance. + */ + private InputStream getQAEventsFileInputStream() throws Exception { + return handler.getFileStream(context, fileLocation) + .orElseThrow(() -> new IllegalArgumentException("Error reading file, the file couldn't be " + + "found for filename: " + fileLocation)); + } + + /** + * Read all the QAEvent from the OPENAIRE Broker related to the subscription + * with the given id. + */ + private List readOpenaireQAEventsFromBroker(String subscriptionId) { + + try { + InputStream eventsInputStream = getEventsBySubscriptions(subscriptionId); + return readOpenaireQAEventsFromJson(eventsInputStream); + } catch (Exception ex) { + handler.logError("An error occurs downloading the events related to the subscription " + + subscriptionId + ": " + getMessage(ex), ex); + } + + return List.of(); + + } + + /** + * Read all the QAEvent present in the given input stream. + * + * @return the QA events to be imported + */ + private List readOpenaireQAEventsFromJson(InputStream inputStream) throws Exception { + return jsonMapper.readValue(inputStream, new TypeReference>() { + }); + } + + /** + * Store the given QAEvents. + * + * @param events the event to be stored + */ + private void storeOpenaireQAEvents(List events) { + for (QAEvent event : events) { + try { + final String resourceUUID = getResourceUUID(context, event.getOriginalId()); + if (resourceUUID == null) { + throw new IllegalArgumentException("Skipped event " + event.getEventId() + + " related to the oai record " + event.getOriginalId() + " as the record was not found"); + } + event.setTarget(resourceUUID); + storeOpenaireQAEvent(event); + } catch (RuntimeException | SQLException e) { + handler.logWarning("An error occurs storing the event with id " + + event.getEventId() + ": " + getMessage(e)); + } + } + } + + private String getResourceUUID(Context context, String originalId) throws IllegalStateException, SQLException { + String id = getHandleFromOriginalId(originalId); + if (StringUtils.isNotBlank(id)) { + DSpaceObject dso = handleService.resolveToObject(context, id); + if (dso != null && dso.getType() == ITEM) { + Item item = (Item) dso; + final String itemUuid = item.getID().toString(); + context.uncacheEntity(item); + return itemUuid; + } else { + return null; + } + } else { + throw new IllegalArgumentException("Malformed originalId " + originalId); + } + } + + // oai:www.openstarts.units.it:10077/21486 + private String getHandleFromOriginalId(String originalId) { + int startPosition = originalId.lastIndexOf(':'); + if (startPosition != -1) { + return originalId.substring(startPosition + 1, originalId.length()); + } else { + return originalId; + } + } + + /** + * Store the given QAEvent, skipping it if it is not supported. + * + * @param event the event to be stored + */ + private void storeOpenaireQAEvent(QAEvent event) { + + if (!StringUtils.equalsAny(event.getTopic(), topicsToImport)) { + handler.logWarning("Event for topic " + event.getTopic() + " is not allowed in the qaevents.cfg"); + return; + } + + event.setSource(QAEvent.OPENAIRE_SOURCE); + + qaEventService.store(context, event); + + } + + /** + * Download the events related to the given subscription from the OPENAIRE broker. + * + * @param subscriptionId the subscription id + * @return an input stream from which to read the events in json format + */ + private InputStream getEventsBySubscriptions(String subscriptionId) throws Exception { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + brokerClient.downloadEvents(openaireBrokerURL, subscriptionId, outputStream); + return new ByteArrayInputStream(outputStream.toByteArray()); + } + + /** + * Takes all the subscription related to the given email from the OPENAIRE + * broker. + */ + private List listEmailSubscriptions() { + try { + return brokerClient.listSubscriptions(openaireBrokerURL, email); + } catch (Exception ex) { + throw new IllegalArgumentException("An error occurs retrieving the subscriptions " + + "from the OPENAIRE broker: " + getMessage(ex), ex); + } + } + + private URL getOpenaireBrokerUri() { + try { + return new URL(configurationService.getProperty("qaevents.openaire.broker-url", "http://api.openaire.eu/broker")); + } catch (MalformedURLException e) { + throw new IllegalStateException("The configured OPENAIRE broker URL is not valid.", e); + } + } + + /** + * Get the root exception message from the given exception. + */ + private String getMessage(Exception ex) { + String message = ExceptionUtils.getRootCauseMessage(ex); + // Remove the Exception name from the message + return isNotBlank(message) ? substringAfter(message, ":").trim() : ""; + } + + private void assignCurrentUserInContext() throws SQLException { + UUID uuid = getEpersonIdentifier(); + if (uuid != null) { + EPerson ePerson = EPersonServiceFactory.getInstance().getEPersonService().find(context, uuid); + context.setCurrentUser(ePerson); + } + } +} diff --git a/dspace-api/src/main/java/org/dspace/qaevent/script/OpenaireEventsImportCli.java b/dspace-api/src/main/java/org/dspace/qaevent/script/OpenaireEventsImportCli.java new file mode 100644 index 0000000000..d98b578cdd --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/qaevent/script/OpenaireEventsImportCli.java @@ -0,0 +1,42 @@ +/** + * 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.qaevent.script; + +import org.apache.commons.cli.HelpFormatter; +import org.apache.commons.cli.ParseException; +import org.dspace.utils.DSpace; + +/** + * Extensions of {@link OpenaireEventsImport} to run the script on console. + * + * @author Alessandro Martelli (alessandro.martelli at 4science.it) + * + */ +public class OpenaireEventsImportCli extends OpenaireEventsImport { + + @Override + @SuppressWarnings({ "rawtypes" }) + public OpenaireEventsImportCliScriptConfiguration getScriptConfiguration() { + return new DSpace().getServiceManager() + .getServiceByName("import-openaire-events", OpenaireEventsImportCliScriptConfiguration.class); + } + + @Override + public void setup() throws ParseException { + super.setup(); + + // in case of CLI we show the help prompt + if (commandLine.hasOption('h')) { + HelpFormatter formatter = new HelpFormatter(); + formatter.printHelp("Import Notification event json file", getScriptConfiguration().getOptions()); + System.exit(0); + } + + } + +} diff --git a/dspace-api/src/main/java/org/dspace/qaevent/script/OpenaireEventsImportCliScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/qaevent/script/OpenaireEventsImportCliScriptConfiguration.java new file mode 100644 index 0000000000..5be0453a17 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/qaevent/script/OpenaireEventsImportCliScriptConfiguration.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.qaevent.script; + +import org.apache.commons.cli.Options; + +/** + * Extension of {@link OpenaireEventsImportScriptConfiguration} to run the script on + * console. + * + * @author Alessandro Martelli (alessandro.martelli at 4science.it) + * + */ +public class OpenaireEventsImportCliScriptConfiguration + extends OpenaireEventsImportScriptConfiguration { + + @Override + public Options getOptions() { + Options options = super.getOptions(); + options.addOption("h", "help", false, "help"); + options.getOption("h").setType(boolean.class); + super.options = options; + return options; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/qaevent/script/OpenaireEventsImportScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/qaevent/script/OpenaireEventsImportScriptConfiguration.java new file mode 100644 index 0000000000..63dcfae740 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/qaevent/script/OpenaireEventsImportScriptConfiguration.java @@ -0,0 +1,72 @@ +/** + * 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.qaevent.script; + +import java.io.InputStream; + +import org.apache.commons.cli.Options; +import org.dspace.scripts.configuration.ScriptConfiguration; + +/** + * Extension of {@link ScriptConfiguration} to perform a QAEvents import from + * file. + * + * @author Alessandro Martelli (alessandro.martelli at 4science.it) + * + */ +public class OpenaireEventsImportScriptConfiguration extends ScriptConfiguration { + + /* + private AuthorizeService authorizeService; + */ + private Class dspaceRunnableClass; + + @Override + public Class getDspaceRunnableClass() { + return dspaceRunnableClass; + } + + /** + * Generic setter for the dspaceRunnableClass + * @param dspaceRunnableClass The dspaceRunnableClass to be set on this OpenaireEventsImportScriptConfiguration + */ + @Override + public void setDspaceRunnableClass(Class dspaceRunnableClass) { + this.dspaceRunnableClass = dspaceRunnableClass; + } +/* + @Override + public boolean isAllowedToExecute(Context context) { + try { + return authorizeService.isAdmin(context); + } catch (SQLException e) { + throw new RuntimeException("SQLException occurred when checking if the current user is an admin", e); + } + } +*/ + @Override + public Options getOptions() { + if (options == null) { + Options options = new Options(); + + options.addOption("f", "file", true, "Import data from Openaire quality assurance broker JSON file." + + " This parameter is mutually exclusive to the email parameter."); + options.getOption("f").setType(InputStream.class); + options.getOption("f").setRequired(false); + + options.addOption("e", "email", true, "Email related to the subscriptions to import data from Openaire " + + "broker. This parameter is mutually exclusive to the file parameter."); + options.getOption("e").setType(String.class); + options.getOption("e").setRequired(false); + + super.options = options; + } + return options; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/qaevent/security/AdministratorsOnlyQASecurity.java b/dspace-api/src/main/java/org/dspace/qaevent/security/AdministratorsOnlyQASecurity.java new file mode 100644 index 0000000000..38cf40ce39 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/qaevent/security/AdministratorsOnlyQASecurity.java @@ -0,0 +1,51 @@ +/** + * 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.qaevent.security; + +import java.sql.SQLException; +import java.util.Optional; + +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.content.QAEvent; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * QASecurity that restrict access to the QA Source and related events only to repository administrators + * + * @author Andrea Bollini (andrea.bollini at 4science.com) + */ +public class AdministratorsOnlyQASecurity implements QASecurity { + + @Autowired + private AuthorizeService authorizeService; + + public Optional generateFilterQuery(Context context, EPerson currentUser) { + return Optional.empty(); + } + + @Override + public boolean canSeeQASource(Context context, EPerson user) { + try { + return authorizeService.isAdmin(context, user); + } catch (SQLException e) { + return false; + } + } + + @Override + public boolean canSeeQAEvent(Context context, EPerson user, QAEvent qaEvent) { + try { + return authorizeService.isAdmin(context, user); + } catch (SQLException e) { + return false; + } + } + +} diff --git a/dspace-api/src/main/java/org/dspace/qaevent/security/QASecurity.java b/dspace-api/src/main/java/org/dspace/qaevent/security/QASecurity.java new file mode 100644 index 0000000000..44b00e7d94 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/qaevent/security/QASecurity.java @@ -0,0 +1,55 @@ +/** + * 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.qaevent.security; + +import java.util.Optional; + +import org.dspace.content.QAEvent; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; + +/** + * The QASecurity interface defines methods for implementing security strategies + * related to Quality Assurance (QA) events. Classes implementing this interface should + * provide logic to filter and determine visibility of QA events based on the user's permissions. + * + * @author Andrea Bollini (andrea.bollini at 4science.com) + * + */ +public interface QASecurity { + + /** + * Return a SOLR queries that can be applied querying the qaevent SOLR core to retrieve only the qaevents visible to + * the provided user + * + * @param context the DSpace context + * @param user the user to consider to restrict the visible qaevents + * @return the SOLR filter query to apply + */ + public Optional generateFilterQuery(Context context, EPerson user); + + /** + * Return true it the user is potentially allowed to see events in the qasource that adopt this + * security strategy + * + * @param context the DSpace context + * @param user the user to consider to restrict the visible qaevents + * @return true if the user can eventually see some qaevents + */ + public boolean canSeeQASource(Context context, EPerson user); + + /** + * Return true it the user is potentially allowed to see events in the qasource that adopt this + * security strategy + * + * @param context the DSpace context + * @param user the user to consider to restrict the visible qaevents + * @return true if the user can see the provided qaEvent + */ + public boolean canSeeQAEvent(Context context, EPerson user, QAEvent qaEvent); +} diff --git a/dspace-api/src/main/java/org/dspace/qaevent/security/UserBasedFilterQASecurity.java b/dspace-api/src/main/java/org/dspace/qaevent/security/UserBasedFilterQASecurity.java new file mode 100644 index 0000000000..3d66d221e6 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/qaevent/security/UserBasedFilterQASecurity.java @@ -0,0 +1,70 @@ +/** + * 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.qaevent.security; + +import java.sql.SQLException; +import java.text.MessageFormat; +import java.util.Optional; + +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.content.QAEvent; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.dspace.qaevent.service.QAEventService; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * QASecurity implementations that allow access to only qa events that match a SORL query generated using the eperson + * uuid + * + * @author Andrea Bollini (andrea.bollini at 4science.com) + */ +public class UserBasedFilterQASecurity implements QASecurity { + + private String filterTemplate; + private boolean allowAdmins = true; + + @Autowired + private QAEventService qaEventService; + @Autowired + private AuthorizeService authorizeService; + + public Optional generateFilterQuery(Context context, EPerson user) { + try { + if (allowAdmins && authorizeService.isAdmin(context, user)) { + return Optional.empty(); + } else { + return Optional.of(MessageFormat.format(filterTemplate, user.getID().toString())); + } + } catch (SQLException e) { + throw new RuntimeException("Error checking permissions", e); + } + } + + public boolean canSeeQASource(Context context, EPerson user) { + return user != null; + } + + public boolean canSeeQAEvent(Context context, EPerson user, QAEvent qaEvent) { + try { + return (allowAdmins && authorizeService.isAdmin(context, user)) + || qaEventService.qaEventsInSource(context, user, qaEvent.getEventId(), qaEvent.getSource()); + } catch (SQLException e) { + throw new RuntimeException("Error checking permissions", e); + } + } + + public void setFilterTemplate(String filterTemplate) { + this.filterTemplate = filterTemplate; + } + + public void setAllowAdmins(boolean allowAdmins) { + this.allowAdmins = allowAdmins; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/qaevent/service/OpenaireClientFactory.java b/dspace-api/src/main/java/org/dspace/qaevent/service/OpenaireClientFactory.java new file mode 100644 index 0000000000..e7a7be33c1 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/qaevent/service/OpenaireClientFactory.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.qaevent.service; + +import eu.dnetlib.broker.BrokerClient; +import org.dspace.utils.DSpace; + +/** + * Factory for the {@link BrokerClient}. + * + * @author Luca Giamminonni (luca.giamminonni at 4science.it) + * + */ +public interface OpenaireClientFactory { + + /** + * Returns an instance of the {@link BrokerClient}. + * + * @return the client instance + */ + public BrokerClient getBrokerClient(); + + public static OpenaireClientFactory getInstance() { + return new DSpace().getServiceManager().getServiceByName("openaireClientFactory", OpenaireClientFactory.class); + } +} diff --git a/dspace-api/src/main/java/org/dspace/qaevent/service/QAEventActionService.java b/dspace-api/src/main/java/org/dspace/qaevent/service/QAEventActionService.java new file mode 100644 index 0000000000..2e5690f622 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/qaevent/service/QAEventActionService.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.qaevent.service; + +import org.dspace.content.QAEvent; +import org.dspace.core.Context; + +/** + * Service that handle the actions that can be done related to an + * {@link QAEvent}. + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + * + */ +public interface QAEventActionService { + + /** + * Accept the given event. + * + * @param context the DSpace context + * @param qaevent the event to be accepted + */ + public void accept(Context context, QAEvent qaevent); + + /** + * Discard the given event. + * + * @param context the DSpace context + * @param qaevent the event to be discarded + */ + public void discard(Context context, QAEvent qaevent); + + /** + * Reject the given event. + * + * @param context the DSpace context + * @param qaevent the event to be rejected + */ + public void reject(Context context, QAEvent qaevent); +} diff --git a/dspace-api/src/main/java/org/dspace/qaevent/service/QAEventSecurityService.java b/dspace-api/src/main/java/org/dspace/qaevent/service/QAEventSecurityService.java new file mode 100644 index 0000000000..7f6ef7a12c --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/qaevent/service/QAEventSecurityService.java @@ -0,0 +1,55 @@ +/** + * 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.qaevent.service; + +import java.util.Optional; + +import org.dspace.content.QAEvent; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; + +/** + * Interface to limit the visibility of {@link QAEvent} to specific users. + * + * @author Andrea Bollini (andrea.bollini at 4science.com) + * + */ +public interface QAEventSecurityService { + + /** + * Check if the specified user can see a specific QASource + * @param context the context + * @param user the eperson to consider + * @param sourceName the source name + * @return true if the specified user can eventually see events in the QASource + */ + boolean canSeeSource(Context context, EPerson user, String sourceName); + + /** + * Check if the specified user can see a specific QAEvent. It is expected that a QAEvent in a not visible QASource + * cannot be accessed. So implementation of this method should enforce this rule. + * + * @param context the context + * @param user the eperson to consider + * @param qaEvent the qaevent to check + * @return true if the specified user can see the specified event + */ + boolean canSeeEvent(Context context, EPerson user, QAEvent qaEvent); + + /** + * Generate a query to restrict the qa events returned by other search/find method to the only ones visible to the + * specified user + * + * @param context the context + * @param user the eperson to consider + * @param sourceName the source name + * @return the solr filter query + */ + public Optional generateQAEventFilterQuery(Context context, EPerson user, String sourceName); + +} diff --git a/dspace-api/src/main/java/org/dspace/qaevent/service/QAEventService.java b/dspace-api/src/main/java/org/dspace/qaevent/service/QAEventService.java new file mode 100644 index 0000000000..3254aecf77 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/qaevent/service/QAEventService.java @@ -0,0 +1,267 @@ +/** + * 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.qaevent.service; + +import java.util.List; +import java.util.UUID; + +import org.dspace.content.QAEvent; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.dspace.qaevent.QASource; +import org.dspace.qaevent.QATopic; + +/** + * Service that handles {@link QAEvent}. + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + * + */ +public interface QAEventService { + + /** + * Find all the event's topics. + * + * @param context the DSpace context + * @param offset the offset to apply + * @param orderField the field to order for + * @param ascending true if the order should be ascending, false otherwise + * @return the topics list + */ + public List findAllTopics(Context context, long offset, long count, String orderField, boolean ascending); + + /** + * Find all the event's topics related to the given source. + * + * @param context the DSpace context + * @param source the source to search for + * @param offset the offset to apply + * @param count the page size + * @param orderField the field to order for + * @param ascending true if the order should be ascending, false otherwise + * @return the topics list + */ + public List findAllTopicsBySource(Context context, String source, long offset, long count, + String orderField, boolean ascending); + + /** + * Find a specific topic by its name, source and optionally a target. + * + * @param context the DSpace context + * @param sourceName the name of the source + * @param topicName the topic name to search for + * @param target (nullable) the uuid of the target to focus on + * @return the topic + */ + public QATopic findTopicBySourceAndNameAndTarget(Context context, String sourceName, String topicName, UUID target); + + /** + * Count all the event's topics. + * + * @return the count result + */ + public long countTopics(); + + /** + * Count all the event's topics related to the given source. + * + * @param context the DSpace context + * @param source the source to search for + * @return the count result + */ + public long countTopicsBySource(Context context, String source); + + /** + * Find all the events by topic. + * + * @param context the DSpace context + * @param sourceName the source name + * @param topic the topic to search for + * @param offset the offset to apply + * @param size the page size + * @param orderField the field to order for + * @param ascending true if the order should be ascending, false otherwise + * @return the events + */ + public List findEventsByTopic(Context context, String sourceName, String topic, long offset, int size, + String orderField, boolean ascending); + + /** + * Find all the events by topic. + * + * @param context the DSpace context + * @param sourceName the source name + * @param topic the topic to search for + * @return the events count + */ + public long countEventsByTopic(Context context, String sourceName, String topic); + + /** + * Find an event by the given id. Please note that no security filter are applied by this method. + * + * @param id the id of the event to search for + * @return the event + */ + public QAEvent findEventByEventId(String id); + + /** + * Store the given event. + * + * @param context the DSpace context + * @param event the event to store + */ + public void store(Context context, QAEvent event); + + /** + * Delete an event by the given id. + * + * @param id the id of the event to delete + */ + public void deleteEventByEventId(String id); + + /** + * Delete events by the given target id. + * + * @param targetId the id of the target id + */ + public void deleteEventsByTargetId(UUID targetId); + + /** + * Find a specific topid by the given id. + * + * @param topicId the topic id to search for + * @return the topic + */ + public QATopic findTopicByTopicId(String topicId); + + /** + * Find a specific source by the given name. + * + * @param context the DSpace context + * @param source the source name + * @return the source + */ + public QASource findSource(Context context, String source); + + /** + * Find a specific source by the given name including the stats focused on the target item. + * + * @param context the DSpace context + * @param source the source name + * @param target the uuid of the item target + * @return the source + */ + public QASource findSource(Context context, String source, UUID target); + + /** + * Find all the event's sources. + * + * @param context the DSpace context + * @param offset the offset to apply + * @param pageSize the page size + * @return the sources list + */ + public List findAllSources(Context context, long offset, int pageSize); + + /** + * Count all the event's sources. + * + * @param context the DSpace context + * @return the count result + */ + public long countSources(Context context); + + /** + * Count all the event's sources related to a specific item + * + * @param context the DSpace context + * @param target the item uuid + * @return the count result + */ + public long countSourcesByTarget(Context context, UUID target); + + /** + * Count all the event's topics related to the given source referring to a specific item + * + * @param context the DSpace context + * @param target the item uuid + * @param source the source to search for + * @return the count result + */ + public long countTopicsBySourceAndTarget(Context context, String source, UUID target); + + /** + * Check if the given QA event supports a related item. + * + * @param qaevent the event to be verified + * @return true if the event supports a related item, false otherwise. + */ + public boolean isRelatedItemSupported(QAEvent qaevent); + + /** + * Find a list of QA events according to the pagination parameters for the specified topic and target sorted by + * trust descending + * + * @param context the DSpace context + * @param source the source name + * @param topic the topic to search for + * @param offset the offset to apply + * @param pageSize the page size + * @param target the uuid of the QA event's target + * @return the events + */ + public List findEventsByTopicAndTarget(Context context, String source, String topic, UUID target, + long offset, int pageSize); + + /** + * Check if a qaevent with the provided id is visible to the current user according to the source security + * + * @param context the DSpace context + * @param user the user to consider for the security check + * @param eventId the id of the event to check for existence + * @param source the qa source name + * @return true if the event exists + */ + public boolean qaEventsInSource(Context context, EPerson user, String eventId, String source); + + /** + * Count the QA events related to the specified topic and target + * + * @param context the DSpace context + * @param source the source name + * @param topic the topic to search for + * @param target the uuid of the QA event's target + * @return the count result + */ + public long countEventsByTopicAndTarget(Context context, String source, String topic, UUID target); + + /** + * Find all the event's sources related to a specific item + * + * @param context the DSpace context + * @param target the item referring to + * @param offset the offset to apply + * @param pageSize the page size + * @return the source list + */ + public List findAllSourcesByTarget(Context context, UUID target, long offset, int pageSize); + + /** + * Find all the event's topics related to the given source for a specific item + * + * @param context the DSpace context + * @param source (not null) the source to search for + * @param target the item referring to + * @param offset the offset to apply + * @param pageSize the page size + * @return the topics list + */ + public List findAllTopicsBySourceAndTarget(Context context, String source, UUID target, long offset, + long pageSize, String orderField, boolean ascending); + +} diff --git a/dspace-api/src/main/java/org/dspace/qaevent/service/dto/CorrectionTypeMessageDTO.java b/dspace-api/src/main/java/org/dspace/qaevent/service/dto/CorrectionTypeMessageDTO.java new file mode 100644 index 0000000000..e5e38c2396 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/qaevent/service/dto/CorrectionTypeMessageDTO.java @@ -0,0 +1,39 @@ +/** + * 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.qaevent.service.dto; + +import java.io.Serializable; + +/** + * The CorrectionTypeMessageDTO class implements the QAMessageDTO interface + * and represents a Data Transfer Object (DTO) for holding information + * related to a correction type message in the context of Quality Assurance (QA). + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.it) + */ +public class CorrectionTypeMessageDTO implements QAMessageDTO, Serializable { + + private static final long serialVersionUID = 2718151302291303796L; + + private String reason; + + public CorrectionTypeMessageDTO() {} + + public CorrectionTypeMessageDTO(String reason) { + this.reason = reason; + } + + public String getReason() { + return reason; + } + + public void setReason(String reason) { + this.reason = reason; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/qaevent/service/dto/NotifyMessageDTO.java b/dspace-api/src/main/java/org/dspace/qaevent/service/dto/NotifyMessageDTO.java new file mode 100644 index 0000000000..2a5842589f --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/qaevent/service/dto/NotifyMessageDTO.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.qaevent.service.dto; + +/** + * Implementation of {@link QAMessageDTO} that model message coming from COAR NOTIFY. + * + * @author Francesco Bacchelli (francesco.bacchelli at 4science.it) + * + */ +public class NotifyMessageDTO implements QAMessageDTO { + + private String serviceName; + + private String serviceId; + + private String href; + + private String relationship; + + public String getServiceName() { + return serviceName; + } + + public void setServiceName(String serviceName) { + this.serviceName = serviceName; + } + + public String getServiceId() { + return serviceId; + } + + public void setServiceId(String serviceId) { + this.serviceId = serviceId; + } + + public String getHref() { + return href; + } + + public void setHref(String href) { + this.href = href; + } + + public String getRelationship() { + return relationship; + } + + public void setRelationship(String relationship) { + this.relationship = relationship; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/qaevent/service/dto/OpenaireMessageDTO.java b/dspace-api/src/main/java/org/dspace/qaevent/service/dto/OpenaireMessageDTO.java new file mode 100644 index 0000000000..821f11f869 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/qaevent/service/dto/OpenaireMessageDTO.java @@ -0,0 +1,175 @@ +/** + * 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.qaevent.service.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Implementation of {@link QAMessageDTO} that model message coming from OPENAIRE. + * @see see + * @author Luca Giamminonni (luca.giamminonni at 4science.it) + * + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class OpenaireMessageDTO implements QAMessageDTO { + + @JsonProperty("pids[0].value") + private String value; + + @JsonProperty("pids[0].type") + private String type; + + @JsonProperty("instances[0].hostedby") + private String instanceHostedBy; + + @JsonProperty("instances[0].instancetype") + private String instanceInstanceType; + + @JsonProperty("instances[0].license") + private String instanceLicense; + + @JsonProperty("instances[0].url") + private String instanceUrl; + + @JsonProperty("abstracts[0]") + private String abstracts; + + @JsonProperty("projects[0].acronym") + private String acronym; + + @JsonProperty("projects[0].code") + private String code; + + @JsonProperty("projects[0].funder") + private String funder; + + @JsonProperty("projects[0].fundingProgram") + private String fundingProgram; + + @JsonProperty("projects[0].jurisdiction") + private String jurisdiction; + + @JsonProperty("projects[0].openaireId") + private String openaireId; + + @JsonProperty("projects[0].title") + private String title; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getInstanceHostedBy() { + return instanceHostedBy; + } + + public void setInstanceHostedBy(String instanceHostedBy) { + this.instanceHostedBy = instanceHostedBy; + } + + public String getInstanceInstanceType() { + return instanceInstanceType; + } + + public void setInstanceInstanceType(String instanceInstanceType) { + this.instanceInstanceType = instanceInstanceType; + } + + public String getInstanceLicense() { + return instanceLicense; + } + + public void setInstanceLicense(String instanceLicense) { + this.instanceLicense = instanceLicense; + } + + public String getInstanceUrl() { + return instanceUrl; + } + + public void setInstanceUrl(String instanceUrl) { + this.instanceUrl = instanceUrl; + } + + public String getAbstracts() { + return abstracts; + } + + public void setAbstracts(String abstracts) { + this.abstracts = abstracts; + } + + public String getAcronym() { + return acronym; + } + + public void setAcronym(String acronym) { + this.acronym = acronym; + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public String getFunder() { + return funder; + } + + public void setFunder(String funder) { + this.funder = funder; + } + + public String getFundingProgram() { + return fundingProgram; + } + + public void setFundingProgram(String fundingProgram) { + this.fundingProgram = fundingProgram; + } + + public String getJurisdiction() { + return jurisdiction; + } + + public void setJurisdiction(String jurisdiction) { + this.jurisdiction = jurisdiction; + } + + public String getOpenaireId() { + return openaireId; + } + + public void setOpenaireId(String openaireId) { + this.openaireId = openaireId; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } +} diff --git a/dspace-api/src/main/java/org/dspace/qaevent/service/dto/QAMessageDTO.java b/dspace-api/src/main/java/org/dspace/qaevent/service/dto/QAMessageDTO.java new file mode 100644 index 0000000000..ede32ef497 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/qaevent/service/dto/QAMessageDTO.java @@ -0,0 +1,20 @@ +/** + * 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.qaevent.service.dto; + +import org.dspace.content.QAEvent; + +/** + * Interface for classes that contains the details related to a {@link QAEvent}. + * + * @author Luca Giamminonni (luca.giamminonni at 4science.it) + * + */ +public interface QAMessageDTO { + +} diff --git a/dspace-api/src/main/java/org/dspace/qaevent/service/impl/OpenaireClientFactoryImpl.java b/dspace-api/src/main/java/org/dspace/qaevent/service/impl/OpenaireClientFactoryImpl.java new file mode 100644 index 0000000000..5839f5e877 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/qaevent/service/impl/OpenaireClientFactoryImpl.java @@ -0,0 +1,35 @@ +/** + * 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.qaevent.service.impl; + +import eu.dnetlib.broker.BrokerClient; +import org.dspace.qaevent.service.OpenaireClientFactory; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Implementation of {@link OpenaireClientFactory} that returns the instance of + * {@link BrokerClient} managed by the Spring context. + * + * @author Luca Giamminonni (luca.giamminonni at 4science.it) + * + */ +public class OpenaireClientFactoryImpl implements OpenaireClientFactory { + + @Autowired + private BrokerClient brokerClient; + + @Override + public BrokerClient getBrokerClient() { + return brokerClient; + } + + public void setBrokerClient(BrokerClient brokerClient) { + this.brokerClient = brokerClient; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventActionServiceImpl.java b/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventActionServiceImpl.java new file mode 100644 index 0000000000..30875a5105 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventActionServiceImpl.java @@ -0,0 +1,138 @@ +/** + * 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.qaevent.service.impl; + +import java.io.IOException; +import java.sql.SQLException; +import java.util.Map; +import java.util.UUID; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.apache.commons.lang3.StringUtils; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.dspace.content.Item; +import org.dspace.content.QAEvent; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.dspace.qaevent.QualityAssuranceAction; +import org.dspace.qaevent.service.QAEventActionService; +import org.dspace.qaevent.service.QAEventService; +import org.dspace.services.ConfigurationService; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Implementation of {@link QAEventActionService}. + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + * + */ +public class QAEventActionServiceImpl implements QAEventActionService { + + private static final Logger log = LogManager.getLogger(QAEventActionServiceImpl.class); + + private ObjectMapper jsonMapper; + + @Autowired + private QAEventService qaEventService; + + @Autowired + private ItemService itemService; + + @Autowired + private ConfigurationService configurationService; + + private Map topicsToActions; + + public void setTopicsToActions(Map topicsToActions) { + this.topicsToActions = topicsToActions; + } + + public Map getTopicsToActions() { + return topicsToActions; + } + + public QAEventActionServiceImpl() { + jsonMapper = new JsonMapper(); + jsonMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + } + + @Override + public void accept(Context context, QAEvent qaevent) { + Item item = null; + Item related = null; + try { + item = itemService.find(context, UUID.fromString(qaevent.getTarget())); + if (qaevent.getRelated() != null) { + related = itemService.find(context, UUID.fromString(qaevent.getRelated())); + } + if (topicsToActions.get(qaevent.getTopic()) == null) { + String msg = "Unable to manage QA Event typed " + qaevent.getTopic() + + ". Managed types are: " + topicsToActions; + log.error(msg); + throw new RuntimeException(msg); + } + context.turnOffAuthorisationSystem(); + topicsToActions.get(qaevent.getTopic()).applyCorrection(context, item, related, + jsonMapper.readValue(qaevent.getMessage(), qaevent.getMessageDtoClass())); + qaEventService.deleteEventByEventId(qaevent.getEventId()); + makeAcknowledgement(qaevent.getEventId(), qaevent.getSource(), QAEvent.ACCEPTED); + } catch (SQLException | JsonProcessingException e) { + throw new RuntimeException(e); + } finally { + context.restoreAuthSystemState(); + } + } + + @Override + public void discard(Context context, QAEvent qaevent) { + qaEventService.deleteEventByEventId(qaevent.getEventId()); + makeAcknowledgement(qaevent.getEventId(), qaevent.getSource(), QAEvent.DISCARDED); + } + + @Override + public void reject(Context context, QAEvent qaevent) { + qaEventService.deleteEventByEventId(qaevent.getEventId()); + makeAcknowledgement(qaevent.getEventId(), qaevent.getSource(), QAEvent.REJECTED); + } + + /** + * Make acknowledgement to the configured urls for the event status. + */ + private void makeAcknowledgement(String eventId, String source, String status) { + String[] ackwnoledgeCallbacks = configurationService + .getArrayProperty("qaevents." + source + ".acknowledge-url"); + if (ackwnoledgeCallbacks != null) { + for (String ackwnoledgeCallback : ackwnoledgeCallbacks) { + if (StringUtils.isNotBlank(ackwnoledgeCallback)) { + ObjectNode node = jsonMapper.createObjectNode(); + node.put("eventId", eventId); + node.put("status", status); + StringEntity requestEntity = new StringEntity(node.toString(), ContentType.APPLICATION_JSON); + CloseableHttpClient httpclient = HttpClients.createDefault(); + HttpPost postMethod = new HttpPost(ackwnoledgeCallback); + postMethod.setEntity(requestEntity); + try { + httpclient.execute(postMethod); + } catch (IOException e) { + log.error(e.getMessage(), e); + } + } + } + } + } +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventSecurityServiceImpl.java b/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventSecurityServiceImpl.java new file mode 100644 index 0000000000..854626b3ba --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventSecurityServiceImpl.java @@ -0,0 +1,72 @@ +/** + * 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.qaevent.service.impl; + +import java.util.Map; +import java.util.Optional; + +import org.apache.logging.log4j.Logger; +import org.dspace.content.QAEvent; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.dspace.qaevent.security.QASecurity; +import org.dspace.qaevent.service.QAEventSecurityService; + +/** + * Implementation of the security service for QAEvents. + * This implementation manages a configuration of {@link QASecurity} instances, + * each responsible for security checks for a specific QA source. + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com) + */ +public class QAEventSecurityServiceImpl implements QAEventSecurityService { + + /** + * The default security settings to be used when specific configurations are not available for a QA source. + */ + private QASecurity defaultSecurity; + + /** + * A mapping of QA source names to their corresponding QASecurity configurations. + */ + private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(QAEventSecurityServiceImpl.class); + + private Map qaSecurityConfiguration; + + public void setQaSecurityConfiguration(Map qaSecurityConfiguration) { + this.qaSecurityConfiguration = qaSecurityConfiguration; + } + + public void setDefaultSecurity(QASecurity defaultSecurity) { + this.defaultSecurity = defaultSecurity; + } + + @Override + public Optional generateQAEventFilterQuery(Context context, EPerson user, String qaSource) { + QASecurity qaSecurity = getQASecurity(qaSource); + return qaSecurity.generateFilterQuery(context, user); + } + + private QASecurity getQASecurity(String qaSource) { + return qaSecurityConfiguration.getOrDefault(qaSource, defaultSecurity); + } + + @Override + public boolean canSeeEvent(Context context, EPerson user, QAEvent qaEvent) { + String source = qaEvent.getSource(); + QASecurity qaSecurity = getQASecurity(source); + return qaSecurity.canSeeQASource(context, user) && qaSecurity.canSeeQAEvent(context, user, qaEvent); + } + + @Override + public boolean canSeeSource(Context context, EPerson user, String qaSource) { + QASecurity qaSecurity = getQASecurity(qaSource); + return qaSecurity.canSeeQASource(context, user); + } + +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java b/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java new file mode 100644 index 0000000000..98077a1c0c --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java @@ -0,0 +1,773 @@ +/** + * 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.qaevent.service.impl; + +import static java.util.Comparator.comparing; +import static org.apache.commons.lang3.StringUtils.endsWith; +import static org.dspace.content.QAEvent.OPENAIRE_SOURCE; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import org.apache.commons.lang3.ArrayUtils; +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.ORDER; +import org.apache.solr.client.solrj.SolrServerException; +import org.apache.solr.client.solrj.impl.HttpSolrClient; +import org.apache.solr.client.solrj.request.UpdateRequest; +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.QAEvent; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.dspace.core.Email; +import org.dspace.core.I18nUtil; +import org.dspace.eperson.EPerson; +import org.dspace.handle.service.HandleService; +import org.dspace.qaevent.AutomaticProcessingAction; +import org.dspace.qaevent.QAEventAutomaticProcessingEvaluation; +import org.dspace.qaevent.QASource; +import org.dspace.qaevent.QATopic; +import org.dspace.qaevent.dao.QAEventsDAO; +import org.dspace.qaevent.dao.impl.QAEventsDAOImpl; +import org.dspace.qaevent.service.QAEventActionService; +import org.dspace.qaevent.service.QAEventSecurityService; +import org.dspace.qaevent.service.QAEventService; +import org.dspace.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; + + +/** + * Implementation of {@link QAEventService} that use Solr to store events. When + * the user performs an action on the event (such as accepting the suggestion or + * rejecting it) then the event is removed from solr and saved in the database + * (see {@link QAEventsDAO}) so that it is no longer proposed. + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + * + */ +public class QAEventServiceImpl implements QAEventService { + + private static final Logger log = LogManager.getLogger(); + + public static final String QAEVENTS_SOURCES = "qaevents.sources"; + + @Autowired(required = true) + protected ConfigurationService configurationService; + + @Autowired(required = true) + protected QAEventSecurityService qaSecurityService; + + @Autowired(required = true) + protected ItemService itemService; + + @Autowired + private HandleService handleService; + + @Autowired + private QAEventsDAOImpl qaEventsDao; + + @Autowired(required = false) + @Qualifier("qaAutomaticProcessingMap") + private Map qaAutomaticProcessingMap; + + @Autowired + private QAEventActionService qaEventActionService; + + private ObjectMapper jsonMapper; + + public QAEventServiceImpl() { + jsonMapper = new JsonMapper(); + jsonMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + } + + /** + * Non-Static CommonsHttpSolrServer for processing indexing events. + */ + protected SolrClient solr = null; + + public static final String SOURCE = "source"; + public static final String ORIGINAL_ID = "original_id"; + public static final String TITLE = "title"; + public static final String TOPIC = "topic"; + public static final String TRUST = "trust"; + public static final String MESSAGE = "message"; + public static final String EVENT_ID = "event_id"; + public static final String RESOURCE_UUID = "resource_uuid"; + public static final String LAST_UPDATE = "last_update"; + public static final String RELATED_UUID = "related_uuid"; + + protected SolrClient getSolr() { + if (solr == null) { + String solrService = DSpaceServicesFactory.getInstance().getConfigurationService() + .getProperty("qaevents.solr.server", "http://localhost:8983/solr/qaevent"); + return new HttpSolrClient.Builder(solrService).build(); + } + return solr; + } + + @Override + public long countTopics() { + SolrQuery solrQuery = new SolrQuery(); + solrQuery.setRows(0); + solrQuery.setQuery("*:*"); + solrQuery.setFacet(true); + solrQuery.setFacetMinCount(1); + solrQuery.addFacetField(TOPIC); + QueryResponse response; + try { + response = getSolr().query(solrQuery); + } catch (SolrServerException | IOException e) { + throw new RuntimeException(e); + } + return response.getFacetField(TOPIC).getValueCount(); + } + + @Override + public long countTopicsBySource(Context context, String sourceName) { + var currentUser = context.getCurrentUser(); + if (isNotSupportedSource(sourceName) || !qaSecurityService.canSeeSource(context, currentUser, sourceName)) { + return 0; + } + SolrQuery solrQuery = new SolrQuery(); + solrQuery.setRows(0); + Optional securityQuery = qaSecurityService.generateQAEventFilterQuery(context, currentUser, sourceName); + solrQuery.setQuery(securityQuery.orElse("*:*")); + solrQuery.setFacet(true); + solrQuery.setFacetMinCount(1); + solrQuery.addFacetField(TOPIC); + solrQuery.addFilterQuery(SOURCE + ":\"" + sourceName + "\""); + QueryResponse response; + try { + response = getSolr().query(solrQuery); + } catch (SolrServerException | IOException e) { + throw new RuntimeException(e); + } + return response.getFacetField(TOPIC).getValueCount(); + } + + @Override + public QATopic findTopicBySourceAndNameAndTarget(Context context, String sourceName, String topicName, + UUID target) { + if (isNotSupportedSource(sourceName) + || !qaSecurityService.canSeeSource(context, context.getCurrentUser(), sourceName)) { + return null; + } + SolrQuery solrQuery = new SolrQuery(); + solrQuery.setRows(0); + Optional securityQuery = qaSecurityService.generateQAEventFilterQuery(context, + context.getCurrentUser(), sourceName); + solrQuery.setQuery(securityQuery.orElse("*:*")); + + solrQuery.addFilterQuery(SOURCE + ":\"" + sourceName + "\""); + solrQuery.addFilterQuery(TOPIC + ":\"" + topicName + "\""); + if (target != null) { + solrQuery.addFilterQuery(RESOURCE_UUID + ":\"" + target.toString() + "\""); + } + solrQuery.setFacet(true); + solrQuery.setFacetMinCount(1); + solrQuery.addFacetField(TOPIC); + QueryResponse response; + try { + response = getSolr().query(solrQuery); + FacetField facetField = response.getFacetField(TOPIC); + for (Count c : facetField.getValues()) { + if (c.getName().equals(topicName)) { + QATopic topic = new QATopic(); + topic.setSource(sourceName); + topic.setKey(c.getName()); + topic.setTotalEvents(c.getCount()); + topic.setLastEvent(new Date()); + return topic; + } + } + } catch (SolrServerException | IOException e) { + throw new RuntimeException(e); + } + return null; + } + + @Override + public void deleteEventByEventId(String id) { + try { + getSolr().deleteById(id); + getSolr().commit(); + } catch (SolrServerException | IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public void deleteEventsByTargetId(UUID targetId) { + try { + getSolr().deleteByQuery(RESOURCE_UUID + ":" + targetId.toString()); + getSolr().commit(); + } catch (SolrServerException | IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public QATopic findTopicByTopicId(String topicId) { + SolrQuery solrQuery = new SolrQuery(); + solrQuery.setRows(0); + solrQuery.setQuery(TOPIC + ":" + topicId.replaceAll("!", "/")); + solrQuery.setFacet(true); + solrQuery.setFacetMinCount(1); + solrQuery.addFacetField(TOPIC); + QueryResponse response; + try { + response = getSolr().query(solrQuery); + FacetField facetField = response.getFacetField(TOPIC); + for (Count c : facetField.getValues()) { + if (c.getName().equals(topicId.replace("!", "/"))) { + QATopic topic = new QATopic(); + topic.setKey(c.getName()); + topic.setTotalEvents(c.getCount()); + topic.setLastEvent(new Date()); + return topic; + } + } + } catch (SolrServerException | IOException e) { + throw new RuntimeException(e); + } + return null; + } + + @Override + public List findAllTopics(Context context, long offset, long count, String orderField, boolean ascending) { + return findAllTopicsBySource(context, null, offset, count, orderField, ascending); + } + + @Override + public List findAllTopicsBySource(Context context, String source, long offset, + long count, String orderField, boolean ascending) { + return findAllTopicsBySourceAndTarget(context, source, null, offset, count, orderField, ascending); + } + + @Override + public List findAllTopicsBySourceAndTarget(Context context, String source, UUID target, long offset, + long pageSize, String orderField, boolean ascending) { + if (isNotSupportedSource(source) + || !qaSecurityService.canSeeSource(context, context.getCurrentUser(), source)) { + return List.of(); + } + SolrQuery solrQuery = new SolrQuery(); + solrQuery.setRows(0); + if (orderField != null) { + solrQuery.setSort(orderField, ascending ? ORDER.asc : ORDER.desc); + solrQuery.setFacetSort(FacetParams.FACET_SORT_INDEX); + } + Optional securityQuery = qaSecurityService.generateQAEventFilterQuery(context, + context.getCurrentUser(), source); + solrQuery.setQuery(securityQuery.orElse("*:*")); + solrQuery.setFacet(true); + solrQuery.setFacetMinCount(1); + solrQuery.setFacetLimit((int) (offset + pageSize)); + solrQuery.addFacetField(TOPIC); + solrQuery.addFilterQuery(SOURCE + ":\"" + source + "\""); + if (target != null) { + solrQuery.addFilterQuery(RESOURCE_UUID + ":" + target.toString()); + } + QueryResponse response; + List topics = new ArrayList<>(); + try { + response = getSolr().query(solrQuery); + FacetField facetField = response.getFacetField(TOPIC); + int idx = 0; + for (Count c : facetField.getValues()) { + if (idx < offset) { + idx++; + continue; + } + QATopic topic = new QATopic(); + topic.setSource(source); + topic.setKey(c.getName()); + topic.setFocus(target); + topic.setTotalEvents(c.getCount()); + topic.setLastEvent(new Date()); + topics.add(topic); + idx++; + } + } catch (SolrServerException | IOException e) { + throw new RuntimeException(e); + } + return topics; + } + + @Override + public void store(Context context, QAEvent dto) { + + if (isNotSupportedSource(dto.getSource())) { + throw new IllegalArgumentException("The source of the given event is not supported: " + dto.getSource()); + } + + if (StringUtils.isBlank(dto.getTopic())) { + throw new IllegalArgumentException("A topic is mandatory for an event"); + } + + String checksum = dto.getEventId(); + try { + if (!qaEventsDao.isEventStored(context, checksum)) { + + SolrInputDocument doc = createSolrDocument(context, dto, checksum); + + UpdateRequest updateRequest = new UpdateRequest(); + + updateRequest.add(doc); + updateRequest.process(getSolr()); + + getSolr().commit(); + + performAutomaticProcessingIfNeeded(context, dto); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private void performAutomaticProcessingIfNeeded(Context context, QAEvent qaEvent) { + if (qaAutomaticProcessingMap == null) { + return; + } + QAEventAutomaticProcessingEvaluation evaluation = qaAutomaticProcessingMap.get(qaEvent.getSource()); + + if (evaluation == null) { + return; + } + + AutomaticProcessingAction action = evaluation.evaluateAutomaticProcessing(context, qaEvent); + + if (action == null) { + return; + } + + switch (action) { + case REJECT: + qaEventActionService.reject(context, qaEvent); + break; + case IGNORE: + qaEventActionService.discard(context, qaEvent); + break; + case ACCEPT: + qaEventActionService.accept(context, qaEvent); + break; + default: + throw new IllegalStateException("Unknown automatic action requested " + action); + } + + } + + /** + * Sends an email notification to the system administrator about a new + * Quality Assurance (QA) request event. The email includes details such as the + * topic, target, and message associated with the QA event. + * + * @param qaEvent The Quality Assurance event for which the notification is generated. + */ + public void sentEmailToAdminAboutNewRequest(QAEvent qaEvent) { + try { + String uiUrl = configurationService.getProperty("dspace.ui.url"); + Email email = Email.getEmail(I18nUtil.getEmailFilename(Locale.getDefault(), "qaevent_admin_notification")); + email.addRecipient(configurationService.getProperty("qaevents.mail.notification")); + email.addArgument(qaEvent.getTopic()); + email.addArgument(uiUrl + "/items/" + qaEvent.getTarget()); + email.addArgument(parsJson(qaEvent.getMessage())); + email.send(); + } catch (Exception e) { + log.warn("Error during sending email of Withdrawn/Reinstate request for item with uuid: {}", + qaEvent.getTarget(), e); + } + } + + private String parsJson(String jsonString) { + try { + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode jsonNode = objectMapper.readTree(jsonString); + return jsonNode.get("reason").asText(); + } catch (Exception e) { + log.warn("Unable to parse the JSON: {}", jsonString); + return jsonString; + } + } + + @Override + public QAEvent findEventByEventId(String eventId) { + SolrQuery solrQuery = new SolrQuery("*:*"); + solrQuery.addFilterQuery(EVENT_ID + ":\"" + eventId + "\""); + try { + QueryResponse response = getSolr().query(solrQuery); + if (response != null) { + SolrDocumentList list = response.getResults(); + if (list != null && list.size() == 1) { + SolrDocument doc = list.get(0); + return getQAEventFromSOLR(doc); + } + } + } catch (SolrServerException | IOException e) { + throw new RuntimeException("Exception querying Solr", e); + } + return null; + } + + @Override + public List findEventsByTopic(Context context, String sourceName, String topic, long offset, int pageSize, + String orderField, boolean ascending) { + EPerson currentUser = context.getCurrentUser(); + if (isNotSupportedSource(sourceName) || !qaSecurityService.canSeeSource(context, currentUser, sourceName)) { + return List.of(); + } + + SolrQuery solrQuery = new SolrQuery(); + solrQuery.setStart(((Long) offset).intValue()); + if (pageSize != -1) { + solrQuery.setRows(pageSize); + } + solrQuery.setSort(orderField, ascending ? ORDER.asc : ORDER.desc); + Optional securityQuery = qaSecurityService.generateQAEventFilterQuery(context, currentUser, sourceName); + solrQuery.setQuery(securityQuery.orElse("*:*")); + + solrQuery.setQuery(TOPIC + ":" + topic.replaceAll("!", "/")); + solrQuery.addFilterQuery(SOURCE + ":\"" + sourceName + "\""); + + try { + QueryResponse response = getSolr().query(solrQuery); + if (response != null) { + SolrDocumentList solrDocuments = response.getResults(); + List responseItem = new ArrayList<>(); + for (SolrDocument doc : solrDocuments) { + QAEvent item = getQAEventFromSOLR(doc); + responseItem.add(item); + } + return responseItem; + } + } catch (SolrServerException | IOException e) { + throw new RuntimeException(e.getMessage(), e); + } + return List.of(); + } + + @Override + public long countEventsByTopic(Context context, String sourceName, String topic) { + EPerson currentUser = context.getCurrentUser(); + if (isNotSupportedSource(sourceName) || !qaSecurityService.canSeeSource(context, currentUser, sourceName)) { + return 0; + } + + Optional securityQuery = qaSecurityService.generateQAEventFilterQuery(context, currentUser, sourceName); + + SolrQuery solrQuery = new SolrQuery(); + solrQuery.setRows(0); + solrQuery.setQuery(securityQuery.orElse("*:*")); + solrQuery.addFilterQuery(SOURCE + ":\"" + sourceName + "\""); + solrQuery.setQuery(TOPIC + ":" + topic.replaceAll("!", "/")); + try { + return getSolr().query(solrQuery).getResults().getNumFound(); + } catch (SolrServerException | IOException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + + @Override + public QASource findSource(Context context, String sourceName) { + String[] split = sourceName.split(":"); + return findSource(context, split[0], split.length == 2 ? UUID.fromString(split[1]) : null); + } + + @Override + public QASource findSource(Context context, String sourceName, UUID target) { + EPerson currentUser = context.getCurrentUser(); + if (isNotSupportedSource(sourceName) || !qaSecurityService.canSeeSource(context, currentUser, sourceName)) { + return null; + } + + Optional securityQuery = qaSecurityService.generateQAEventFilterQuery(context, currentUser, sourceName); + + SolrQuery solrQuery = new SolrQuery(); + solrQuery.setQuery(securityQuery.orElse("*:*")); + solrQuery.setRows(0); + solrQuery.addFilterQuery(SOURCE + ":\"" + sourceName + "\""); + if (target != null) { + solrQuery.addFilterQuery("resource_uuid:" + target.toString()); + } + solrQuery.setFacet(true); + solrQuery.setFacetMinCount(1); + solrQuery.addFacetField(SOURCE); + + try { + QueryResponse response = getSolr().query(solrQuery); + FacetField facetField = response.getFacetField(SOURCE); + for (Count c : facetField.getValues()) { + if (c.getName().equalsIgnoreCase(sourceName)) { + QASource source = new QASource(); + source.setName(c.getName()); + source.setFocus(target); + source.setTotalEvents(c.getCount()); + source.setLastEvent(new Date()); + return source; + } + } + } catch (SolrServerException | IOException e) { + throw new RuntimeException(e.getMessage(), e); + } + + QASource source = new QASource(); + source.setName(sourceName); + source.setTotalEvents(0L); + + return source; + } + + @Override + public List findAllSources(Context context, long offset, int pageSize) { + return Arrays.stream(getSupportedSources()) + .map((sourceName) -> findSource(context, sourceName)) + .filter(Objects::nonNull) + .sorted(comparing(QASource::getTotalEvents) + .reversed()) + .skip(offset) + .limit(pageSize) + .collect(Collectors.toList()); + } + + @Override + public long countSources(Context context) { + return Arrays.stream(getSupportedSources()) + .map((sourceName) -> findSource(context, sourceName)) + .filter(Objects::nonNull) + .filter(source -> source.getTotalEvents() > 0) + .count(); + } + + @Override + public long countSourcesByTarget(Context context, UUID target) { + return Arrays.stream(getSupportedSources()) + .map((sourceName) -> findSource(context, sourceName, target)) + .filter(Objects::nonNull) + .filter(source -> source.getTotalEvents() > 0) + .count(); + } + + @Override + public boolean isRelatedItemSupported(QAEvent qaevent) { + // Currently only PROJECT topics related to OPENAIRE supports related items + return qaevent.getSource().equals(OPENAIRE_SOURCE) && endsWith(qaevent.getTopic(), "/PROJECT"); + } + + private SolrInputDocument createSolrDocument(Context context, QAEvent dto, String checksum) throws Exception { + SolrInputDocument doc = new SolrInputDocument(); + doc.addField(SOURCE, dto.getSource()); + doc.addField(EVENT_ID, checksum); + doc.addField(ORIGINAL_ID, dto.getOriginalId()); + doc.addField(TITLE, dto.getTitle()); + doc.addField(TOPIC, dto.getTopic()); + doc.addField(TRUST, dto.getTrust()); + doc.addField(MESSAGE, dto.getMessage()); + doc.addField(LAST_UPDATE, new Date()); + String resourceUUID = getResourceUUID(context, dto.getOriginalId()); + if (resourceUUID == null) { + resourceUUID = dto.getTarget(); + /*throw new IllegalArgumentException("Skipped event " + checksum + + " related to the oai record " + dto.getOriginalId() + " as the record was not found");*/ + } + doc.addField(RESOURCE_UUID, resourceUUID); + doc.addField(RELATED_UUID, dto.getRelated()); + return doc; + } + + private String getResourceUUID(Context context, String originalId) throws Exception { + String id = getHandleFromOriginalId(originalId); + if (id != null) { + Item item = (Item) handleService.resolveToObject(context, id); + if (item != null) { + final String itemUuid = item.getID().toString(); + context.uncacheEntity(item); + return itemUuid; + } else { + return null; + } + } else { + throw new IllegalArgumentException("Malformed originalId " + originalId); + } + } + + // oai:www.openstarts.units.it:10077/21486 + private String getHandleFromOriginalId(String originalId) { + int startPosition = originalId.lastIndexOf(':'); + if (startPosition != -1) { + return originalId.substring(startPosition + 1, originalId.length()); + } else { + return originalId; + } + } + + private QAEvent getQAEventFromSOLR(SolrDocument doc) { + QAEvent item = new QAEvent(); + item.setSource((String) doc.get(SOURCE)); + item.setEventId((String) doc.get(EVENT_ID)); + item.setLastUpdate((Date) doc.get(LAST_UPDATE)); + item.setMessage((String) doc.get(MESSAGE)); + item.setOriginalId((String) doc.get(ORIGINAL_ID)); + item.setTarget((String) doc.get(RESOURCE_UUID)); + item.setTitle((String) doc.get(TITLE)); + item.setTopic((String) doc.get(TOPIC)); + item.setTrust((double) doc.get(TRUST)); + item.setRelated((String) doc.get(RELATED_UUID)); + return item; + } + + @Override + public boolean qaEventsInSource(Context context, EPerson user, String eventId, String source) { + SolrQuery solrQuery = new SolrQuery(); + Optional securityQuery = qaSecurityService.generateQAEventFilterQuery(context, user, source); + solrQuery.setQuery(securityQuery.orElse("*:*")); + solrQuery.addFilterQuery(EVENT_ID + ":\"" + eventId + "\""); + QueryResponse response; + try { + response = getSolr().query(solrQuery); + if (response != null) { + return response.getResults().getNumFound() == 1; + } + } catch (SolrServerException | IOException e) { + throw new RuntimeException("Exception querying Solr", e); + } + return false; + } + + @Override + public long countEventsByTopicAndTarget(Context context, String sourceName, String topic, UUID target) { + var currentUser = context.getCurrentUser(); + if (isNotSupportedSource(sourceName) || !qaSecurityService.canSeeSource(context, currentUser, sourceName)) { + return 0; + } + SolrQuery solrQuery = new SolrQuery(); + solrQuery.setRows(0); + Optional securityQuery = qaSecurityService.generateQAEventFilterQuery(context, currentUser, sourceName); + solrQuery.setQuery(securityQuery.orElse("*:*")); + if (target != null) { + solrQuery.addFilterQuery(RESOURCE_UUID + ":\"" + target.toString() + "\""); + } + solrQuery.addFilterQuery(SOURCE + ":\"" + sourceName + "\""); + solrQuery.addFilterQuery(TOPIC + ":\"" + topic + "\""); + QueryResponse response = null; + try { + response = getSolr().query(solrQuery); + return response.getResults().getNumFound(); + } catch (SolrServerException | IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public List findEventsByTopicAndTarget(Context context, String source, String topic, UUID target, + long offset, int pageSize) { + var currentUser = context.getCurrentUser(); + if (isNotSupportedSource(source) || !qaSecurityService.canSeeSource(context, currentUser, source)) { + return List.of(); + } + SolrQuery solrQuery = new SolrQuery(); + solrQuery.setStart(((Long) offset).intValue()); + solrQuery.setRows(pageSize); + solrQuery.setSort(TRUST, ORDER.desc); + Optional securityQuery = qaSecurityService.generateQAEventFilterQuery(context, currentUser, source); + solrQuery.setQuery(securityQuery.orElse("*:*")); + if (target != null) { + solrQuery.addFilterQuery(RESOURCE_UUID + ":\"" + target.toString() + "\""); + } + solrQuery.addFilterQuery(SOURCE + ":\"" + source + "\""); + solrQuery.addFilterQuery(TOPIC + ":\"" + topic + "\""); + + try { + QueryResponse response = getSolr().query(solrQuery); + if (response != null) { + SolrDocumentList list = response.getResults(); + List responseItem = new ArrayList<>(); + for (SolrDocument doc : list) { + QAEvent item = getQAEventFromSOLR(doc); + responseItem.add(item); + } + return responseItem; + } + } catch (SolrServerException | IOException e) { + throw new RuntimeException(e); + } + return List.of(); + } + + private boolean isNotSupportedSource(String source) { + return !ArrayUtils.contains(getSupportedSources(), source); + } + + private String[] getSupportedSources() { + return configurationService.getArrayProperty(QAEVENTS_SOURCES, + new String[] { QAEvent.OPENAIRE_SOURCE, QAEvent.COAR_NOTIFY_SOURCE }); + } + + @Override + public long countTopicsBySourceAndTarget(Context context, String source, UUID target) { + var currentUser = context.getCurrentUser(); + if (isNotSupportedSource(source) || !qaSecurityService.canSeeSource(context, currentUser, source)) { + return 0; + } + SolrQuery solrQuery = new SolrQuery(); + solrQuery.setRows(0); + Optional securityQuery = qaSecurityService.generateQAEventFilterQuery(context, currentUser, source); + solrQuery.setQuery(securityQuery.orElse("*:*")); + solrQuery.setFacet(true); + solrQuery.setFacetMinCount(1); + solrQuery.addFacetField(TOPIC); + solrQuery.addFilterQuery(SOURCE + ":\"" + source + "\""); + if (target != null) { + solrQuery.addFilterQuery(RESOURCE_UUID + ":\"" + target.toString() + "\""); + } + try { + QueryResponse response = getSolr().query(solrQuery); + return response.getFacetField(TOPIC).getValueCount(); + } catch (SolrServerException | IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public List findAllSourcesByTarget(Context context, UUID target, long offset, int pageSize) { + return Arrays.stream(getSupportedSources()) + .map((sourceName) -> findSource(context, sourceName, target)) + .filter(Objects::nonNull) + .sorted(comparing(QASource::getTotalEvents).reversed()) + .filter(source -> source.getTotalEvents() > 0) + .skip(offset) + .limit(pageSize) + .collect(Collectors.toList()); + } + +} diff --git a/dspace-api/src/main/java/org/dspace/rdf/RDFConsumer.java b/dspace-api/src/main/java/org/dspace/rdf/RDFConsumer.java index 34ab572d1b..8b43ad69d7 100644 --- a/dspace-api/src/main/java/org/dspace/rdf/RDFConsumer.java +++ b/dspace-api/src/main/java/org/dspace/rdf/RDFConsumer.java @@ -16,7 +16,7 @@ import java.util.List; import java.util.NoSuchElementException; import java.util.UUID; -import com.hp.hpl.jena.rdf.model.Model; +import org.apache.jena.rdf.model.Model; import org.apache.logging.log4j.Logger; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Bitstream; @@ -241,7 +241,7 @@ public class RDFConsumer implements Consumer { } DSOIdentifier id = new DSOIdentifier(dso, ctx); - // If an item gets withdrawn, a MODIFIY event is fired. We have to + // If an item gets withdrawn, a MODIFY event is fired. We have to // delete the item from the triple store instead of converting it. // we don't have to take care for reinstantions of items as they can // be processed as normal modify events. diff --git a/dspace-api/src/main/java/org/dspace/rdf/RDFUtil.java b/dspace-api/src/main/java/org/dspace/rdf/RDFUtil.java index 1e9744aec5..7dea426228 100644 --- a/dspace-api/src/main/java/org/dspace/rdf/RDFUtil.java +++ b/dspace-api/src/main/java/org/dspace/rdf/RDFUtil.java @@ -12,8 +12,8 @@ import java.sql.SQLException; import java.util.List; import java.util.UUID; -import com.hp.hpl.jena.rdf.model.Model; import org.apache.commons.lang3.StringUtils; +import org.apache.jena.rdf.model.Model; import org.apache.logging.log4j.Logger; import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.factory.AuthorizeServiceFactory; @@ -90,7 +90,7 @@ public class RDFUtil { /** * Loads converted data of a DSpaceObject identified by the URI provided - * as {@code identifier}. This method uses the RDFStorage configurated in + * as {@code identifier}. This method uses the RDFStorage configured in * the DSpace configuration. Close the model * ({@link com.hp.hpl.jena.rdf.model.Model#close() Model.close()}) as soon * as possible to free system resources. @@ -200,7 +200,7 @@ public class RDFUtil { if (!found) { log.warn("Configuration of DSpaceObjects of type " + Constants.typeText[dso.getType()] - + " prohibitted by configuration."); + + " prohibited by configuration."); return null; } } diff --git a/dspace-api/src/main/java/org/dspace/rdf/RDFizer.java b/dspace-api/src/main/java/org/dspace/rdf/RDFizer.java index ac4e341c5e..7f0358dfbe 100644 --- a/dspace-api/src/main/java/org/dspace/rdf/RDFizer.java +++ b/dspace-api/src/main/java/org/dspace/rdf/RDFizer.java @@ -16,7 +16,6 @@ import java.util.Set; import java.util.UUID; import java.util.concurrent.CopyOnWriteArraySet; -import com.hp.hpl.jena.rdf.model.Model; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.DefaultParser; @@ -25,6 +24,7 @@ import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import org.apache.commons.lang3.StringUtils; +import org.apache.jena.rdf.model.Model; import org.apache.logging.log4j.Logger; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Collection; @@ -205,7 +205,7 @@ public class RDFizer { } if (dso.getType() == Constants.SITE) { - // we don't need to iterate over all objects, use a shorctut: + // we don't need to iterate over all objects, use a shortcut: this.deleteAll(); } Callback callback = new Callback() { @@ -352,11 +352,11 @@ public class RDFizer { } markProcessed(dso); // this is useful to debug depth first search, but it is really noisy. - //log.debug("Procesing " + contentServiceFactory.getDSpaceObjectService(dso).getTypeText(dso) + " " + dso + //log.debug("Processing " + contentServiceFactory.getDSpaceObjectService(dso).getTypeText(dso) + " " + dso // .getID() + ":" + dso.getHandle() + "."); // if this method is used for conversion we should check if we have the - // permissions to read a DSO before converting all of it decendents + // permissions to read a DSO before converting all of it descendants // (e.g. check read permission on a community before converting all of // its subcommunties and collections). // just skip items with missing permissions and report them. @@ -700,11 +700,11 @@ public class RDFizer { options.addOption("o", "stdout", false, "Print all converted data to " + "stdout using turtle as serialization."); options.addOption("n", "dry-run", false, "Don't send any data or commands " + - "to the triplestore. Usefull for debugging or in conjunction " + + "to the triplestore. Useful for debugging or in conjunction " + "with --stdout."); options.addOption("c", "convert-all", false, "Convert all DSpace Objects" + " that are readable for an anonymous user. This may take a long time" + - "depending on the number of stored communties, collections and " + + "depending on the number of stored communities, collections and " + "items. Existing information in the triple store will be updated."); Option optIdentifiers = Option.builder("i") diff --git a/dspace-api/src/main/java/org/dspace/rdf/conversion/ConverterPlugin.java b/dspace-api/src/main/java/org/dspace/rdf/conversion/ConverterPlugin.java index f0ad88c582..fa00374f9d 100644 --- a/dspace-api/src/main/java/org/dspace/rdf/conversion/ConverterPlugin.java +++ b/dspace-api/src/main/java/org/dspace/rdf/conversion/ConverterPlugin.java @@ -10,7 +10,7 @@ package org.dspace.rdf.conversion; import java.sql.SQLException; -import com.hp.hpl.jena.rdf.model.Model; +import org.apache.jena.rdf.model.Model; import org.dspace.authorize.AuthorizeException; import org.dspace.content.DSpaceObject; import org.dspace.core.Context; diff --git a/dspace-api/src/main/java/org/dspace/rdf/conversion/DMRM.java b/dspace-api/src/main/java/org/dspace/rdf/conversion/DMRM.java index 0d0f955e0a..ca95838061 100644 --- a/dspace-api/src/main/java/org/dspace/rdf/conversion/DMRM.java +++ b/dspace-api/src/main/java/org/dspace/rdf/conversion/DMRM.java @@ -8,10 +8,10 @@ package org.dspace.rdf.conversion; -import com.hp.hpl.jena.rdf.model.Model; -import com.hp.hpl.jena.rdf.model.ModelFactory; -import com.hp.hpl.jena.rdf.model.Property; -import com.hp.hpl.jena.rdf.model.Resource; +import org.apache.jena.rdf.model.Model; +import org.apache.jena.rdf.model.ModelFactory; +import org.apache.jena.rdf.model.Property; +import org.apache.jena.rdf.model.Resource; /** * Schema for DSpace Metadata RDF Mappings. @@ -88,7 +88,7 @@ public class DMRM { public static final Resource DSpaceValue = m_model.createResource(NS + "DSpaceValue"); /** - *

    Specifies the RDF to generate for a specified matadata.

    + *

    Specifies the RDF to generate for a specified metadata.

    */ public static final Property creates = m_model.createProperty(NS + "creates"); diff --git a/dspace-api/src/main/java/org/dspace/rdf/conversion/MetadataConverterPlugin.java b/dspace-api/src/main/java/org/dspace/rdf/conversion/MetadataConverterPlugin.java index 72ba03d99d..c9e55cba19 100644 --- a/dspace-api/src/main/java/org/dspace/rdf/conversion/MetadataConverterPlugin.java +++ b/dspace-api/src/main/java/org/dspace/rdf/conversion/MetadataConverterPlugin.java @@ -15,17 +15,17 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; -import com.hp.hpl.jena.rdf.model.InfModel; -import com.hp.hpl.jena.rdf.model.Model; -import com.hp.hpl.jena.rdf.model.ModelFactory; -import com.hp.hpl.jena.rdf.model.ResIterator; -import com.hp.hpl.jena.reasoner.Reasoner; -import com.hp.hpl.jena.reasoner.ReasonerRegistry; -import com.hp.hpl.jena.reasoner.ValidityReport; -import com.hp.hpl.jena.util.FileManager; -import com.hp.hpl.jena.util.FileUtils; -import com.hp.hpl.jena.vocabulary.RDF; import org.apache.commons.lang3.StringUtils; +import org.apache.jena.rdf.model.InfModel; +import org.apache.jena.rdf.model.Model; +import org.apache.jena.rdf.model.ModelFactory; +import org.apache.jena.rdf.model.ResIterator; +import org.apache.jena.reasoner.Reasoner; +import org.apache.jena.reasoner.ReasonerRegistry; +import org.apache.jena.reasoner.ValidityReport; +import org.apache.jena.util.FileManager; +import org.apache.jena.util.FileUtils; +import org.apache.jena.vocabulary.RDF; import org.apache.logging.log4j.Logger; import org.dspace.app.util.factory.UtilServiceFactory; import org.dspace.authorize.AuthorizeException; diff --git a/dspace-api/src/main/java/org/dspace/rdf/conversion/MetadataRDFMapping.java b/dspace-api/src/main/java/org/dspace/rdf/conversion/MetadataRDFMapping.java index 6286f3b87a..4117f3549b 100644 --- a/dspace-api/src/main/java/org/dspace/rdf/conversion/MetadataRDFMapping.java +++ b/dspace-api/src/main/java/org/dspace/rdf/conversion/MetadataRDFMapping.java @@ -14,15 +14,15 @@ import java.util.List; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; -import com.hp.hpl.jena.rdf.model.Literal; -import com.hp.hpl.jena.rdf.model.Model; -import com.hp.hpl.jena.rdf.model.Property; -import com.hp.hpl.jena.rdf.model.RDFNode; -import com.hp.hpl.jena.rdf.model.Resource; -import com.hp.hpl.jena.rdf.model.Statement; -import com.hp.hpl.jena.rdf.model.StmtIterator; -import com.hp.hpl.jena.vocabulary.RDF; import org.apache.commons.lang3.StringUtils; +import org.apache.jena.rdf.model.Literal; +import org.apache.jena.rdf.model.Model; +import org.apache.jena.rdf.model.Property; +import org.apache.jena.rdf.model.RDFNode; +import org.apache.jena.rdf.model.Resource; +import org.apache.jena.rdf.model.Statement; +import org.apache.jena.rdf.model.StmtIterator; +import org.apache.jena.vocabulary.RDF; import org.apache.logging.log4j.Logger; /** @@ -159,7 +159,7 @@ public class MetadataRDFMapping { } public void convert(String value, String lang, String dsoIRI, Model m) { - log.debug("Using convertion for field " + name + " on value: " + value + log.debug("Using conversion for field " + name + " on value: " + value + " for " + dsoIRI + "."); // run over all results for (Iterator iter = this.results.iterator(); iter.hasNext(); ) { @@ -282,7 +282,7 @@ public class MetadataRDFMapping { String uri = predicate.getURI(); if (uri == null) { log.debug("A result predicate is blank node, but not a " - + "ResourceGenerator. Ingoring this result."); + + "ResourceGenerator. Ignoring this result."); return null; } return m.createProperty(uri); @@ -447,7 +447,7 @@ public class MetadataRDFMapping { if (!modifierNode.isResource()) { log.error("The modifier of a result is a Literal not an Resource! " - + "Ingoring this result."); + + "Ignoring this result."); return null; } Resource modifier = modifierNode.asResource(); diff --git a/dspace-api/src/main/java/org/dspace/rdf/conversion/RDFConverter.java b/dspace-api/src/main/java/org/dspace/rdf/conversion/RDFConverter.java index d8e71856a1..7617355db6 100644 --- a/dspace-api/src/main/java/org/dspace/rdf/conversion/RDFConverter.java +++ b/dspace-api/src/main/java/org/dspace/rdf/conversion/RDFConverter.java @@ -10,7 +10,7 @@ package org.dspace.rdf.conversion; import java.sql.SQLException; -import com.hp.hpl.jena.rdf.model.Model; +import org.apache.jena.rdf.model.Model; import org.dspace.authorize.AuthorizeException; import org.dspace.content.DSpaceObject; import org.dspace.core.Context; diff --git a/dspace-api/src/main/java/org/dspace/rdf/conversion/RDFConverterImpl.java b/dspace-api/src/main/java/org/dspace/rdf/conversion/RDFConverterImpl.java index 93a9b6211d..0842bf5e40 100644 --- a/dspace-api/src/main/java/org/dspace/rdf/conversion/RDFConverterImpl.java +++ b/dspace-api/src/main/java/org/dspace/rdf/conversion/RDFConverterImpl.java @@ -11,8 +11,8 @@ package org.dspace.rdf.conversion; import java.sql.SQLException; import java.util.List; -import com.hp.hpl.jena.rdf.model.Model; -import com.hp.hpl.jena.rdf.model.ModelFactory; +import org.apache.jena.rdf.model.Model; +import org.apache.jena.rdf.model.ModelFactory; import org.apache.logging.log4j.Logger; import org.dspace.authorize.AuthorizeException; import org.dspace.content.DSpaceObject; diff --git a/dspace-api/src/main/java/org/dspace/rdf/conversion/SimpleDSORelationsConverterPlugin.java b/dspace-api/src/main/java/org/dspace/rdf/conversion/SimpleDSORelationsConverterPlugin.java index 63382a7c26..4665625702 100644 --- a/dspace-api/src/main/java/org/dspace/rdf/conversion/SimpleDSORelationsConverterPlugin.java +++ b/dspace-api/src/main/java/org/dspace/rdf/conversion/SimpleDSORelationsConverterPlugin.java @@ -14,11 +14,11 @@ import java.sql.SQLException; import java.util.Iterator; import java.util.List; -import com.hp.hpl.jena.rdf.model.Model; -import com.hp.hpl.jena.rdf.model.ModelFactory; -import com.hp.hpl.jena.util.FileManager; -import com.hp.hpl.jena.util.FileUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.jena.rdf.model.Model; +import org.apache.jena.rdf.model.ModelFactory; +import org.apache.jena.util.FileManager; +import org.apache.jena.util.FileUtils; import org.apache.logging.log4j.Logger; import org.dspace.app.util.Util; import org.dspace.content.Bitstream; diff --git a/dspace-api/src/main/java/org/dspace/rdf/conversion/StaticDSOConverterPlugin.java b/dspace-api/src/main/java/org/dspace/rdf/conversion/StaticDSOConverterPlugin.java index f86af753e6..f18074a1bd 100644 --- a/dspace-api/src/main/java/org/dspace/rdf/conversion/StaticDSOConverterPlugin.java +++ b/dspace-api/src/main/java/org/dspace/rdf/conversion/StaticDSOConverterPlugin.java @@ -12,10 +12,10 @@ import java.io.IOException; import java.io.InputStream; import java.sql.SQLException; -import com.hp.hpl.jena.rdf.model.Model; -import com.hp.hpl.jena.rdf.model.ModelFactory; -import com.hp.hpl.jena.util.FileManager; -import com.hp.hpl.jena.util.FileUtils; +import org.apache.jena.rdf.model.Model; +import org.apache.jena.rdf.model.ModelFactory; +import org.apache.jena.util.FileManager; +import org.apache.jena.util.FileUtils; import org.apache.logging.log4j.Logger; import org.dspace.content.DSpaceObject; import org.dspace.content.factory.ContentServiceFactory; diff --git a/dspace-api/src/main/java/org/dspace/rdf/negotiation/MediaRange.java b/dspace-api/src/main/java/org/dspace/rdf/negotiation/MediaRange.java index 6b2caa598d..f652838121 100644 --- a/dspace-api/src/main/java/org/dspace/rdf/negotiation/MediaRange.java +++ b/dspace-api/src/main/java/org/dspace/rdf/negotiation/MediaRange.java @@ -28,11 +28,11 @@ public class MediaRange { // SEPARATOR: ( ) < > @ , ; : \ " / [ ] ? = { } // the separators can be used in as class inside square brackets. To be able - // to negate the class, the spearators necessary square brackets are not + // to negate the class, the separators necessary square brackets are not // included in the string. public static final String separators = "()<>@,;:\\\\\"/\\[\\]?={} \\t"; - // TOKEN: ANY US ASCII except ctl an separtor + // TOKEN: ANY US ASCII except ctl an separator public static final String token = "[\\040-\\0176" + "&&[^" + separators + "]]+"; // "\" followed by any US ASCII character (octets 0 - 177) @@ -57,8 +57,8 @@ public class MediaRange { // group 5 contains the value of the last parameter before the quality parameter if any // group 6 contains the quality value if any // group 7 contains all parameters after the quality parameter if any - // group 8 contains the name of the last parameter after the quality paremeter if any - // group 9 contains the value of the laster parameter after the quality paremeter if any + // group 8 contains the name of the last parameter after the quality parameter if any + // group 9 contains the value of the laster parameter after the quality parameter if any public static final String mediaRangeRegex = "(?:(" + token + ")/(" + token + "?)" + "(" + nonQualityParam + "*)" + qualityParam + "?(" + nonQualityParam + "*))"; @@ -101,7 +101,7 @@ public class MediaRange { throw new IllegalArgumentException("A media range's type cannot " + "be wildcarded if its subtype isn't as well."); } - // initalize with defualt value, parse later + // initialize with default value, parse later double qvalue = DEFAULT_QVALUE; // initialize empty lists, parse parameters later List parameterNames = new ArrayList<>(); @@ -142,7 +142,7 @@ public class MediaRange { + unparsedParameters + "') of a previously parsed media " + "range!"); throw new IllegalStateException("Run into problems while parsing " - + "a substring of a previuosly succesfully parsed string."); + + "a substring of a previuosly successfully parsed string."); } while (m.find()) { if (!StringUtils.isEmpty(m.group(1))) { diff --git a/dspace-api/src/main/java/org/dspace/rdf/negotiation/NegotiationFilter.java b/dspace-api/src/main/java/org/dspace/rdf/negotiation/NegotiationFilter.java index 998f57ca4f..d210803ba7 100644 --- a/dspace-api/src/main/java/org/dspace/rdf/negotiation/NegotiationFilter.java +++ b/dspace-api/src/main/java/org/dspace/rdf/negotiation/NegotiationFilter.java @@ -10,15 +10,15 @@ package org.dspace.rdf.negotiation; import java.io.IOException; import java.util.regex.Matcher; import java.util.regex.Pattern; -import javax.servlet.Filter; -import javax.servlet.FilterChain; -import javax.servlet.FilterConfig; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.FilterConfig; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.apache.logging.log4j.Logger; import org.dspace.rdf.RDFUtil; import org.dspace.services.factory.DSpaceServicesFactory; diff --git a/dspace-api/src/main/java/org/dspace/rdf/negotiation/Negotiator.java b/dspace-api/src/main/java/org/dspace/rdf/negotiation/Negotiator.java index d011d305b1..07891b6d6d 100644 --- a/dspace-api/src/main/java/org/dspace/rdf/negotiation/Negotiator.java +++ b/dspace-api/src/main/java/org/dspace/rdf/negotiation/Negotiator.java @@ -12,8 +12,8 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletResponse; import org.apache.commons.lang3.StringUtils; import org.apache.commons.validator.routines.UrlValidator; import org.apache.logging.log4j.Logger; @@ -25,7 +25,7 @@ import org.dspace.services.factory.DSpaceServicesFactory; */ public class Negotiator { - // Serialiazation codes + // Serialization codes public static final int UNSPECIFIED = -1; public static final int WILDCARD = 0; public static final int HTML = 1; diff --git a/dspace-api/src/main/java/org/dspace/rdf/storage/RDFStorage.java b/dspace-api/src/main/java/org/dspace/rdf/storage/RDFStorage.java index 31294323c7..52bee26d76 100644 --- a/dspace-api/src/main/java/org/dspace/rdf/storage/RDFStorage.java +++ b/dspace-api/src/main/java/org/dspace/rdf/storage/RDFStorage.java @@ -10,7 +10,7 @@ package org.dspace.rdf.storage; import java.util.List; -import com.hp.hpl.jena.rdf.model.Model; +import org.apache.jena.rdf.model.Model; /** * @author Pascal-Nicolas Becker (dspace -at- pascal -hyphen- becker -dot- de) diff --git a/dspace-api/src/main/java/org/dspace/rdf/storage/RDFStorageImpl.java b/dspace-api/src/main/java/org/dspace/rdf/storage/RDFStorageImpl.java index fd84db5d5f..2ea5c3c880 100644 --- a/dspace-api/src/main/java/org/dspace/rdf/storage/RDFStorageImpl.java +++ b/dspace-api/src/main/java/org/dspace/rdf/storage/RDFStorageImpl.java @@ -12,24 +12,15 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import com.hp.hpl.jena.graph.Graph; -import com.hp.hpl.jena.graph.Node; -import com.hp.hpl.jena.graph.NodeFactory; -import com.hp.hpl.jena.query.Dataset; -import com.hp.hpl.jena.query.DatasetFactory; -import com.hp.hpl.jena.query.QueryExecution; -import com.hp.hpl.jena.query.QueryExecutionFactory; -import com.hp.hpl.jena.query.QuerySolution; -import com.hp.hpl.jena.query.ResultSet; -import com.hp.hpl.jena.rdf.model.Model; -import com.hp.hpl.jena.sparql.core.DatasetGraph; -import com.hp.hpl.jena.update.GraphStore; -import com.hp.hpl.jena.update.GraphStoreFactory; import org.apache.commons.lang3.StringUtils; -import org.apache.jena.atlas.web.auth.HttpAuthenticator; -import org.apache.jena.atlas.web.auth.SimpleAuthenticator; -import org.apache.jena.web.DatasetGraphAccessor; -import org.apache.jena.web.DatasetGraphAccessorHTTP; +import org.apache.jena.http.auth.AuthEnv; +import org.apache.jena.query.QueryExecution; +import org.apache.jena.query.QuerySolution; +import org.apache.jena.query.ResultSet; +import org.apache.jena.rdf.model.Model; +import org.apache.jena.rdfconnection.RDFConnection; +import org.apache.jena.rdfconnection.RDFConnectionRemote; +import org.apache.jena.sparql.exec.http.QueryExecutionHTTP; import org.apache.logging.log4j.Logger; import org.dspace.rdf.RDFUtil; import org.dspace.services.ConfigurationService; @@ -47,48 +38,35 @@ public class RDFStorageImpl @Override public void store(String uri, Model model) { - Node graphNode = NodeFactory.createURI(uri); - DatasetGraphAccessor accessor = this.getAccessor(); - Dataset ds = DatasetFactory.create(model); - DatasetGraph dsg = ds.asDatasetGraph(); - Graph g = dsg.getDefaultGraph(); - accessor.httpPut(graphNode, g); + RDFConnection connection = this.getConnection(); + connection.put(uri, model); } @Override public Model load(String uri) { - Node graphNode = NodeFactory.createURI(uri); - DatasetGraphAccessor accessor = this.getAccessor(); - Graph g = accessor.httpGet(graphNode); - if (g == null || g.isEmpty()) { - return null; - } - GraphStore gs = GraphStoreFactory.create(g); - Dataset ds = gs.toDataset(); - Model m = ds.getDefaultModel(); - return m; + RDFConnection connection = this.getConnection(); + return connection.fetch(uri); } - protected DatasetGraphAccessor getAccessor() { - DatasetGraphAccessor accessor; + protected RDFConnection getConnection() { + RDFConnection connection; if (configurationService.hasProperty(RDFUtil.STORAGE_GRAPHSTORE_LOGIN_KEY) && configurationService.hasProperty(RDFUtil.STORAGE_GRAPHSTORE_PASSWORD_KEY)) { - HttpAuthenticator httpAuthenticator = new SimpleAuthenticator( - configurationService.getProperty(RDFUtil.STORAGE_GRAPHSTORE_LOGIN_KEY), - configurationService.getProperty(RDFUtil.STORAGE_GRAPHSTORE_PASSWORD_KEY).toCharArray()); - accessor = new DatasetGraphAccessorHTTP(getGraphStoreEndpoint(), - httpAuthenticator); + AuthEnv.get() + .registerUsernamePassword(getGraphStoreEndpoint(), + configurationService.getProperty(RDFUtil.STORAGE_GRAPHSTORE_LOGIN_KEY), + configurationService.getProperty(RDFUtil.STORAGE_GRAPHSTORE_PASSWORD_KEY)); } else { log.debug("Did not found credential to use for our connection to the " + "Graph Store HTTP endpoint, trying to connect unauthenticated."); - accessor = new DatasetGraphAccessorHTTP(getGraphStoreEndpoint()); } - return accessor; + connection = RDFConnectionRemote.service(getGraphStoreEndpoint()).build(); + return connection; } @Override public void delete(String uri) { - this.getAccessor().httpDelete(NodeFactory.createURI(uri)); + this.getConnection().delete(uri); } @Override @@ -97,34 +75,30 @@ public class RDFStorageImpl this.delete(graph); } // clean default graph: - this.getAccessor().httpDelete(); + this.getConnection().delete(); } @Override public List getAllStoredGraphs() { String queryString = "SELECT DISTINCT ?g WHERE { GRAPH ?g { ?s ?p ?o } }"; - QueryExecution qexec; if (configurationService.hasProperty(RDFUtil.STORAGE_SPARQL_LOGIN_KEY) && configurationService.hasProperty(RDFUtil.STORAGE_SPARQL_PASSWORD_KEY)) { - HttpAuthenticator httpAuthenticator = new SimpleAuthenticator( - configurationService.getProperty(RDFUtil.STORAGE_SPARQL_LOGIN_KEY), - configurationService.getProperty(RDFUtil.STORAGE_GRAPHSTORE_PASSWORD_KEY).toCharArray()); - qexec = QueryExecutionFactory.sparqlService(getSparqlEndpoint(), - queryString, httpAuthenticator); - } else { - qexec = QueryExecutionFactory.sparqlService(getSparqlEndpoint(), - queryString); + AuthEnv.get() + .registerUsernamePassword(getSparqlEndpoint(), + configurationService.getProperty(RDFUtil.STORAGE_SPARQL_LOGIN_KEY), + configurationService.getProperty(RDFUtil.STORAGE_GRAPHSTORE_PASSWORD_KEY)); } - ResultSet rs = qexec.execSelect(); - List graphs = Collections.synchronizedList(new ArrayList()); - while (rs.hasNext()) { - QuerySolution solution = rs.next(); - if (solution.contains("g")) { - graphs.add(solution.get("g").asResource().getURI()); + List graphs = Collections.synchronizedList(new ArrayList<>()); + try (QueryExecution qexec = QueryExecutionHTTP.service(getSparqlEndpoint()).queryString(queryString).build()) { + ResultSet rs = qexec.execSelect(); + while (rs.hasNext()) { + QuerySolution solution = rs.next(); + if (solution.contains("g")) { + graphs.add(solution.get("g").asResource().getURI()); + } } } - qexec.close(); return graphs; } diff --git a/dspace-api/src/main/java/org/dspace/scripts/Process.java b/dspace-api/src/main/java/org/dspace/scripts/Process.java index 9834c78c15..375c39ca55 100644 --- a/dspace-api/src/main/java/org/dspace/scripts/Process.java +++ b/dspace-api/src/main/java/org/dspace/scripts/Process.java @@ -10,25 +10,24 @@ package org.dspace.scripts; import java.util.ArrayList; import java.util.Date; import java.util.List; -import javax.persistence.CascadeType; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.FetchType; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.JoinColumn; -import javax.persistence.JoinTable; -import javax.persistence.Lob; -import javax.persistence.ManyToMany; -import javax.persistence.ManyToOne; -import javax.persistence.SequenceGenerator; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.JoinTable; +import jakarta.persistence.ManyToMany; +import jakarta.persistence.ManyToOne; +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.content.Bitstream; @@ -36,7 +35,7 @@ import org.dspace.content.ProcessStatus; import org.dspace.core.ReloadableEntity; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; -import org.hibernate.annotations.Type; +import org.hibernate.Length; /** * This class is the DB Entity representation of the Process object to be stored in the Database @@ -70,9 +69,7 @@ public class Process implements ReloadableEntity { @Enumerated(EnumType.STRING) private ProcessStatus processStatus; - @Lob - @Type(type = "org.hibernate.type.TextType") - @Column(name = "parameters") + @Column(name = "parameters", length = Length.LONG32) private String parameters; @ManyToMany(fetch = FetchType.LAZY) diff --git a/dspace-api/src/main/java/org/dspace/scripts/ScriptServiceImpl.java b/dspace-api/src/main/java/org/dspace/scripts/ScriptServiceImpl.java index abb700cb10..1d9773f5d6 100644 --- a/dspace-api/src/main/java/org/dspace/scripts/ScriptServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/scripts/ScriptServiceImpl.java @@ -12,19 +12,19 @@ import java.util.Comparator; import java.util.List; import java.util.stream.Collectors; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.core.Context; import org.dspace.kernel.ServiceManager; import org.dspace.scripts.configuration.ScriptConfiguration; import org.dspace.scripts.service.ScriptService; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; /** * The implementation for the {@link ScriptService} */ public class ScriptServiceImpl implements ScriptService { - private static final Logger log = LoggerFactory.getLogger(ScriptServiceImpl.class); + private static final Logger log = LogManager.getLogger(); @Autowired private ServiceManager serviceManager; @@ -48,7 +48,7 @@ public class ScriptServiceImpl implements ScriptService { try { return (DSpaceRunnable) scriptToExecute.getDspaceRunnableClass().getDeclaredConstructor().newInstance(); } catch (InvocationTargetException | NoSuchMethodException e) { - log.error(e.getMessage(), e); + log.error(e::getMessage, e); throw new RuntimeException(e); } } diff --git a/dspace-api/src/main/java/org/dspace/scripts/configuration/ScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/scripts/configuration/ScriptConfiguration.java index ec8e3632cf..70debc172a 100644 --- a/dspace-api/src/main/java/org/dspace/scripts/configuration/ScriptConfiguration.java +++ b/dspace-api/src/main/java/org/dspace/scripts/configuration/ScriptConfiguration.java @@ -107,8 +107,8 @@ public abstract class ScriptConfiguration implements B public abstract Options getOptions(); /** - * The getter for the options of the Script (help informations) - * + * The getter for the options of the Script (help information) + * * @return the options value of this ScriptConfiguration for help */ public Options getHelpOptions() { diff --git a/dspace-api/src/main/java/org/dspace/service/ClientInfoService.java b/dspace-api/src/main/java/org/dspace/service/ClientInfoService.java index ef92afab94..9b28dce142 100644 --- a/dspace-api/src/main/java/org/dspace/service/ClientInfoService.java +++ b/dspace-api/src/main/java/org/dspace/service/ClientInfoService.java @@ -7,7 +7,7 @@ */ package org.dspace.service; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; /** * Service that can be used to retrieve information about DSpace clients diff --git a/dspace-api/src/main/java/org/dspace/service/impl/ClientInfoServiceImpl.java b/dspace-api/src/main/java/org/dspace/service/impl/ClientInfoServiceImpl.java index e83aa93e33..edff457ac5 100644 --- a/dspace-api/src/main/java/org/dspace/service/impl/ClientInfoServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/service/impl/ClientInfoServiceImpl.java @@ -10,17 +10,17 @@ package org.dspace.service.impl; import static org.apache.commons.lang3.StringUtils.ordinalIndexOf; import java.net.Inet4Address; -import javax.servlet.http.HttpServletRequest; import com.google.common.net.InetAddresses; +import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.core.Utils; import org.dspace.service.ClientInfoService; import org.dspace.services.ConfigurationService; import org.dspace.statistics.util.IPTable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; /** @@ -32,16 +32,16 @@ public class ClientInfoServiceImpl implements ClientInfoService { private static final String X_FORWARDED_FOR_HEADER = "X-Forwarded-For"; - private static final Logger log = LoggerFactory.getLogger(ClientInfoServiceImpl.class); + private static final Logger log = LogManager.getLogger(); private Boolean useProxiesEnabled; - private ConfigurationService configurationService; + private final ConfigurationService configurationService; /** * Sparse HashTable structure to hold IP address ranges of trusted proxies */ - private IPTable trustedProxies; + private final IPTable trustedProxies; @Autowired(required = true) public ClientInfoServiceImpl(ConfigurationService configurationService) { @@ -84,7 +84,7 @@ public class ClientInfoServiceImpl implements ClientInfoService { public boolean isUseProxiesEnabled() { if (useProxiesEnabled == null) { useProxiesEnabled = configurationService.getBooleanProperty("useProxies", true); - log.info("Proxies (useProxies) enabled? " + useProxiesEnabled); + log.info("Proxies (useProxies) enabled? {}", useProxiesEnabled); } return useProxiesEnabled; @@ -163,6 +163,7 @@ public class ClientInfoServiceImpl implements ClientInfoService { * @param ipAddress IP address to check for * @return true if trusted, false otherwise */ + @Override public boolean isRequestFromTrustedProxy(String ipAddress) { try { return trustedProxies != null && trustedProxies.contains(ipAddress); @@ -205,15 +206,15 @@ public class ClientInfoServiceImpl implements ClientInfoService { } /** - * Anonymize the given IP address by setting the last specified bytes to 0 - * @param ipAddress the ip address to be anonymize + * Anonymize the given IP address by setting the last specified bytes to 0. + * @param ipAddress the ip address to be anonymized * @param bytes the number of bytes to be set to 0 * @return the modified ip address */ private String anonymizeIpAddress(String ipAddress, int bytes) { if (bytes > 4) { - log.warn("It is not possible to anonymize " + bytes + " bytes of an IPv4 address."); + log.warn("It is not possible to anonymize {} bytes of an IPv4 address.", bytes); return ipAddress; } diff --git a/dspace-api/src/main/java/org/dspace/service/impl/HttpConnectionPoolService.java b/dspace-api/src/main/java/org/dspace/service/impl/HttpConnectionPoolService.java index c5f7c46b58..31a8ed12c1 100644 --- a/dspace-api/src/main/java/org/dspace/service/impl/HttpConnectionPoolService.java +++ b/dspace-api/src/main/java/org/dspace/service/impl/HttpConnectionPoolService.java @@ -8,11 +8,11 @@ package org.dspace.service.impl; import java.util.concurrent.TimeUnit; -import javax.annotation.PostConstruct; -import javax.inject.Inject; -import javax.inject.Named; -import javax.inject.Singleton; +import jakarta.annotation.PostConstruct; +import jakarta.inject.Inject; +import jakarta.inject.Named; +import jakarta.inject.Singleton; import org.apache.http.HeaderElement; import org.apache.http.HeaderElementIterator; import org.apache.http.HttpResponse; diff --git a/dspace-api/src/main/java/org/dspace/statistics/AnonymizeStatistics.java b/dspace-api/src/main/java/org/dspace/statistics/AnonymizeStatistics.java index ef2a612133..aef062bc5c 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/AnonymizeStatistics.java +++ b/dspace-api/src/main/java/org/dspace/statistics/AnonymizeStatistics.java @@ -185,7 +185,7 @@ public class AnonymizeStatistics { long total = getDocuments().getResults().getNumFound(); printInfo(total + " documents to update"); - // The documents will be processed in seperate threads. + // The documents will be processed in separate threads. ExecutorService executorService = Executors.newFixedThreadPool(threads); QueryResponse documents; diff --git a/dspace-api/src/main/java/org/dspace/statistics/HttpSolrClientFactory.java b/dspace-api/src/main/java/org/dspace/statistics/HttpSolrClientFactory.java new file mode 100644 index 0000000000..63496ee5dc --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/statistics/HttpSolrClientFactory.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.statistics; + +import org.apache.solr.client.solrj.SolrClient; +import org.apache.solr.client.solrj.impl.HttpSolrClient; + +/** + * Factory of HtmlSolrClient instances. + * + * @author mwood + */ +public class HttpSolrClientFactory + implements SolrClientFactory { + + @Override + public SolrClient getClient(String coreUrl) { + SolrClient client = new HttpSolrClient.Builder() + .withBaseSolrUrl(coreUrl) + .build(); + return client; + } +} diff --git a/dspace-api/src/main/java/org/dspace/statistics/SolrClientFactory.java b/dspace-api/src/main/java/org/dspace/statistics/SolrClientFactory.java new file mode 100644 index 0000000000..1971e40863 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/statistics/SolrClientFactory.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.statistics; + +import org.apache.solr.client.solrj.SolrClient; + +/** + * Build connections to Solr cores. + * + * @author mwood + */ +public interface SolrClientFactory { + /** + * Instantiate a SolrClient connected to a specified core. + * + * @param coreUrl URL of the core to connect with. + * @return a connection to the given core. + */ + public SolrClient getClient(String coreUrl); +} diff --git a/dspace-api/src/main/java/org/dspace/statistics/SolrLoggerServiceImpl.java b/dspace-api/src/main/java/org/dspace/statistics/SolrLoggerServiceImpl.java index 5f976bbfd9..90bc1751f9 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/SolrLoggerServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/statistics/SolrLoggerServiceImpl.java @@ -37,13 +37,13 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; -import javax.servlet.http.HttpServletRequest; import com.maxmind.geoip2.DatabaseReader; import com.maxmind.geoip2.exception.GeoIp2Exception; import com.maxmind.geoip2.model.CityResponse; import com.opencsv.CSVReader; import com.opencsv.CSVWriter; +import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; @@ -115,11 +115,9 @@ import org.springframework.beans.factory.annotation.Autowired; * @author mdiggory at atmire.com */ public class SolrLoggerServiceImpl implements SolrLoggerService, InitializingBean { - private static final Logger log = LogManager.getLogger(); private static final String MULTIPLE_VALUES_SPLITTER = "|"; - protected SolrClient solr; public static final String DATE_FORMAT_8601 = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"; @@ -140,22 +138,22 @@ public class SolrLoggerServiceImpl implements SolrLoggerService, InitializingBea @Autowired(required = true) protected ContentServiceFactory contentServiceFactory; @Autowired(required = true) - private ConfigurationService configurationService; + protected ConfigurationService configurationService; @Autowired(required = true) - private ClientInfoService clientInfoService; + protected ClientInfoService clientInfoService; @Autowired - private SolrStatisticsCore solrStatisticsCore; + protected SolrStatisticsCore solrStatisticsCore; @Autowired - private GeoIpService geoIpService; + protected GeoIpService geoIpService; @Autowired private AuthorizeService authorizeService; - /** URL to the current-year statistics core. Prior-year shards will have a year suffixed. */ - private String statisticsCoreURL; + protected SolrClient solr; /** Name of the current-year statistics core. Prior-year shards will have a year suffixed. */ private String statisticsCoreBase; + /** Possible values of the {@code type} field of a usage event document. */ public static enum StatisticsType { VIEW("view"), SEARCH("search"), @@ -174,13 +172,11 @@ public class SolrLoggerServiceImpl implements SolrLoggerService, InitializingBea } protected SolrLoggerServiceImpl() { - } - @Override public void afterPropertiesSet() throws Exception { - statisticsCoreURL = configurationService.getProperty("solr-statistics.server"); + String statisticsCoreURL = configurationService.getProperty("solr-statistics.server"); if (null != statisticsCoreURL) { Path statisticsPath = Paths.get(new URI(statisticsCoreURL).getPath()); @@ -772,83 +768,40 @@ public class SolrLoggerServiceImpl implements SolrLoggerService, InitializingBea } } - @Override - public void markRobotsByIP() { - for (String ip : SpiderDetector.getSpiderIpAddresses()) { - - try { - - /* Result Process to alter record to be identified as a bot */ - ResultProcessor processor = new ResultProcessor() { - @Override - public void process(SolrInputDocument doc) throws IOException, SolrServerException { - doc.removeField("isBot"); - doc.addField("isBot", true); - solr.add(doc); - log.info("Marked " + doc.getFieldValue("ip") + " as bot"); - } - }; - - /* query for ip, exclude results previously set as bots. */ - processor.execute("ip:" + ip + "* AND -isBot:true"); - - solr.commit(); - - } catch (Exception e) { - log.error(e.getMessage(), e); - } - - - } - - } - - @Override - public void markRobotByUserAgent(String agent) { - try { - - /* Result Process to alter record to be identified as a bot */ - ResultProcessor processor = new ResultProcessor() { - @Override - public void process(SolrInputDocument doc) throws IOException, SolrServerException { + public void markRobots() { + ResultProcessor processor = new ResultProcessor() { + @Override + public void process(SolrInputDocument doc) + throws IOException, SolrServerException { + String clientIP = (String) doc.getFieldValue("ip"); + String hostname = (String) doc.getFieldValue("dns"); + String agent = (String) doc.getFieldValue("userAgent"); + if (SpiderDetector.isSpider(clientIP, null, hostname, agent)) { doc.removeField("isBot"); doc.addField("isBot", true); solr.add(doc); + log.info("Marked {} / {} / {} as a robot in record {}.", + clientIP, hostname, agent, + doc.getField("uid").getValue()); } - }; - - /* query for ip, exclude results previously set as bots. */ - processor.execute("userAgent:" + agent + " AND -isBot:true"); + } + }; + try { + processor.execute("-isBot:true"); solr.commit(); - } catch (Exception e) { - log.error(e.getMessage(), e); + } catch (SolrServerException | IOException ex) { + log.error("Failed while marking robot accesses.", ex); } } @Override - public void deleteRobotsByIsBotFlag() { + public void deleteRobots() { try { solr.deleteByQuery("isBot:true"); - } catch (Exception e) { - log.error(e.getMessage(), e); - } - } - - @Override - public void deleteIP(String ip) { - try { - solr.deleteByQuery("ip:" + ip + "*"); - } catch (Exception e) { - log.error(e.getMessage(), e); - } - } - - @Override - public void deleteRobotsByIP() { - for (String ip : SpiderDetector.getSpiderIpAddresses()) { - deleteIP(ip); + } catch (IOException | SolrServerException e) { + log.error("Failed while deleting robot accesses.", e); } } @@ -880,7 +833,7 @@ public class SolrLoggerServiceImpl implements SolrLoggerService, InitializingBea processor.execute(query); - // Add the new (updated onces + // Add the new (updated once for (int i = 0; i < docsToUpdate.size(); i++) { SolrInputDocument solrDocument = docsToUpdate.get(i); @@ -1052,7 +1005,6 @@ public class SolrLoggerServiceImpl implements SolrLoggerService, InitializingBea } catch (ParseException e1) { e1.printStackTrace(); } - // e.printStackTrace(); } String dateformatString = "dd-MM-yyyy"; if ("DAY".equals(type)) { @@ -1117,7 +1069,7 @@ public class SolrLoggerServiceImpl implements SolrLoggerService, InitializingBea String facetQuery = facetQueries.get(i); solrQuery.addFacetQuery(facetQuery); } - if (0 < facetQueries.size()) { + if (!facetQueries.isEmpty()) { solrQuery.setFacet(true); } } @@ -1135,13 +1087,7 @@ public class SolrLoggerServiceImpl implements SolrLoggerService, InitializingBea // performance and ensure the search result ordering will // not be influenced - // Choose to filter by the Legacy spider IP list (may get too long to properly filter all IP's - if (defaultFilterQueries && configurationService.getBooleanProperty( - "solr-statistics.query.filter.spiderIp", false)) { - solrQuery.addFilterQuery(getIgnoreSpiderIPs()); - } - - // Choose to filter by isBot field, may be overriden in future + // Choose to filter by isBot field, may be overridden in future // to allow views on stats based on bots. if (defaultFilterQueries && configurationService.getBooleanProperty( "solr-statistics.query.filter.isBot", true)) { @@ -1156,7 +1102,7 @@ public class SolrLoggerServiceImpl implements SolrLoggerService, InitializingBea if (defaultFilterQueries && bundles != null && bundles.length > 0) { /** - * The code below creates a query that will allow only records which do not have a bundlename + * The code below creates a query that will allow only records which do not have a bundle name * (items, collections, ...) or bitstreams that have a configured bundle name */ StringBuilder bundleQuery = new StringBuilder(); @@ -1190,32 +1136,6 @@ public class SolrLoggerServiceImpl implements SolrLoggerService, InitializingBea return response; } - - /** - * String of IP and Ranges in IPTable as a Solr Query - */ - protected String filterQuery = null; - - @Override - public String getIgnoreSpiderIPs() { - if (filterQuery == null) { - StringBuilder query = new StringBuilder(); - boolean first = true; - for (String ip : SpiderDetector.getSpiderIpAddresses()) { - if (first) { - query.append(" AND "); - first = false; - } - - query.append(" NOT(ip: ").append(ip).append(")"); - } - filterQuery = query.toString(); - } - - return filterQuery; - - } - @Override public void shardSolrIndex() throws IOException, SolrServerException { if (!(solr instanceof HttpSolrClient)) { @@ -1306,7 +1226,7 @@ public class SolrLoggerServiceImpl implements SolrLoggerService, InitializingBea try ( CloseableHttpClient hc = HttpClientBuilder.create().build(); ) { HttpResponse response = hc.execute(get); csvInputstream = response.getEntity().getContent(); - //Write the csv ouput to a file ! + //Write the csv output to a file ! FileUtils.copyInputStreamToFile(csvInputstream, csvFile); } filesToUpload.add(csvFile); @@ -1653,7 +1573,7 @@ public class SolrLoggerServiceImpl implements SolrLoggerService, InitializingBea * The statistics shards should not be initialized until all tomcat webapps * are fully initialized. DS-3457 uncovered an issue in DSpace 6x in which * this code triggered Tomcat to hang when statistics shards are present. - * This code is synchonized in the event that 2 threads trigger the + * This code is synchronized in the event that 2 threads trigger the * initialization at the same time. */ protected synchronized void initSolrYearCores() { @@ -1702,6 +1622,7 @@ public class SolrLoggerServiceImpl implements SolrLoggerService, InitializingBea statisticYearCoresInit = true; } + @Override public Object anonymizeIp(String ip) throws UnknownHostException { InetAddress address = InetAddress.getByName(ip); if (address instanceof Inet4Address) { diff --git a/dspace-api/src/main/java/org/dspace/statistics/SolrStatisticsCore.java b/dspace-api/src/main/java/org/dspace/statistics/SolrStatisticsCore.java index 9ad72cbf31..d7d249082b 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/SolrStatisticsCore.java +++ b/dspace-api/src/main/java/org/dspace/statistics/SolrStatisticsCore.java @@ -9,8 +9,7 @@ package org.dspace.statistics; import static org.apache.logging.log4j.LogManager.getLogger; -import javax.inject.Named; - +import jakarta.inject.Named; import org.apache.logging.log4j.Logger; import org.apache.solr.client.solrj.SolrClient; import org.apache.solr.client.solrj.impl.HttpSolrClient; @@ -28,10 +27,10 @@ public class SolrStatisticsCore { protected SolrClient solr = null; @Autowired - private ConfigurationService configurationService; + protected ConfigurationService configurationService; @Autowired @Named("solrHttpConnectionPoolService") - private HttpConnectionPoolService httpConnectionPoolService; + protected HttpConnectionPoolService httpConnectionPoolService; /** * Returns the {@link SolrClient} for the Statistics core. diff --git a/dspace-api/src/main/java/org/dspace/statistics/content/DatasetGenerator.java b/dspace-api/src/main/java/org/dspace/statistics/content/DatasetGenerator.java index 85bff41593..2b4247e2a7 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/content/DatasetGenerator.java +++ b/dspace-api/src/main/java/org/dspace/statistics/content/DatasetGenerator.java @@ -19,7 +19,7 @@ package org.dspace.statistics.content; public abstract class DatasetGenerator { /** - * The type of generator can either be CATEGORY or SERIE + * The type of generator can either be CATEGORY or SERIES **/ protected int datasetType; diff --git a/dspace-api/src/main/java/org/dspace/statistics/content/StatisticsDataVisits.java b/dspace-api/src/main/java/org/dspace/statistics/content/StatisticsDataVisits.java index 121e66af48..ba1c51e207 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/content/StatisticsDataVisits.java +++ b/dspace-api/src/main/java/org/dspace/statistics/content/StatisticsDataVisits.java @@ -230,7 +230,7 @@ public class StatisticsDataVisits extends StatisticsData { dataset.setRowLabel(0, getResultName(dataSetQuery.getName(), dataSetQuery, context)); dataset.setRowLabelAttr(0, getAttributes(dataSetQuery.getName(), dataSetQuery, context)); } else { - // We need to get the max objects and the next part of the query on them (next part beeing + // We need to get the max objects and the next part of the query on them (next part being // the datasettimequery ObjectCount[] maxObjectCounts = solrLoggerService .queryFacetField(query, filterQuery, dataSetQuery.getFacetField(), dataSetQuery.getMax(), diff --git a/dspace-api/src/main/java/org/dspace/statistics/export/FailedOpenURLTrackerServiceImpl.java b/dspace-api/src/main/java/org/dspace/statistics/export/FailedOpenURLTrackerServiceImpl.java index cb8e64cc65..cf0972888e 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/export/FailedOpenURLTrackerServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/statistics/export/FailedOpenURLTrackerServiceImpl.java @@ -48,7 +48,7 @@ public class FailedOpenURLTrackerServiceImpl implements FailedOpenURLTrackerServ /** * Creates a new OpenURLTracker * @param context - * @return the creatred OpenURLTracker + * @return the created OpenURLTracker * @throws SQLException */ @Override diff --git a/dspace-api/src/main/java/org/dspace/statistics/export/OpenURLTracker.java b/dspace-api/src/main/java/org/dspace/statistics/export/OpenURLTracker.java index b853f255e8..47809ad230 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/export/OpenURLTracker.java +++ b/dspace-api/src/main/java/org/dspace/statistics/export/OpenURLTracker.java @@ -8,18 +8,19 @@ package org.dspace.statistics.export; import java.util.Date; -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.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.dspace.core.HibernateProxyHelper; import org.dspace.core.ReloadableEntity; -import org.hibernate.proxy.HibernateProxyHelper; + /** * Class that represents an OpenURLTracker which tracks a failed transmission to IRUS diff --git a/dspace-api/src/main/java/org/dspace/statistics/export/processor/BitstreamEventProcessor.java b/dspace-api/src/main/java/org/dspace/statistics/export/processor/BitstreamEventProcessor.java index 85cb7bc14c..41cf655dbc 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/export/processor/BitstreamEventProcessor.java +++ b/dspace-api/src/main/java/org/dspace/statistics/export/processor/BitstreamEventProcessor.java @@ -11,8 +11,8 @@ import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.sql.SQLException; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.content.Bitstream; import org.dspace.content.Bundle; import org.dspace.content.Item; diff --git a/dspace-api/src/main/java/org/dspace/statistics/export/processor/ExportEventProcessor.java b/dspace-api/src/main/java/org/dspace/statistics/export/processor/ExportEventProcessor.java index 609298779d..434de459ba 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/export/processor/ExportEventProcessor.java +++ b/dspace-api/src/main/java/org/dspace/statistics/export/processor/ExportEventProcessor.java @@ -15,8 +15,8 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.List; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.codec.CharEncoding; import org.apache.commons.lang3.StringUtils; import org.dspace.content.DCDate; diff --git a/dspace-api/src/main/java/org/dspace/statistics/export/processor/ItemEventProcessor.java b/dspace-api/src/main/java/org/dspace/statistics/export/processor/ItemEventProcessor.java index 92adb67546..76ea30245d 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/export/processor/ItemEventProcessor.java +++ b/dspace-api/src/main/java/org/dspace/statistics/export/processor/ItemEventProcessor.java @@ -11,14 +11,13 @@ import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.sql.SQLException; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.content.Item; import org.dspace.core.Context; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; - /** * Processor that handles Item events from the IrusExportUsageEventListener */ diff --git a/dspace-api/src/main/java/org/dspace/statistics/export/service/FailedOpenURLTrackerService.java b/dspace-api/src/main/java/org/dspace/statistics/export/service/FailedOpenURLTrackerService.java index 9b482e3d54..ed6f8bc3bf 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/export/service/FailedOpenURLTrackerService.java +++ b/dspace-api/src/main/java/org/dspace/statistics/export/service/FailedOpenURLTrackerService.java @@ -37,7 +37,7 @@ public interface FailedOpenURLTrackerService { /** * Creates a new OpenURLTracker * @param context - * @return the creatred OpenURLTracker + * @return the created OpenURLTracker * @throws SQLException */ OpenURLTracker create(Context context) throws SQLException; diff --git a/dspace-api/src/main/java/org/dspace/statistics/service/SolrLoggerService.java b/dspace-api/src/main/java/org/dspace/statistics/service/SolrLoggerService.java index 61b2bb6013..74b729c353 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/service/SolrLoggerService.java +++ b/dspace-api/src/main/java/org/dspace/statistics/service/SolrLoggerService.java @@ -12,8 +12,8 @@ import java.net.UnknownHostException; import java.sql.SQLException; import java.util.List; import java.util.Map; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.apache.solr.client.solrj.SolrServerException; import org.apache.solr.client.solrj.response.QueryResponse; import org.apache.solr.common.SolrInputDocument; @@ -111,15 +111,18 @@ public interface SolrLoggerService { List oldFieldVals, String field) throws IOException; - public void markRobotsByIP(); + /** + * Scan the entire 'statistics' collection for documents that should be + * marked 'isBot:true' according to + * {@link org.dspace.statistics.util.SpiderDetector#isSpider(java.lang.String, + * java.lang.String, java.lang.String, java.lang.String)}. + */ + public void markRobots(); - public void markRobotByUserAgent(String agent); - - public void deleteRobotsByIsBotFlag(); - - public void deleteIP(String ip); - - public void deleteRobotsByIP(); + /** + * Delete all 'statistics' documents having 'isBot:true'. + */ + public void deleteRobots(); /* * update(String query, boolean addField, String fieldName, Object @@ -259,13 +262,6 @@ public interface SolrLoggerService { int facetMinCount, boolean defaultFilterQueries) throws SolrServerException, IOException; - /** - * Returns in a filterQuery string all the ip addresses that should be ignored - * - * @return a string query with ip addresses - */ - public String getIgnoreSpiderIPs(); - public void shardSolrIndex() throws IOException, SolrServerException; public void reindexBitstreamHits(boolean removeDeletedBitstreams) throws Exception; diff --git a/dspace-api/src/main/java/org/dspace/statistics/util/IPTable.java b/dspace-api/src/main/java/org/dspace/statistics/util/IPTable.java index cb94dcc1a1..9d571cb751 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/util/IPTable.java +++ b/dspace-api/src/main/java/org/dspace/statistics/util/IPTable.java @@ -174,6 +174,9 @@ public class IPTable { * @throws IPFormatException Exception Class to deal with IPFormat errors. */ public boolean contains(String ip) throws IPFormatException { + if (null == ip) { + throw new IPFormatException("Address may not be null"); + } try { long ipToTest = ipToLong(InetAddress.getByName(ip)); diff --git a/dspace-api/src/main/java/org/dspace/statistics/util/SpiderDetector.java b/dspace-api/src/main/java/org/dspace/statistics/util/SpiderDetector.java index c4c5765e08..fa5f71e326 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/util/SpiderDetector.java +++ b/dspace-api/src/main/java/org/dspace/statistics/util/SpiderDetector.java @@ -10,14 +10,13 @@ package org.dspace.statistics.util; import java.io.File; import java.io.IOException; import java.util.Set; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.statistics.factory.StatisticsServiceFactory; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** - * SpiderDetector delegates static methods to SpiderDetectorService, which is used to find IP's that are spiders... + * SpiderDetector delegates static methods to SpiderDetectorService, + * which is used to find IPs, hosts, or agents that are spiders... * * @author kevinvandevelde at atmire.com * @author ben at atmire.com @@ -25,29 +24,16 @@ import org.slf4j.LoggerFactory; * @author frederic at atmire.com */ public class SpiderDetector { - - private static final Logger log = LoggerFactory.getLogger(SpiderDetector.class); - - //Service where all methods get delegated to, this is instantiated by a spring-bean defined in core-services.xml - private static SpiderDetectorService spiderDetectorService = StatisticsServiceFactory.getInstance() - .getSpiderDetectorService(); + //Service where all methods get delegated to. This is instantiated by a + // Spring bean defined in core-services.xml + private static final SpiderDetectorService spiderDetectorService + = StatisticsServiceFactory.getInstance().getSpiderDetectorService(); /** * Default constructor */ private SpiderDetector() { } - /** - * Get an immutable Set representing all the Spider Addresses here - * - * @return a set of IP addresses as strings - */ - public static Set getSpiderIpAddresses() { - - spiderDetectorService.loadSpiderIpAddresses(); - return spiderDetectorService.getTable().toSet(); - } - /** * Utility method which reads lines from a file & returns them in a Set. * diff --git a/dspace-api/src/main/java/org/dspace/statistics/util/SpiderDetectorService.java b/dspace-api/src/main/java/org/dspace/statistics/util/SpiderDetectorService.java index 710e8472d2..cf5a5d4319 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/util/SpiderDetectorService.java +++ b/dspace-api/src/main/java/org/dspace/statistics/util/SpiderDetectorService.java @@ -10,26 +10,60 @@ package org.dspace.statistics.util; import java.io.File; import java.io.IOException; import java.util.Set; -import javax.servlet.http.HttpServletRequest; + +import jakarta.servlet.http.HttpServletRequest; /** - * Interface to implement a SpiderDetectorService + * Interface to implement a SpiderDetectorService. * * @author frederic at atmire.com */ public interface SpiderDetectorService { + /** + * Service Method for testing spiders against existing spider files. + * + * @param clientIP address of the client. + * @param proxyIPs comma-list of X-Forwarded-For addresses, or null. + * @param hostname domain name of host, or null. + * @param agent User-Agent header value, or null. + * @return true if the client matches any spider characteristics list. + */ public boolean isSpider(String clientIP, String proxyIPs, String hostname, String agent); + /** + * Service Method for testing spiders against existing spider files. + * + * @param request the current HTTP request. + * @return true|false if the request was detected to be from a spider. + */ public boolean isSpider(HttpServletRequest request); + /** + * Check individual IP is a spider. + * + * @param ip the IP address to be checked. + * @return if is spider IP + */ public boolean isSpider(String ip); + /** + * Loader to populate the IP address table from files. + */ public void loadSpiderIpAddresses(); + /** + * Utility method which reads lines from a file & returns them in a Set. + * + * @param patternFile the location of our spider file + * @return a vector full of patterns + * @throws IOException could not happen since we check the file be4 we use it + */ public Set readPatterns(File patternFile) throws IOException; + /** + * @return the table of IP net blocks. + */ public IPTable getTable(); - } diff --git a/dspace-api/src/main/java/org/dspace/statistics/util/SpiderDetectorServiceImpl.java b/dspace-api/src/main/java/org/dspace/statistics/util/SpiderDetectorServiceImpl.java index 0b5149df74..f1b03b5439 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/util/SpiderDetectorServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/statistics/util/SpiderDetectorServiceImpl.java @@ -17,14 +17,15 @@ import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.regex.Pattern; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.constraints.NotNull; import org.apache.commons.configuration2.ex.ConversionException; import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.service.ClientInfoService; import org.dspace.services.ConfigurationService; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; /** @@ -39,18 +40,18 @@ import org.springframework.beans.factory.annotation.Autowired; */ public class SpiderDetectorServiceImpl implements SpiderDetectorService { - private static final Logger log = LoggerFactory.getLogger(SpiderDetectorServiceImpl.class); + private static final Logger log = LogManager.getLogger(); private Boolean useCaseInsensitiveMatching; private final List agents - = Collections.synchronizedList(new ArrayList()); + = Collections.synchronizedList(new ArrayList<>()); private final List domains - = Collections.synchronizedList(new ArrayList()); + = Collections.synchronizedList(new ArrayList<>()); - private ConfigurationService configurationService; - private ClientInfoService clientInfoService; + private final ConfigurationService configurationService; + private final ClientInfoService clientInfoService; /** * Sparse HashTable structure to hold IP address ranges. @@ -63,23 +64,17 @@ public class SpiderDetectorServiceImpl implements SpiderDetectorService { this.clientInfoService = clientInfoService; } + @Override public IPTable getTable() { return table; } - /** - * Service Method for testing spiders against existing spider files. - *

    + /* * In future spiders HashSet may be optimized as byte offset array to * improve performance and memory footprint further. - * - * @param clientIP address of the client. - * @param proxyIPs comma-list of X-Forwarded-For addresses, or null. - * @param hostname domain name of host, or null. - * @param agent User-Agent header value, or null. - * @return true if the client matches any spider characteristics list. */ - public boolean isSpider(String clientIP, String proxyIPs, String hostname, String agent) { + @Override + public boolean isSpider(@NotNull String clientIP, String proxyIPs, String hostname, String agent) { // See if any agent patterns match if (null != agent) { synchronized (agents) { @@ -137,13 +132,7 @@ public class SpiderDetectorServiceImpl implements SpiderDetectorService { return false; } - /** - * Utility method which reads lines from a file & returns them in a Set. - * - * @param patternFile the location of our spider file - * @return a vector full of patterns - * @throws IOException could not happen since we check the file be4 we use it - */ + @Override public Set readPatterns(File patternFile) throws IOException { Set patterns = new HashSet<>(); @@ -191,7 +180,7 @@ public class SpiderDetectorServiceImpl implements SpiderDetectorService { patterns = readPatterns(file); } catch (IOException ex) { log.error("Patterns not read from {}: {}", - file.getPath(), ex.getMessage()); + file::getPath, ex::getMessage); continue; } //If case insensitive matching is enabled, lowercase the patterns so they can be lowercase matched @@ -203,19 +192,14 @@ public class SpiderDetectorServiceImpl implements SpiderDetectorService { } - log.info("Loaded pattern file: {}", file.getPath()); + log.info("Loaded pattern file: {}", file::getPath); } } else { - log.info("No patterns loaded from {}", patternsDir.getPath()); + log.info("No patterns loaded from {}", patternsDir::getPath); } } - /** - * Service Method for testing spiders against existing spider files. - * - * @param request - * @return true|false if the request was detected to be from a spider. - */ + @Override public boolean isSpider(HttpServletRequest request) { return isSpider(request.getRemoteAddr(), request.getHeader("X-Forwarded-For"), @@ -223,12 +207,7 @@ public class SpiderDetectorServiceImpl implements SpiderDetectorService { request.getHeader("User-Agent")); } - /** - * Check individual IP is a spider. - * - * @param ip - * @return if is spider IP - */ + @Override public boolean isSpider(String ip) { if (table == null) { loadSpiderIpAddresses(); @@ -238,16 +217,15 @@ public class SpiderDetectorServiceImpl implements SpiderDetectorService { if (table.contains(ip)) { return true; } - } catch (Exception e) { + } catch (IPTable.IPFormatException e) { + log.warn("Assumed not a spider: {}", e::getMessage); return false; } return false; } - /* - * loader to populate the table from files. - */ + @Override public synchronized void loadSpiderIpAddresses() { if (table == null) { @@ -289,7 +267,7 @@ public class SpiderDetectorServiceImpl implements SpiderDetectorService { } /** - * checks if case insensitive matching is enabled + * Checks if case insensitive matching is enabled. * * @return true if it's enabled, false if not */ @@ -306,5 +284,4 @@ public class SpiderDetectorServiceImpl implements SpiderDetectorService { return useCaseInsensitiveMatching; } - } diff --git a/dspace-api/src/main/java/org/dspace/statistics/util/StatisticsClient.java b/dspace-api/src/main/java/org/dspace/statistics/util/StatisticsClient.java index 319fe437d6..95c4f1f813 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/util/StatisticsClient.java +++ b/dspace-api/src/main/java/org/dspace/statistics/util/StatisticsClient.java @@ -66,7 +66,6 @@ public class StatisticsClient { options.addOption("m", "mark-spiders", false, "Update isBot Flag in Solr"); options.addOption("f", "delete-spiders-by-flag", false, "Delete Spiders in Solr By isBot Flag"); - options.addOption("i", "delete-spiders-by-ip", false, "Delete Spiders in Solr By IP Address"); options.addOption("b", "reindex-bitstreams", false, "Reindex the bitstreams to ensure we have the bundle name"); options.addOption("e", "export", false, "Export SOLR view statistics data to usage-statistics-intermediate-format"); @@ -87,11 +86,9 @@ public class StatisticsClient { if (line.hasOption("u")) { StatisticsClient.updateSpiderFiles(); } else if (line.hasOption('m')) { - solrLoggerService.markRobotsByIP(); + solrLoggerService.markRobots(); } else if (line.hasOption('f')) { - solrLoggerService.deleteRobotsByIsBotFlag(); - } else if (line.hasOption('i')) { - solrLoggerService.deleteRobotsByIP(); + solrLoggerService.deleteRobots(); } else if (line.hasOption('b')) { solrLoggerService.reindexBitstreamHits(line.hasOption('r')); } else if (line.hasOption('e')) { @@ -104,7 +101,7 @@ public class StatisticsClient { } /** - * Method to update Spiders in config directory. + * Method to update Spiders in configuration directory. */ private static void updateSpiderFiles() { try { diff --git a/dspace-api/src/main/java/org/dspace/storage/bitstore/BitstreamStorageServiceImpl.java b/dspace-api/src/main/java/org/dspace/storage/bitstore/BitstreamStorageServiceImpl.java index 956ac5a7f8..290e480a93 100644 --- a/dspace-api/src/main/java/org/dspace/storage/bitstore/BitstreamStorageServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/storage/bitstore/BitstreamStorageServiceImpl.java @@ -15,8 +15,8 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.UUID; -import javax.annotation.Nullable; +import jakarta.annotation.Nullable; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections4.MapUtils; import org.apache.logging.log4j.LogManager; @@ -148,7 +148,7 @@ public class BitstreamStorageServiceImpl implements BitstreamStorageService, Ini * @param assetstore The assetstore number for the bitstream to be * registered * @param bitstreamPath The relative path of the bitstream to be registered. - * The path is relative to the path of ths assetstore. + * The path is relative to the path of this assetstore. * @return The ID of the registered bitstream * @throws SQLException If a problem occurs accessing the RDBMS * @throws IOException if IO error @@ -224,7 +224,7 @@ public class BitstreamStorageServiceImpl implements BitstreamStorageService, Ini int cleanedBitstreamCount = 0; int deletedBitstreamCount = bitstreamService.countDeletedBitstreams(context); - System.out.println("Found " + deletedBitstreamCount + " deleted bistream to cleanup"); + System.out.println("Found " + deletedBitstreamCount + " deleted bitstream to cleanup"); try { context.turnOffAuthorisationSystem(); diff --git a/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java b/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java index e21a101a7e..4d32b50d67 100644 --- a/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java +++ b/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java @@ -22,7 +22,6 @@ import java.util.List; import java.util.Map; import java.util.UUID; import java.util.function.Supplier; -import javax.validation.constraints.NotNull; import com.amazonaws.AmazonClientException; import com.amazonaws.auth.AWSCredentials; @@ -39,6 +38,7 @@ import com.amazonaws.services.s3.transfer.Download; import com.amazonaws.services.s3.transfer.TransferManager; import com.amazonaws.services.s3.transfer.TransferManagerBuilder; import com.amazonaws.services.s3.transfer.Upload; +import jakarta.validation.constraints.NotNull; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.DefaultParser; import org.apache.commons.cli.HelpFormatter; @@ -553,7 +553,7 @@ public class S3BitStoreService extends BaseBitStoreService { store.bucketName = DEFAULT_BUCKET_PREFIX + hostname + ".s3test"; store.s3Service.createBucket(store.bucketName); /* Broken in DSpace 6 TODO Refactor - // time everything, todo, swtich to caliper + // time everything, todo, switch to caliper long start = System.currentTimeMillis(); // Case 1: store a file String id = store.generateId(); diff --git a/dspace-api/src/main/java/org/dspace/storage/bitstore/service/BitstreamStorageService.java b/dspace-api/src/main/java/org/dspace/storage/bitstore/service/BitstreamStorageService.java index 7f5ed8f912..8c3a67ee9a 100644 --- a/dspace-api/src/main/java/org/dspace/storage/bitstore/service/BitstreamStorageService.java +++ b/dspace-api/src/main/java/org/dspace/storage/bitstore/service/BitstreamStorageService.java @@ -12,8 +12,8 @@ import java.io.InputStream; import java.sql.SQLException; import java.util.Map; import java.util.UUID; -import javax.annotation.Nullable; +import jakarta.annotation.Nullable; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Bitstream; import org.dspace.core.Context; @@ -92,7 +92,7 @@ public interface BitstreamStorageService { * @param assetstore The assetstore number for the bitstream to be * registered * @param bitstreamPath The relative path of the bitstream to be registered. - * The path is relative to the path of ths assetstore. + * The path is relative to the path of this assetstore. * @return The ID of the registered bitstream * @throws SQLException If a problem occurs accessing the RDBMS * @throws IOException if IO error diff --git a/dspace-api/src/main/java/org/dspace/storage/rdbms/DatabaseUtils.java b/dspace-api/src/main/java/org/dspace/storage/rdbms/DatabaseUtils.java index 0732eea2a0..7a2d3a8b74 100644 --- a/dspace-api/src/main/java/org/dspace/storage/rdbms/DatabaseUtils.java +++ b/dspace-api/src/main/java/org/dspace/storage/rdbms/DatabaseUtils.java @@ -635,7 +635,7 @@ public class DatabaseUtils { // For DSpace, we sometimes have to insert "old" migrations in after a major release // if further development/bug fixes are needed in older versions. So, "Ignored" migrations are // nothing to worry about...you can always trigger them to run using "database migrate ignored" from CLI - flywayConfiguration.ignoreIgnoredMigrations(true); + flywayConfiguration.ignoreMigrationPatterns("*:ignored"); // Set Flyway callbacks (i.e. classes which are called post-DB migration and similar) List flywayCallbacks = DSpaceServicesFactory.getInstance().getServiceManager() diff --git a/dspace-api/src/main/java/org/dspace/storage/rdbms/PostgresUtils.java b/dspace-api/src/main/java/org/dspace/storage/rdbms/PostgresUtils.java index 0d049bb6ac..38bb639983 100644 --- a/dspace-api/src/main/java/org/dspace/storage/rdbms/PostgresUtils.java +++ b/dspace-api/src/main/java/org/dspace/storage/rdbms/PostgresUtils.java @@ -26,7 +26,7 @@ import org.flywaydb.core.api.FlywayException; * @author Tim Donohue */ public class PostgresUtils { - // PostgreSQL pgcrypto extention name, and required versions of Postgres & pgcrypto + // PostgreSQL pgcrypto extension name, and required versions of Postgres & pgcrypto public static final String PGCRYPTO = "pgcrypto"; public static final Double PGCRYPTO_VERSION = 1.1; public static final Double POSTGRES_VERSION = 9.4; diff --git a/dspace-api/src/main/java/org/dspace/storage/rdbms/RegistryUpdater.java b/dspace-api/src/main/java/org/dspace/storage/rdbms/RegistryUpdater.java index 7debf3ba44..1419910d52 100644 --- a/dspace-api/src/main/java/org/dspace/storage/rdbms/RegistryUpdater.java +++ b/dspace-api/src/main/java/org/dspace/storage/rdbms/RegistryUpdater.java @@ -14,6 +14,8 @@ import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.TransformerException; import javax.xml.xpath.XPathExpressionException; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.administer.MetadataImporter; import org.dspace.administer.RegistryImportException; import org.dspace.administer.RegistryLoader; @@ -24,8 +26,6 @@ import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; import org.flywaydb.core.api.callback.Callback; import org.flywaydb.core.api.callback.Event; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.xml.sax.SAXException; /** @@ -52,7 +52,7 @@ public class RegistryUpdater implements Callback { /** * logging category */ - private static final Logger log = LoggerFactory.getLogger(RegistryUpdater.class); + private static final Logger log = LogManager.getLogger(); /** * Method to actually update our registries from latest configuration files. @@ -86,7 +86,7 @@ public class RegistryUpdater implements Callback { context.restoreAuthSystemState(); // Commit changes and close context context.complete(); - log.info("All Bitstream Format Regitry and Metadata Registry updates were completed."); + log.info("All Bitstream Format Registry and Metadata Registry updates were completed."); } catch (IOException | SQLException | ParserConfigurationException | TransformerException | RegistryImportException | AuthorizeException | NonUniqueMetadataException diff --git a/dspace-api/src/main/java/org/dspace/storage/rdbms/migration/MigrationUtils.java b/dspace-api/src/main/java/org/dspace/storage/rdbms/migration/MigrationUtils.java index f0c4e4e179..28df30d870 100644 --- a/dspace-api/src/main/java/org/dspace/storage/rdbms/migration/MigrationUtils.java +++ b/dspace-api/src/main/java/org/dspace/storage/rdbms/migration/MigrationUtils.java @@ -108,7 +108,7 @@ public class MigrationUtils { // As long as we have a constraint name, drop it if (constraintName != null && !constraintName.isEmpty()) { - // This drop constaint SQL should be the same in all databases + // This drop constraint SQL should be the same in all databases String dropConstraintSQL = "ALTER TABLE " + tableName + " DROP CONSTRAINT " + constraintName; if (cascade) { dropConstraintSQL += " CASCADE"; diff --git a/dspace-api/src/main/java/org/dspace/storage/rdbms/migration/V1_3_9__Drop_constraint_for_DSpace_1_4_schema.java b/dspace-api/src/main/java/org/dspace/storage/rdbms/migration/V1_3_9__Drop_constraint_for_DSpace_1_4_schema.java index 758e745ddc..801e3faadb 100644 --- a/dspace-api/src/main/java/org/dspace/storage/rdbms/migration/V1_3_9__Drop_constraint_for_DSpace_1_4_schema.java +++ b/dspace-api/src/main/java/org/dspace/storage/rdbms/migration/V1_3_9__Drop_constraint_for_DSpace_1_4_schema.java @@ -24,7 +24,7 @@ import org.flywaydb.core.api.migration.Context; * since unit tests require H2 and the syntax for H2 is different from Postgres). *

    * NOTE: This migration class is very simple because it is meant to be used - * in conjuction with the corresponding SQL script: + * in conjunction with the corresponding SQL script: * ./etc/migrations/[db-type]/V1.4__Upgrade_to_DSpace_1.4_schema.sql *

    * Also note that this migration is "hackingly" versioned "1.3.9" as it needs to diff --git a/dspace-api/src/main/java/org/dspace/storage/rdbms/migration/V1_5_9__Drop_constraint_for_DSpace_1_6_schema.java b/dspace-api/src/main/java/org/dspace/storage/rdbms/migration/V1_5_9__Drop_constraint_for_DSpace_1_6_schema.java index 37100a17f9..2f24f4c254 100644 --- a/dspace-api/src/main/java/org/dspace/storage/rdbms/migration/V1_5_9__Drop_constraint_for_DSpace_1_6_schema.java +++ b/dspace-api/src/main/java/org/dspace/storage/rdbms/migration/V1_5_9__Drop_constraint_for_DSpace_1_6_schema.java @@ -24,7 +24,7 @@ import org.flywaydb.core.api.migration.Context; * since unit tests require H2 and the syntax for H2 is different from Postgres). *

    * NOTE: This migration class is very simple because it is meant to be used - * in conjuction with the corresponding SQL script: + * in conjunction with the corresponding SQL script: * ./etc/migrations/[db-type]/V1.6__Upgrade_to_DSpace_1.6_schema.sql *

    * Also note that this migration is "hackingly" versioned "1.5.9" as it needs to diff --git a/dspace-api/src/main/java/org/dspace/storage/rdbms/migration/V5_0_2014_09_25__DS_1582_Metadata_For_All_Objects_drop_constraint.java b/dspace-api/src/main/java/org/dspace/storage/rdbms/migration/V5_0_2014_09_25__DS_1582_Metadata_For_All_Objects_drop_constraint.java index 8e2be91127..337190ec39 100644 --- a/dspace-api/src/main/java/org/dspace/storage/rdbms/migration/V5_0_2014_09_25__DS_1582_Metadata_For_All_Objects_drop_constraint.java +++ b/dspace-api/src/main/java/org/dspace/storage/rdbms/migration/V5_0_2014_09_25__DS_1582_Metadata_For_All_Objects_drop_constraint.java @@ -25,7 +25,7 @@ import org.flywaydb.core.api.migration.Context; * since unit tests require H2 and the syntax for H2 is different from Postgres). *

    * NOTE: This migration class is very simple because it is meant to be used - * in conjuction with the corresponding SQL script: + * in conjunction with the corresponding SQL script: * ./etc/migrations/[db-type]/V5.0_2014_09_26__DS-1582_Metadata_For_All_Objects.sql *

    * Also note that this migration is dated as 2014_09_25 so that it will run diff --git a/dspace-api/src/main/java/org/dspace/storage/rdbms/migration/V6_0_2016_01_26__DS_2188_Remove_DBMS_Browse_Tables.java b/dspace-api/src/main/java/org/dspace/storage/rdbms/migration/V6_0_2016_01_26__DS_2188_Remove_DBMS_Browse_Tables.java index daf2269e92..5893650a42 100644 --- a/dspace-api/src/main/java/org/dspace/storage/rdbms/migration/V6_0_2016_01_26__DS_2188_Remove_DBMS_Browse_Tables.java +++ b/dspace-api/src/main/java/org/dspace/storage/rdbms/migration/V6_0_2016_01_26__DS_2188_Remove_DBMS_Browse_Tables.java @@ -58,7 +58,7 @@ public class V6_0_2016_01_26__DS_2188_Remove_DBMS_Browse_Tables extends BaseJava // Keep looping (incrementing our index by 1) until we've hit three index // tables that have not been found. // We don't actually know how many index tables will be in each database, - // and there are no guarrantees it'll match the highest index of the site's + // and there are no guarantees it'll match the highest index of the site's // existing "webui.browse.index.#" settings. // Since that's the case, we'll just keep searching for index tables, // until we encounter a total of three that are not found. diff --git a/dspace-api/src/main/java/org/dspace/storage/rdbms/migration/V7_0_2020_10_31__CollectionCommunity_Metadata_Handle.java b/dspace-api/src/main/java/org/dspace/storage/rdbms/migration/V7_0_2020_10_31__CollectionCommunity_Metadata_Handle.java index 21ebcd5edd..973134bfd1 100644 --- a/dspace-api/src/main/java/org/dspace/storage/rdbms/migration/V7_0_2020_10_31__CollectionCommunity_Metadata_Handle.java +++ b/dspace-api/src/main/java/org/dspace/storage/rdbms/migration/V7_0_2020_10_31__CollectionCommunity_Metadata_Handle.java @@ -18,7 +18,7 @@ import org.flywaydb.core.api.migration.BaseJavaMigration; import org.flywaydb.core.api.migration.Context; /** - * Insert a 'dc.idendifier.uri' metadata record for each Community and Collection in the database. + * Insert a 'dc.identifier.uri' metadata record for each Community and Collection in the database. * The value is calculated concatenating the canonicalPrefix extracted from the configuration * (default is "http://hdl.handle.net/) and the object's handle suffix stored inside the handle table. * diff --git a/dspace-api/src/main/java/org/dspace/storage/rdbms/xmlworkflow/V5_0_2014_11_04__Enable_XMLWorkflow_Migration.java b/dspace-api/src/main/java/org/dspace/storage/rdbms/xmlworkflow/V5_0_2014_11_04__Enable_XMLWorkflow_Migration.java index 0361e68053..d461da4d2d 100644 --- a/dspace-api/src/main/java/org/dspace/storage/rdbms/xmlworkflow/V5_0_2014_11_04__Enable_XMLWorkflow_Migration.java +++ b/dspace-api/src/main/java/org/dspace/storage/rdbms/xmlworkflow/V5_0_2014_11_04__Enable_XMLWorkflow_Migration.java @@ -14,8 +14,6 @@ import org.dspace.storage.rdbms.DatabaseUtils; import org.dspace.storage.rdbms.migration.MigrationUtils; import org.flywaydb.core.api.migration.BaseJavaMigration; import org.flywaydb.core.api.migration.Context; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * This class automatically migrates your DSpace Database to use the @@ -35,11 +33,6 @@ import org.slf4j.LoggerFactory; */ public class V5_0_2014_11_04__Enable_XMLWorkflow_Migration extends BaseJavaMigration { - /** - * logging category - */ - private static final Logger log = LoggerFactory.getLogger(V5_0_2014_11_04__Enable_XMLWorkflow_Migration.class); - // Size of migration script run Integer migration_file_size = -1; diff --git a/dspace-api/src/main/java/org/dspace/submit/model/UploadConfiguration.java b/dspace-api/src/main/java/org/dspace/submit/model/UploadConfiguration.java index a6421b3f7a..757a01cb1c 100644 --- a/dspace-api/src/main/java/org/dspace/submit/model/UploadConfiguration.java +++ b/dspace-api/src/main/java/org/dspace/submit/model/UploadConfiguration.java @@ -8,8 +8,8 @@ package org.dspace.submit.model; import java.util.List; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.dspace.services.ConfigurationService; /** diff --git a/dspace-api/src/main/java/org/dspace/supervision/SupervisionOrder.java b/dspace-api/src/main/java/org/dspace/supervision/SupervisionOrder.java index 52d5dacb74..6b447361ef 100644 --- a/dspace-api/src/main/java/org/dspace/supervision/SupervisionOrder.java +++ b/dspace-api/src/main/java/org/dspace/supervision/SupervisionOrder.java @@ -7,17 +7,16 @@ */ package org.dspace.supervision; -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 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 org.dspace.content.Item; import org.dspace.core.Context; import org.dspace.core.ReloadableEntity; diff --git a/dspace-api/src/main/java/org/dspace/supervision/dao/impl/SupervisionOrderDaoImpl.java b/dspace-api/src/main/java/org/dspace/supervision/dao/impl/SupervisionOrderDaoImpl.java index 09cd0841e7..ac056d5530 100644 --- a/dspace-api/src/main/java/org/dspace/supervision/dao/impl/SupervisionOrderDaoImpl.java +++ b/dspace-api/src/main/java/org/dspace/supervision/dao/impl/SupervisionOrderDaoImpl.java @@ -9,10 +9,10 @@ package org.dspace.supervision.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.content.Item; import org.dspace.core.AbstractHibernateDAO; import org.dspace.core.Context; diff --git a/dspace-api/src/main/java/org/dspace/usage/TabFileUsageEventListener.java b/dspace-api/src/main/java/org/dspace/usage/TabFileUsageEventListener.java index dc1aeb56c9..9ed663c39d 100644 --- a/dspace-api/src/main/java/org/dspace/usage/TabFileUsageEventListener.java +++ b/dspace-api/src/main/java/org/dspace/usage/TabFileUsageEventListener.java @@ -15,16 +15,16 @@ import java.io.PrintWriter; import java.text.SimpleDateFormat; import java.util.Date; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.core.Constants; import org.dspace.services.ConfigurationService; import org.dspace.services.model.Event; import org.dspace.utils.DSpace; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** - * Serialize {@link UsageEvent} data to a file as Tab delimited. In dspace.cfg - * specify the path to the file as the value of + * Serialize {@link UsageEvent} data to a file as Tab delimited. + * In {@code dspace.cfg} specify the path to the file as the value of * {@code usageEvent.tabFileLogger.file}. If that path is not absolute, it * will be interpreted as relative to the directory named in {@code log.dir}. * If no name is configured, it defaults to "usage-events.tsv". If the file is @@ -38,8 +38,7 @@ public class TabFileUsageEventListener /** * log category. */ - private static final Logger errorLog = LoggerFactory - .getLogger(TabFileUsageEventListener.class); + private static final Logger errorLog = LogManager.getLogger(); /** * ISO 8601 Basic string format for record timestamps. @@ -77,11 +76,11 @@ public class TabFileUsageEventListener try { eventLog = new PrintWriter(new OutputStreamWriter( new FileOutputStream(logFile, true))); - errorLog.debug("Writing to {}", logFile.getAbsolutePath()); + errorLog.debug("Writing to {}", logFile::getAbsolutePath); } catch (FileNotFoundException e) { errorLog.error("{} cannot open file, will not log events: {}", - TabFileUsageEventListener.class.getName(), - e.getMessage()); + TabFileUsageEventListener.class::getName, + e::getMessage); throw new IllegalArgumentException("Cannot open event log file", e); } @@ -104,9 +103,7 @@ public class TabFileUsageEventListener init(); } - if (errorLog.isDebugEnabled()) { - errorLog.debug("got: {}", event.toString()); - } + errorLog.debug("got: {}", event::toString); if (!(event instanceof UsageEvent)) { return; diff --git a/dspace-api/src/main/java/org/dspace/usage/UsageEvent.java b/dspace-api/src/main/java/org/dspace/usage/UsageEvent.java index ec9a2b1264..90dd9e47e2 100644 --- a/dspace-api/src/main/java/org/dspace/usage/UsageEvent.java +++ b/dspace-api/src/main/java/org/dspace/usage/UsageEvent.java @@ -7,8 +7,7 @@ */ package org.dspace.usage; -import javax.servlet.http.HttpServletRequest; - +import jakarta.servlet.http.HttpServletRequest; import org.dspace.content.DSpaceObject; import org.dspace.core.Constants; import org.dspace.core.Context; diff --git a/dspace-api/src/main/java/org/dspace/usage/UsageSearchEvent.java b/dspace-api/src/main/java/org/dspace/usage/UsageSearchEvent.java index 938f9e7984..f285a4f90a 100644 --- a/dspace-api/src/main/java/org/dspace/usage/UsageSearchEvent.java +++ b/dspace-api/src/main/java/org/dspace/usage/UsageSearchEvent.java @@ -8,8 +8,8 @@ package org.dspace.usage; import java.util.List; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.content.DSpaceObject; import org.dspace.core.Context; diff --git a/dspace-api/src/main/java/org/dspace/util/DSpacePostgreSQLDialect.java b/dspace-api/src/main/java/org/dspace/util/DSpacePostgreSQLDialect.java new file mode 100644 index 0000000000..8b982d4544 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/util/DSpacePostgreSQLDialect.java @@ -0,0 +1,50 @@ +/** + * 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.util; + +import org.hibernate.boot.model.FunctionContributions; +import org.hibernate.dialect.PostgreSQLDialect; +import org.hibernate.type.BasicTypeRegistry; +import org.hibernate.type.StandardBasicTypes; + +/** + * PostgreSQL-specific dialect that adds regular expression support as a JPA function. + * @see org.dspace.contentreport.QueryOperator + * @author Jean-François Morin (Université Laval) + */ +public class DSpacePostgreSQLDialect extends PostgreSQLDialect { + + public static final String REGEX_MATCHES = "matches"; + public static final String REGEX_IMATCHES = "imatches"; + public static final String REGEX_NOT_MATCHES = "not_matches"; + public static final String REGEX_NOT_IMATCHES = "not_imatches"; + + @Override + public void initializeFunctionRegistry(FunctionContributions functionContributions) { + super.initializeFunctionRegistry(functionContributions); + + BasicTypeRegistry basicTypeRegistry = functionContributions.getTypeConfiguration().getBasicTypeRegistry(); + + functionContributions.getFunctionRegistry().registerPattern( + REGEX_MATCHES, + "?1 ~ ?2", + basicTypeRegistry.resolve( StandardBasicTypes.BOOLEAN )); + functionContributions.getFunctionRegistry().registerPattern( + REGEX_IMATCHES, + "?1 ~* ?2", + basicTypeRegistry.resolve( StandardBasicTypes.BOOLEAN )); + functionContributions.getFunctionRegistry().registerPattern( + REGEX_NOT_MATCHES, + "?1 !~ ?2", + basicTypeRegistry.resolve( StandardBasicTypes.BOOLEAN )); + functionContributions.getFunctionRegistry().registerPattern( + REGEX_NOT_IMATCHES, + "?1 !~* ?2", + basicTypeRegistry.resolve( StandardBasicTypes.BOOLEAN )); + } +} diff --git a/dspace-api/src/main/java/org/dspace/util/FrontendUrlService.java b/dspace-api/src/main/java/org/dspace/util/FrontendUrlService.java index a50baf910e..d183ec7eac 100644 --- a/dspace-api/src/main/java/org/dspace/util/FrontendUrlService.java +++ b/dspace-api/src/main/java/org/dspace/util/FrontendUrlService.java @@ -14,6 +14,8 @@ import static org.apache.commons.lang3.StringUtils.lowerCase; import java.util.List; import java.util.Optional; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.content.Bitstream; import org.dspace.content.Item; import org.dspace.core.Context; @@ -22,18 +24,16 @@ import org.dspace.discovery.DiscoverResult; import org.dspace.discovery.SearchService; import org.dspace.discovery.SearchServiceException; import org.dspace.services.ConfigurationService; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; /** - * Service class for generation of front-end urls. + * Service class for generation of front-end URLs. */ @Component public class FrontendUrlService { - private static final Logger log = LoggerFactory.getLogger(FrontendUrlService.class); + private static final Logger log = LogManager.getLogger(); @Autowired private ConfigurationService configurationService; @@ -80,7 +80,8 @@ public class FrontendUrlService { } } } catch (SearchServiceException e) { - log.error("Failed getting entitytype through solr for item " + item.getID() + ": " + e.getMessage()); + log.error("Failed getting entitytype through solr for item {}: {}", + item::getID, e::getMessage); } return Optional.empty(); } diff --git a/dspace-api/src/main/java/org/dspace/util/JpaCriteriaBuilderKit.java b/dspace-api/src/main/java/org/dspace/util/JpaCriteriaBuilderKit.java new file mode 100644 index 0000000000..7f74f0a728 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/util/JpaCriteriaBuilderKit.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.util; + +import jakarta.persistence.criteria.AbstractQuery; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.Root; + +/** + * Data structure containing the required objects to build criteria + * for a JPA query built using the JPA Criteria API. + * The getters match those generated by the JVM when using a record + * so that no API changes will be required when this class gets converted + * into a record when DSpace gets promoted to Java 17 or later. + * @author Jean-François Morin (Université Laval) + */ +// TODO: Convert this data structure into a record when DSpace gets promoted to Java 17 or later +public class JpaCriteriaBuilderKit { + + private CriteriaBuilder criteriaBuilder; + /** Can be a CriteriaQuery as well as a Subquery - both extend AbstractQuery. */ + private AbstractQuery query; + private Root root; + + public JpaCriteriaBuilderKit(CriteriaBuilder criteriaBuilder, AbstractQuery query, + Root root) { + this.criteriaBuilder = criteriaBuilder; + this.query = query; + this.root = root; + } + + public CriteriaBuilder criteriaBuilder() { + return criteriaBuilder; + } + + public AbstractQuery query() { + return query; + } + + public Root root() { + return root; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/util/MultiFormatDateParser.java b/dspace-api/src/main/java/org/dspace/util/MultiFormatDateParser.java index 87dc6d9db8..de3ef92619 100644 --- a/dspace-api/src/main/java/org/dspace/util/MultiFormatDateParser.java +++ b/dspace-api/src/main/java/org/dspace/util/MultiFormatDateParser.java @@ -21,11 +21,11 @@ import java.util.Map.Entry; import java.util.TimeZone; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; -import javax.inject.Inject; +import jakarta.inject.Inject; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.servicemanager.DSpaceKernelInit; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * Attempt to parse date strings in a variety of formats. This uses an external @@ -38,7 +38,7 @@ import org.slf4j.LoggerFactory; * @author mwood */ public class MultiFormatDateParser { - private static final Logger log = LoggerFactory.getLogger(MultiFormatDateParser.class); + private static final Logger log = LogManager.getLogger(); /** * A list of rules, each binding a regular expression to a date format. @@ -70,8 +70,8 @@ public class MultiFormatDateParser { try { pattern = Pattern.compile(rule.getKey(), Pattern.CASE_INSENSITIVE); } catch (PatternSyntaxException ex) { - log.error("Skipping format with unparseable pattern '{}'", - rule.getKey()); + log.error("Skipping format with unparsable pattern '{}'", + rule::getKey); continue; } @@ -80,7 +80,7 @@ public class MultiFormatDateParser { format = new SimpleDateFormat(rule.getValue()); } catch (IllegalArgumentException ex) { log.error("Skipping uninterpretable date format '{}'", - rule.getValue()); + rule::getValue); continue; } format.setCalendar(Calendar.getInstance(UTC_ZONE)); @@ -107,7 +107,7 @@ public class MultiFormatDateParser { } } catch (ParseException ex) { log.info("Date string '{}' matched pattern '{}' but did not parse: {}", - new String[] {dateString, candidate.format.toPattern(), ex.getMessage()}); + () -> dateString, candidate.format::toPattern, ex::getMessage); continue; } return result; diff --git a/dspace-api/src/main/java/org/dspace/util/RawJsonDeserializer.java b/dspace-api/src/main/java/org/dspace/util/RawJsonDeserializer.java new file mode 100644 index 0000000000..baadf0d283 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/util/RawJsonDeserializer.java @@ -0,0 +1,35 @@ +/** + * 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.util; + +import java.io.IOException; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * Extension of {@link JsonDeserializer} that convert a json to a String. + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + * + */ +public class RawJsonDeserializer extends JsonDeserializer { + + @Override + public String deserialize(JsonParser jp, DeserializationContext ctxt) + throws IOException, JsonProcessingException { + + ObjectMapper mapper = (ObjectMapper) jp.getCodec(); + JsonNode node = mapper.readTree(jp); + return mapper.writeValueAsString(node); + } +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/util/SolrImportExport.java b/dspace-api/src/main/java/org/dspace/util/SolrImportExport.java index b23e4396e2..c132af1855 100644 --- a/dspace-api/src/main/java/org/dspace/util/SolrImportExport.java +++ b/dspace-api/src/main/java/org/dspace/util/SolrImportExport.java @@ -293,7 +293,7 @@ public class SolrImportExport { + ") but usable space in export directory is only " + FileUtils.byteCountToDisplaySize(usableExportSpace) + ". Not continuing with reindex, please use the " + DIRECTORY_OPTION - + " option to specify an alternative export directy with sufficient space."); + + " option to specify an alternative export directly with sufficient space."); return; } diff --git a/dspace-api/src/main/java/org/dspace/util/SolrUpgradePre6xStatistics.java b/dspace-api/src/main/java/org/dspace/util/SolrUpgradePre6xStatistics.java index 9342cb8b39..044d9792be 100644 --- a/dspace-api/src/main/java/org/dspace/util/SolrUpgradePre6xStatistics.java +++ b/dspace-api/src/main/java/org/dspace/util/SolrUpgradePre6xStatistics.java @@ -78,7 +78,7 @@ public class SolrUpgradePre6xStatistics { private static final int NUMREC_DEFAULT = 100000; private static final int BATCH_DEFAULT = 10000; - //After processing each batch of updates to SOLR, evaulate if the hibernate cache needs to be cleared + //After processing each batch of updates to SOLR, evaluate if the hibernate cache needs to be cleared private static final int CACHE_LIMIT = 20000; private static final String INDEX_DEFAULT = "statistics"; @@ -112,7 +112,7 @@ public class SolrUpgradePre6xStatistics { //Logger private static final Logger log = LogManager.getLogger(); - //DSpace Servcies + //DSpace Services private ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); protected CommunityService communityService = ContentServiceFactory.getInstance().getCommunityService(); protected CollectionService collectionService = ContentServiceFactory.getInstance().getCollectionService(); @@ -271,7 +271,7 @@ public class SolrUpgradePre6xStatistics { options.addOption(HELP_OPTION, "help", false, "Get help on options for this command."); options.addOption(INDEX_NAME_OPTION, "index-name", true, "The names of the indexes to process. At least one is required (default=statistics)"); - options.addOption(NUMREC_OPTION, "num-rec", true, "Total number of records to update (defaut=100,000)."); + options.addOption(NUMREC_OPTION, "num-rec", true, "Total number of records to update (default=100,000)."); options.addOption(BATCH_OPTION, "batch-size", true, "Number of records to batch update to SOLR at one time (default=10,000)."); return options; @@ -327,7 +327,7 @@ public class SolrUpgradePre6xStatistics { System.out.println(" * This process should be run iteratively over every statistics shard "); System.out.println(" * until there are no remaining records with legacy ids present."); System.out.println(" * This process can be run while the system is in use."); - System.out.println(" * It is likely to take 1 hour/1,000,000 legacy records to be udpated."); + System.out.println(" * It is likely to take 1 hour/1,000,000 legacy records to be updated."); System.out.println(" *"); System.out.println(" * This process will rewrite most solr statistics records and may temporarily double "); System.out.println( @@ -408,7 +408,7 @@ public class SolrUpgradePre6xStatistics { } else if (id == Constants.ITEM) { name = "Item " + s; } else if (id == Constants.BITSTREAM) { - name = "Bistream " + s; + name = "Bitstream " + s; } else { /* * In testing, I discovered some unexpected values in the scopeType field. It @@ -479,7 +479,7 @@ public class SolrUpgradePre6xStatistics { sQ.setRows(batchSize); // Ensure that items are grouped by id - // Sort by id fails due to presense of id and string fields. The ord function + // Sort by id fails due to presence of id and string fields. The ord function // seems to help sQ.addSort("type", SolrQuery.ORDER.desc); sQ.addSort("scopeType", SolrQuery.ORDER.desc); diff --git a/dspace-api/src/main/java/org/dspace/versioning/AbstractVersionProvider.java b/dspace-api/src/main/java/org/dspace/versioning/AbstractVersionProvider.java index 329332d315..1dae5427f8 100644 --- a/dspace-api/src/main/java/org/dspace/versioning/AbstractVersionProvider.java +++ b/dspace-api/src/main/java/org/dspace/versioning/AbstractVersionProvider.java @@ -84,8 +84,8 @@ public abstract class AbstractVersionProvider { // DSpace knows several types of resource policies (see the class // org.dspace.authorize.ResourcePolicy): Submission, Workflow, Custom // and inherited. Submission, Workflow and Inherited policies will be - // set automatically as neccessary. We need to copy the custom policies - // only to preserve customly set policies and embargos (which are + // set automatically as necessary. We need to copy the custom policies + // only to preserve customly set policies and embargoes (which are // realized by custom policies with a start date). List bundlePolicies = authorizeService.findPoliciesByDSOAndType(c, nativeBundle, ResourcePolicy.TYPE_CUSTOM); diff --git a/dspace-api/src/main/java/org/dspace/versioning/DefaultItemVersionProvider.java b/dspace-api/src/main/java/org/dspace/versioning/DefaultItemVersionProvider.java index fa89b34414..c41b985690 100644 --- a/dspace-api/src/main/java/org/dspace/versioning/DefaultItemVersionProvider.java +++ b/dspace-api/src/main/java/org/dspace/versioning/DefaultItemVersionProvider.java @@ -72,7 +72,7 @@ public class DefaultItemVersionProvider extends AbstractVersionProvider implemen if (versionHistoryService.isLastVersion(c, history, versionToDelete) && versioningService.getVersionsByHistory(c, history).size() > 1) { // if a new version gets archived, the old one is set to false. - // we need to do the oposite now, if the old version was previously + // we need to do the opposite now, if the old version was previously // unarchived. If the old version is still archived, the new // version is a WorkspaceItem or WorkflowItem we should skip this, // as unarchiving of previous versions is done only when a newer @@ -116,8 +116,8 @@ public class DefaultItemVersionProvider extends AbstractVersionProvider implemen // DSpace knows several types of resource policies (see the class // org.dspace.authorize.ResourcePolicy): Submission, Workflow, Custom // and inherited. Submission, Workflow and Inherited policies will be - // set automatically as neccessary. We need to copy the custom policies - // only to preserve customly set policies and embargos (which are + // set automatically as necessary. We need to copy the custom policies + // only to preserve customly set policies and embargoes (which are // realized by custom policies with a start date). List policies = authorizeService.findPoliciesByDSOAndType(c, previousItem, ResourcePolicy.TYPE_CUSTOM); diff --git a/dspace-api/src/main/java/org/dspace/versioning/Version.java b/dspace-api/src/main/java/org/dspace/versioning/Version.java index ee5c1c4183..ada857c1cf 100644 --- a/dspace-api/src/main/java/org/dspace/versioning/Version.java +++ b/dspace-api/src/main/java/org/dspace/versioning/Version.java @@ -8,24 +8,25 @@ package org.dspace.versioning; 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.Item; import org.dspace.core.Context; +import org.dspace.core.HibernateProxyHelper; import org.dspace.core.ReloadableEntity; import org.dspace.eperson.EPerson; -import org.hibernate.proxy.HibernateProxyHelper; + /** * @author Fabio Bolognesi (fabio at atmire dot com) diff --git a/dspace-api/src/main/java/org/dspace/versioning/VersionHistory.java b/dspace-api/src/main/java/org/dspace/versioning/VersionHistory.java index 231ccc29d9..f207fe8d4f 100644 --- a/dspace-api/src/main/java/org/dspace/versioning/VersionHistory.java +++ b/dspace-api/src/main/java/org/dspace/versioning/VersionHistory.java @@ -10,21 +10,22 @@ package org.dspace.versioning; import java.util.ArrayList; import java.util.List; import java.util.Objects; -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.OneToMany; -import javax.persistence.OrderBy; -import javax.persistence.SequenceGenerator; -import javax.persistence.Table; +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.OneToMany; +import jakarta.persistence.OrderBy; +import jakarta.persistence.SequenceGenerator; +import jakarta.persistence.Table; import org.apache.commons.collections4.CollectionUtils; import org.dspace.core.Context; +import org.dspace.core.HibernateProxyHelper; import org.dspace.core.ReloadableEntity; -import org.hibernate.proxy.HibernateProxyHelper; + /** diff --git a/dspace-api/src/main/java/org/dspace/versioning/VersioningServiceImpl.java b/dspace-api/src/main/java/org/dspace/versioning/VersioningServiceImpl.java index ece536e81b..b9c3a524a1 100644 --- a/dspace-api/src/main/java/org/dspace/versioning/VersioningServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/versioning/VersioningServiceImpl.java @@ -247,7 +247,7 @@ public class VersioningServiceImpl implements VersioningService { protected int getNextVersionNumer(Context c, VersionHistory vh) throws SQLException { int next = versionDAO.getNextVersionNumber(c, vh); - // check if we have uncommited versions in DSpace's cache + // check if we have uncommitted versions in DSpace's cache if (versionHistoryService.getLatestVersion(c, vh) != null && versionHistoryService.getLatestVersion(c, vh).getVersionNumber() >= next) { next = versionHistoryService.getLatestVersion(c, vh).getVersionNumber() + 1; diff --git a/dspace-api/src/main/java/org/dspace/versioning/dao/impl/VersionDAOImpl.java b/dspace-api/src/main/java/org/dspace/versioning/dao/impl/VersionDAOImpl.java index 0e28e72d07..0ef01b8262 100644 --- a/dspace-api/src/main/java/org/dspace/versioning/dao/impl/VersionDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/versioning/dao/impl/VersionDAOImpl.java @@ -10,11 +10,11 @@ package org.dspace.versioning.dao.impl; import java.sql.SQLException; import java.util.LinkedList; import java.util.List; -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.content.Item; import org.dspace.core.AbstractHibernateDAO; import org.dspace.core.Context; @@ -74,7 +74,7 @@ public class VersionDAOImpl extends AbstractHibernateDAO implements Ver ) ); - List orderList = new LinkedList<>(); + List orderList = new LinkedList<>(); orderList.add(criteriaBuilder.desc(versionRoot.get(Version_.versionNumber))); criteriaQuery.orderBy(orderList); @@ -84,9 +84,9 @@ public class VersionDAOImpl extends AbstractHibernateDAO implements Ver @Override public int countVersionsByHistoryWithItem(Context context, VersionHistory versionHistory) throws SQLException { Query query = createQuery(context, "SELECT count(*) FROM " + Version.class.getSimpleName() - + " WHERE versionhistory_id = (:versionhistoryId)" - + " AND item_id IS NOT NULL"); - query.setParameter("versionhistoryId", versionHistory); + + " WHERE versionHistory = :versionhistory" + + " AND item IS NOT NULL"); + query.setParameter("versionhistory", versionHistory); return count(query); } diff --git a/dspace-api/src/main/java/org/dspace/versioning/dao/impl/VersionHistoryDAOImpl.java b/dspace-api/src/main/java/org/dspace/versioning/dao/impl/VersionHistoryDAOImpl.java index eac78c3e62..b71ef59f2f 100644 --- a/dspace-api/src/main/java/org/dspace/versioning/dao/impl/VersionHistoryDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/versioning/dao/impl/VersionHistoryDAOImpl.java @@ -10,11 +10,11 @@ package org.dspace.versioning.dao.impl; import java.sql.SQLException; import java.util.LinkedList; import java.util.List; -import javax.persistence.criteria.CriteriaBuilder; -import javax.persistence.criteria.CriteriaQuery; -import javax.persistence.criteria.Join; -import javax.persistence.criteria.Root; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Join; +import jakarta.persistence.criteria.Root; import org.dspace.content.Item; import org.dspace.core.AbstractHibernateDAO; import org.dspace.core.Context; @@ -47,7 +47,7 @@ public class VersionHistoryDAOImpl extends AbstractHibernateDAO criteriaQuery.select(versionHistoryRoot); criteriaQuery.where(criteriaBuilder.equal(join.get(Version_.item), item)); - List orderList = new LinkedList<>(); + List orderList = new LinkedList<>(); orderList.add(criteriaBuilder.desc(join.get(Version_.versionNumber))); criteriaQuery.orderBy(orderList); diff --git a/dspace-api/src/main/java/org/dspace/web/ContextUtil.java b/dspace-api/src/main/java/org/dspace/web/ContextUtil.java index 4bdf26c053..a17d05002b 100644 --- a/dspace-api/src/main/java/org/dspace/web/ContextUtil.java +++ b/dspace-api/src/main/java/org/dspace/web/ContextUtil.java @@ -10,10 +10,10 @@ package org.dspace.web; import java.sql.SQLException; import java.util.Enumeration; import java.util.Locale; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.dspace.core.Context; diff --git a/dspace-api/src/main/java/org/dspace/workflow/CurationTaskConfig.java b/dspace-api/src/main/java/org/dspace/workflow/CurationTaskConfig.java index be870ee33e..675fbb03be 100644 --- a/dspace-api/src/main/java/org/dspace/workflow/CurationTaskConfig.java +++ b/dspace-api/src/main/java/org/dspace/workflow/CurationTaskConfig.java @@ -18,14 +18,14 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; -import javax.validation.constraints.NotNull; import javax.xml.XMLConstants; -import javax.xml.bind.JAXBContext; -import javax.xml.bind.JAXBException; -import javax.xml.bind.Unmarshaller; import javax.xml.validation.Schema; import javax.xml.validation.SchemaFactory; +import jakarta.validation.constraints.NotNull; +import jakarta.xml.bind.JAXBContext; +import jakarta.xml.bind.JAXBException; +import jakarta.xml.bind.Unmarshaller; import org.xml.sax.SAXException; /** diff --git a/dspace-api/src/main/java/org/dspace/workflow/FlowStep.java b/dspace-api/src/main/java/org/dspace/workflow/FlowStep.java index b133c51cfb..8709481834 100644 --- a/dspace-api/src/main/java/org/dspace/workflow/FlowStep.java +++ b/dspace-api/src/main/java/org/dspace/workflow/FlowStep.java @@ -9,7 +9,8 @@ package org.dspace.workflow; import java.util.ArrayList; import java.util.List; -import javax.validation.constraints.NotNull; + +import jakarta.validation.constraints.NotNull; /** * Linkage between a workflow step and some {@link org.dspace.curate.CurationTask}s. diff --git a/dspace-api/src/main/java/org/dspace/workflow/Task.java b/dspace-api/src/main/java/org/dspace/workflow/Task.java index 112e7c5585..94e92a6713 100644 --- a/dspace-api/src/main/java/org/dspace/workflow/Task.java +++ b/dspace-api/src/main/java/org/dspace/workflow/Task.java @@ -11,7 +11,8 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import javax.validation.constraints.NotNull; + +import jakarta.validation.constraints.NotNull; /** * An association between a {@link org.dspace.curate.CurationTask curation task} diff --git a/dspace-api/src/main/java/org/dspace/workflow/TaskSet.java b/dspace-api/src/main/java/org/dspace/workflow/TaskSet.java index 951940d5bb..a077bfddbe 100644 --- a/dspace-api/src/main/java/org/dspace/workflow/TaskSet.java +++ b/dspace-api/src/main/java/org/dspace/workflow/TaskSet.java @@ -8,7 +8,8 @@ package org.dspace.workflow; import java.util.List; -import javax.validation.constraints.NotNull; + +import jakarta.validation.constraints.NotNull; /** * A collection of {@link org.dspace.curate.CurationTask curation tasks} to be diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/WorkflowRequirementsServiceImpl.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/WorkflowRequirementsServiceImpl.java index aecdccd55a..82296b38f1 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/WorkflowRequirementsServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/WorkflowRequirementsServiceImpl.java @@ -31,7 +31,7 @@ import org.dspace.xmlworkflow.storedcomponents.service.XmlWorkflowItemService; import org.springframework.beans.factory.annotation.Autowired; /** - * A class that contains utililty methods related to the workflow + * A class that contains utility methods related to the workflow * The adding/removing from claimed users and ensuring that * if multiple users have to perform these steps that a count is kept * so that no more then the allowed user count are allowed to perform their actions diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/WorkflowUtils.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/WorkflowUtils.java index a81bebcd5a..b4e9f3d7f1 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/WorkflowUtils.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/WorkflowUtils.java @@ -15,9 +15,9 @@ import java.util.Date; import java.util.Enumeration; import java.util.LinkedHashMap; import java.util.Map; -import javax.mail.MessagingException; -import javax.servlet.http.HttpServletRequest; +import jakarta.mail.MessagingException; +import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/XmlWorkflowServiceImpl.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/XmlWorkflowServiceImpl.java index faf15d02c6..06ded267d1 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/XmlWorkflowServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/XmlWorkflowServiceImpl.java @@ -18,9 +18,9 @@ import java.util.Locale; import java.util.Map; import java.util.MissingResourceException; import java.util.UUID; -import javax.mail.MessagingException; -import javax.servlet.http.HttpServletRequest; +import jakarta.mail.MessagingException; +import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang.StringUtils; import org.apache.logging.log4j.Logger; @@ -416,7 +416,7 @@ public class XmlWorkflowServiceImpl implements XmlWorkflowService { || currentOutcome.getType() == ActionResult.TYPE.TYPE_SUBMISSION_PAGE) { //We either pressed the cancel button or got an order to return to the submission page, so don't return // an action - //By not returning an action we ensure ourselfs that we go back to the submission page + //By not returning an action we ensure ourselves that we go back to the submission page c.restoreAuthSystemState(); return null; } else if (currentOutcome.getType() == ActionResult.TYPE.TYPE_OUTCOME) { @@ -1191,14 +1191,20 @@ public class XmlWorkflowServiceImpl implements XmlWorkflowService { // Create provenance description StringBuffer provmessage = new StringBuffer(); - if (myitem.getSubmitter() != null) { + //behavior to generate provenance message, if set true, personal data (e.g. email) of submitter will be hidden + //default value false, personal data of submitter will be shown in provenance message + String isProvenancePrivacyActiveProperty = + configurationService.getProperty("metadata.privacy.dc.description.provenance", "false"); + boolean isProvenancePrivacyActive = Boolean.parseBoolean(isProvenancePrivacyActiveProperty); + + if (myitem.getSubmitter() != null && !isProvenancePrivacyActive) { provmessage.append("Submitted by ").append(myitem.getSubmitter().getFullName()) - .append(" (").append(myitem.getSubmitter().getEmail()).append(") on ") - .append(now.toString()); + .append(" (").append(myitem.getSubmitter().getEmail()).append(") on ") + .append(now.toString()); } else { // else, null submitter - provmessage.append("Submitted by unknown (probably automated) on") - .append(now.toString()); + provmessage.append("Submitted by unknown (probably automated or submitter hidden) on ") + .append(now.toString()); } if (action != null) { provmessage.append(" workflow start=").append(action.getProvenanceStartId()).append("\n"); diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/migration/RestartWorkflow.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/migration/RestartWorkflow.java index 8490107518..c780d66a0c 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/migration/RestartWorkflow.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/migration/RestartWorkflow.java @@ -28,7 +28,7 @@ import org.dspace.workflow.WorkflowService; import org.dspace.workflow.factory.WorkflowServiceFactory; /** - * A utility class that will send all the worklfow items + * A utility class that will send all the workflow items * back to their submitters * * @author Bram De Schouwer (bram.deschouwer at dot com) diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/service/XmlWorkflowService.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/service/XmlWorkflowService.java index 6b03803d8a..6dce02de83 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/service/XmlWorkflowService.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/service/XmlWorkflowService.java @@ -10,9 +10,9 @@ package org.dspace.xmlworkflow.service; import java.io.IOException; import java.sql.SQLException; import java.util.List; -import javax.mail.MessagingException; -import javax.servlet.http.HttpServletRequest; +import jakarta.mail.MessagingException; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Item; import org.dspace.core.Context; diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/Action.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/Action.java index 1cfa33b121..f16ef419e7 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/Action.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/Action.java @@ -11,8 +11,8 @@ import java.io.IOException; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.authorize.AuthorizeException; import org.dspace.content.DCDate; import org.dspace.content.MetadataSchemaEnum; @@ -229,7 +229,7 @@ public abstract class Action { * Adds info in the metadata field dc.description.provenance about item being approved containing in which step * it was approved, which user approved it and the time * - * @param c DSpace contect + * @param c DSpace contact * @param wfi Workflow item we're adding workflow accept provenance on */ public void addApprovedProvenance(Context c, XmlWorkflowItem wfi) throws SQLException, AuthorizeException { diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/AcceptEditRejectAction.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/AcceptEditRejectAction.java index 67b400c659..b55df1a5db 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/AcceptEditRejectAction.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/AcceptEditRejectAction.java @@ -11,8 +11,8 @@ import java.io.IOException; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.util.Util; import org.dspace.authorize.AuthorizeException; import org.dspace.core.Context; diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/FinalEditAction.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/FinalEditAction.java index 9b83be5d7b..b287d0c3d1 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/FinalEditAction.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/FinalEditAction.java @@ -10,8 +10,8 @@ package org.dspace.xmlworkflow.state.actions.processingaction; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.util.Util; import org.dspace.authorize.AuthorizeException; import org.dspace.core.Context; diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ProcessingAction.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ProcessingAction.java index 7a1c62adbd..6a41f40398 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ProcessingAction.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ProcessingAction.java @@ -9,8 +9,8 @@ package org.dspace.xmlworkflow.state.actions.processingaction; import java.io.IOException; import java.sql.SQLException; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.authorize.AuthorizeException; import org.dspace.content.service.ItemService; import org.dspace.core.Context; diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ReviewAction.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ReviewAction.java index bd74ab3c71..ad2cbe06b1 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ReviewAction.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ReviewAction.java @@ -11,8 +11,8 @@ import java.io.IOException; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.util.Util; import org.dspace.authorize.AuthorizeException; import org.dspace.core.Context; diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ScoreEvaluationAction.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ScoreEvaluationAction.java index 16d35b3668..c8fd3ecb66 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ScoreEvaluationAction.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ScoreEvaluationAction.java @@ -14,8 +14,8 @@ import java.io.IOException; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Item; import org.dspace.content.MetadataSchemaEnum; diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ScoreReviewAction.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ScoreReviewAction.java index 43a3decacc..a8ed4fd3da 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ScoreReviewAction.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ScoreReviewAction.java @@ -11,8 +11,8 @@ import java.sql.SQLException; import java.util.Arrays; import java.util.Collections; import java.util.List; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.lang.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SelectReviewerAction.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SelectReviewerAction.java index 0e8ab40a52..4adeb127ea 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SelectReviewerAction.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SelectReviewerAction.java @@ -12,9 +12,9 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.UUID; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; +import jakarta.annotation.Nullable; +import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SingleUserReviewAction.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SingleUserReviewAction.java index b3fe896ace..64e0957b65 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SingleUserReviewAction.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SingleUserReviewAction.java @@ -11,8 +11,8 @@ import java.io.IOException; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.app.util.Util; diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/userassignment/AssignAction.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/userassignment/AssignAction.java index e837a8a893..4d645ae6a5 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/userassignment/AssignAction.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/userassignment/AssignAction.java @@ -10,8 +10,8 @@ package org.dspace.xmlworkflow.state.actions.userassignment; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.core.Context; import org.dspace.xmlworkflow.RoleMembers; import org.dspace.xmlworkflow.WorkflowConfigurationException; diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/userassignment/AssignOriginalSubmitterAction.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/userassignment/AssignOriginalSubmitterAction.java index 0cd82fe770..79e2ffc900 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/userassignment/AssignOriginalSubmitterAction.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/userassignment/AssignOriginalSubmitterAction.java @@ -12,9 +12,9 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import javax.mail.MessagingException; -import javax.servlet.http.HttpServletRequest; +import jakarta.mail.MessagingException; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.authorize.AuthorizeException; import org.dspace.core.Context; import org.dspace.core.LogHelper; diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/userassignment/AutoAssignAction.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/userassignment/AutoAssignAction.java index 401a7c506b..cf3ceff575 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/userassignment/AutoAssignAction.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/userassignment/AutoAssignAction.java @@ -11,8 +11,8 @@ import java.io.IOException; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.apache.logging.log4j.Logger; import org.dspace.authorize.AuthorizeException; import org.dspace.core.Context; diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/userassignment/ClaimAction.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/userassignment/ClaimAction.java index 21fcf6f309..385d6feea6 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/userassignment/ClaimAction.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/userassignment/ClaimAction.java @@ -11,9 +11,9 @@ import java.io.IOException; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; -import javax.mail.MessagingException; -import javax.servlet.http.HttpServletRequest; +import jakarta.mail.MessagingException; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.authorize.AuthorizeException; import org.dspace.core.Context; import org.dspace.core.LogHelper; diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/userassignment/InheritUsersAction.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/userassignment/InheritUsersAction.java index 1ffce1afdb..25be8da25c 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/userassignment/InheritUsersAction.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/userassignment/InheritUsersAction.java @@ -10,8 +10,8 @@ package org.dspace.xmlworkflow.state.actions.userassignment; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.core.Context; import org.dspace.xmlworkflow.RoleMembers; import org.dspace.xmlworkflow.WorkflowConfigurationException; diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/userassignment/NoUserSelectionAction.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/userassignment/NoUserSelectionAction.java index d23a98cedb..9606892554 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/userassignment/NoUserSelectionAction.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/userassignment/NoUserSelectionAction.java @@ -9,8 +9,8 @@ package org.dspace.xmlworkflow.state.actions.userassignment; import java.util.ArrayList; import java.util.List; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.core.Context; import org.dspace.xmlworkflow.RoleMembers; import org.dspace.xmlworkflow.state.Step; diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/userassignment/UserSelectionAction.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/userassignment/UserSelectionAction.java index 6a0d3e9ca6..f405242aab 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/userassignment/UserSelectionAction.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/userassignment/UserSelectionAction.java @@ -9,8 +9,8 @@ package org.dspace.xmlworkflow.state.actions.userassignment; import java.io.IOException; import java.sql.SQLException; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.apache.logging.log4j.Logger; import org.dspace.authorize.AuthorizeException; import org.dspace.core.Context; @@ -88,7 +88,7 @@ public abstract class UserSelectionAction extends Action { throws WorkflowConfigurationException, SQLException; /** - * A boolean indicating wether or not the task pool is used for this type of user selection + * A boolean indicating whether or not the task pool is used for this type of user selection * * @return a boolean */ diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/ClaimedTask.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/ClaimedTask.java index 8f4794cb3b..adaf0ec06e 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/ClaimedTask.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/ClaimedTask.java @@ -7,17 +7,16 @@ */ package org.dspace.xmlworkflow.storedcomponents; -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 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 org.dspace.core.Context; import org.dspace.core.ReloadableEntity; import org.dspace.eperson.EPerson; @@ -44,18 +43,12 @@ public class ClaimedTask implements ReloadableEntity { @JoinColumn(name = "workflowitem_id") private XmlWorkflowItem workflowItem; - // @Column(name = "workflow_id") -// @Lob @Column(name = "workflow_id", columnDefinition = "text") private String workflowId; - // @Column(name = "step_id") -// @Lob @Column(name = "step_id", columnDefinition = "text") private String stepId; - // @Column(name = "action_id") -// @Lob @Column(name = "action_id", columnDefinition = "text") private String actionId; diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/CollectionRole.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/CollectionRole.java index c9a7995e03..4cbaf1c411 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/CollectionRole.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/CollectionRole.java @@ -8,17 +8,17 @@ package org.dspace.xmlworkflow.storedcomponents; import java.sql.SQLException; -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 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 org.dspace.content.Collection; import org.dspace.core.Context; import org.dspace.core.ReloadableEntity; @@ -44,8 +44,6 @@ public class CollectionRole implements ReloadableEntity { @SequenceGenerator(name = "cwf_collectionrole_seq", sequenceName = "cwf_collectionrole_seq", allocationSize = 1) private Integer id; - // @Column(name = "role_id") - // @Lob @Column(name = "role_id", columnDefinition = "text") private String roleId; diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/InProgressUser.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/InProgressUser.java index efbd26bde5..38f410566d 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/InProgressUser.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/InProgressUser.java @@ -7,17 +7,16 @@ */ package org.dspace.xmlworkflow.storedcomponents; -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 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 org.dspace.core.Context; import org.dspace.core.ReloadableEntity; import org.dspace.eperson.EPerson; diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/PoolTask.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/PoolTask.java index 9cfc9ea068..0c6b01f5c1 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/PoolTask.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/PoolTask.java @@ -7,18 +7,17 @@ */ package org.dspace.xmlworkflow.storedcomponents; -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.OneToOne; -import javax.persistence.SequenceGenerator; -import javax.persistence.Table; - +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.OneToOne; +import jakarta.persistence.SequenceGenerator; +import jakarta.persistence.Table; import org.dspace.core.Context; import org.dspace.core.ReloadableEntity; import org.dspace.eperson.EPerson; @@ -46,18 +45,12 @@ public class PoolTask implements ReloadableEntity { @JoinColumn(name = "workflowitem_id") private XmlWorkflowItem workflowItem; - // @Column(name = "workflow_id") -// @Lob @Column(name = "workflow_id", columnDefinition = "text") private String workflowId; - // @Column(name = "step_id") -// @Lob @Column(name = "step_id", columnDefinition = "text") private String stepId; - // @Column(name = "action_id") -// @Lob @Column(name = "action_id", columnDefinition = "text") private String actionId; diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/PoolTaskServiceImpl.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/PoolTaskServiceImpl.java index fb673725e1..d3c8f6334d 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/PoolTaskServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/PoolTaskServiceImpl.java @@ -13,6 +13,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; +import java.util.Optional; import java.util.Set; import org.apache.commons.collections4.CollectionUtils; @@ -100,12 +101,17 @@ public class PoolTaskServiceImpl implements PoolTaskService { //If the user does not have a claimedtask yet, see whether one of the groups of the user has pooltasks //for this workflow item Set groups = groupService.allMemberGroupsSet(context, ePerson); - for (Group group : groups) { - poolTask = poolTaskDAO.findByWorkflowItemAndGroup(context, group, workflowItem); - if (poolTask != null) { - return poolTask; - } + List generalTasks = poolTaskDAO.findByWorkflowItem(context, workflowItem); + Optional firstClaimedTask = groups.stream() + .flatMap(group -> generalTasks.stream() + .filter(f -> f.getGroup().getID().equals(group.getID())) + .findFirst() + .stream()) + .findFirst(); + + if (firstClaimedTask.isPresent()) { + return firstClaimedTask.get(); } } } diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/WorkflowItemRole.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/WorkflowItemRole.java index cc6df9731b..a0d46c27d1 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/WorkflowItemRole.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/WorkflowItemRole.java @@ -8,17 +8,17 @@ package org.dspace.xmlworkflow.storedcomponents; import java.sql.SQLException; -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.OneToOne; -import javax.persistence.SequenceGenerator; -import javax.persistence.Table; +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.OneToOne; +import jakarta.persistence.SequenceGenerator; +import jakarta.persistence.Table; import org.dspace.core.Context; import org.dspace.core.ReloadableEntity; import org.dspace.eperson.EPerson; @@ -44,8 +44,6 @@ public class WorkflowItemRole implements ReloadableEntity { @SequenceGenerator(name = "cwf_workflowitemrole_seq", sequenceName = "cwf_workflowitemrole_seq", allocationSize = 1) private Integer id; - // @Column(name = "role_id") -// @Lob @Column(name = "role_id", columnDefinition = "text") private String roleId; diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/XmlWorkflowItem.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/XmlWorkflowItem.java index f6ffe6049a..529a35bda3 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/XmlWorkflowItem.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/XmlWorkflowItem.java @@ -7,18 +7,17 @@ */ package org.dspace.xmlworkflow.storedcomponents; -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.OneToOne; -import javax.persistence.SequenceGenerator; -import javax.persistence.Table; - +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.OneToOne; +import jakarta.persistence.SequenceGenerator; +import jakarta.persistence.Table; import org.dspace.content.Collection; import org.dspace.content.Item; import org.dspace.core.Context; diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/dao/impl/ClaimedTaskDAOImpl.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/dao/impl/ClaimedTaskDAOImpl.java index 956a4648c5..70fe74de8a 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/dao/impl/ClaimedTaskDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/dao/impl/ClaimedTaskDAOImpl.java @@ -9,10 +9,10 @@ package org.dspace.xmlworkflow.storedcomponents.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.core.AbstractHibernateDAO; import org.dspace.core.Context; import org.dspace.eperson.EPerson; diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/dao/impl/CollectionRoleDAOImpl.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/dao/impl/CollectionRoleDAOImpl.java index b3cd32c74f..5c19fd5a8e 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/dao/impl/CollectionRoleDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/dao/impl/CollectionRoleDAOImpl.java @@ -9,11 +9,11 @@ package org.dspace.xmlworkflow.storedcomponents.dao.impl; import java.sql.SQLException; import java.util.List; -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.content.Collection; import org.dspace.core.AbstractHibernateDAO; import org.dspace.core.Context; diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/dao/impl/InProgressUserDAOImpl.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/dao/impl/InProgressUserDAOImpl.java index 783d403c05..a02a947e2c 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/dao/impl/InProgressUserDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/dao/impl/InProgressUserDAOImpl.java @@ -9,10 +9,10 @@ package org.dspace.xmlworkflow.storedcomponents.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.core.AbstractHibernateDAO; import org.dspace.core.Context; import org.dspace.eperson.EPerson; @@ -78,7 +78,7 @@ public class InProgressUserDAOImpl extends AbstractHibernateDAO CriteriaQuery criteriaQuery = criteriaBuilder.createQuery(Long.class); Root inProgressUserRoot = criteriaQuery.from(InProgressUser.class); - + criteriaQuery.select(criteriaBuilder.count(inProgressUserRoot)); criteriaQuery.where(criteriaBuilder.and( criteriaBuilder.equal(inProgressUserRoot.get(InProgressUser_.workflowItem), workflowItem), criteriaBuilder.equal(inProgressUserRoot.get(InProgressUser_.finished), false) @@ -94,7 +94,7 @@ public class InProgressUserDAOImpl extends AbstractHibernateDAO CriteriaQuery criteriaQuery = criteriaBuilder.createQuery(Long.class); Root inProgressUserRoot = criteriaQuery.from(InProgressUser.class); - + criteriaQuery.select(criteriaBuilder.count(inProgressUserRoot)); criteriaQuery.where(criteriaBuilder.and( criteriaBuilder.equal(inProgressUserRoot.get(InProgressUser_.workflowItem), workflowItem), criteriaBuilder.equal(inProgressUserRoot.get(InProgressUser_.finished), true) diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/dao/impl/PoolTaskDAOImpl.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/dao/impl/PoolTaskDAOImpl.java index 0857a325b5..2e95c2f9d0 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/dao/impl/PoolTaskDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/dao/impl/PoolTaskDAOImpl.java @@ -9,10 +9,10 @@ package org.dspace.xmlworkflow.storedcomponents.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.core.AbstractHibernateDAO; import org.dspace.core.Context; import org.dspace.eperson.EPerson; diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/dao/impl/WorkflowItemRoleDAOImpl.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/dao/impl/WorkflowItemRoleDAOImpl.java index fdc2413b5f..6dc2fc9601 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/dao/impl/WorkflowItemRoleDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/dao/impl/WorkflowItemRoleDAOImpl.java @@ -9,10 +9,10 @@ package org.dspace.xmlworkflow.storedcomponents.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.core.AbstractHibernateDAO; import org.dspace.core.Context; import org.dspace.eperson.EPerson; diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/dao/impl/XmlWorkflowItemDAOImpl.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/dao/impl/XmlWorkflowItemDAOImpl.java index 659a2123d9..8994efae1a 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/dao/impl/XmlWorkflowItemDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/dao/impl/XmlWorkflowItemDAOImpl.java @@ -9,11 +9,11 @@ package org.dspace.xmlworkflow.storedcomponents.dao.impl; import java.sql.SQLException; import java.util.List; -import javax.persistence.criteria.CriteriaBuilder; -import javax.persistence.criteria.CriteriaQuery; -import javax.persistence.criteria.Join; -import javax.persistence.criteria.Root; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Join; +import jakarta.persistence.criteria.Root; import org.dspace.content.Collection; import org.dspace.content.Item; import org.dspace.content.Item_; @@ -66,12 +66,11 @@ public class XmlWorkflowItemDAOImpl extends AbstractHibernateDAO criteriaQuery = criteriaBuilder.createQuery(Long.class); Root xmlWorkflowItemRoot = criteriaQuery.from(XmlWorkflowItem.class); + criteriaQuery.select(criteriaBuilder.count(xmlWorkflowItemRoot)); if (collection != null) { criteriaQuery.where(criteriaBuilder.equal(xmlWorkflowItemRoot.get(XmlWorkflowItem_.collection), collection)); @@ -109,7 +108,8 @@ public class XmlWorkflowItemDAOImpl extends AbstractHibernateDAO criteriaQuery = criteriaBuilder.createQuery(Long.class); Root xmlWorkflowItemRoot = criteriaQuery.from(XmlWorkflowItem.class); - Join join = xmlWorkflowItemRoot.join("item"); + criteriaQuery.select(criteriaBuilder.count(xmlWorkflowItemRoot)); + Join join = xmlWorkflowItemRoot.join(XmlWorkflowItem_.item); criteriaQuery.where(criteriaBuilder.equal(join.get(Item_.submitter), ep)); return count(context, criteriaQuery, criteriaBuilder, xmlWorkflowItemRoot); } diff --git a/dspace-api/src/main/resources/Messages.properties b/dspace-api/src/main/resources/Messages.properties index efbbeedde0..3304e8ce8d 100644 --- a/dspace-api/src/main/resources/Messages.properties +++ b/dspace-api/src/main/resources/Messages.properties @@ -50,8 +50,6 @@ metadata.bitstream.iiif-virtual.mimetype = Mime Type metadata.bitstream.iiif-virtual.bytes = File size metadata.bitstream.iiif-virtual.checksum = Checksum -org.dspace.app.itemexport.no-result = The DSpaceObject that you specified has no items. -org.dspace.app.util.SyndicationFeed.no-description = No Description org.dspace.checker.ResultsLogger.bitstream-format = Bitstream format org.dspace.checker.ResultsLogger.bitstream-found = Bitstream found org.dspace.checker.ResultsLogger.bitstream-id = Bitstream ID @@ -99,9 +97,9 @@ org.dspace.checker.SimpleReporterImpl.result org.dspace.checker.SimpleReporterImpl.size = Size org.dspace.checker.SimpleReporterImpl.source = Source org.dspace.checker.SimpleReporterImpl.store-number = Store Number -org.dspace.checker.SimpleReporterImpl.unchecked-bitstream-report = The following is a UN-CHECKED BITSTREAM REPORT report for +org.dspace.checker.SimpleReporterImpl.unchecked-bitstream-report = The following is a UN-CHECKED BITSTREAM report for + org.dspace.content.untitled = Untitled -org.dspace.eperson.X509Authentication.title = Enter DSpace using Web Certificate org.dspace.eperson.Subscribe.authors = Authors: org.dspace.eperson.Subscribe.id = ID: org.dspace.eperson.Subscribe.new-items = New Items: @@ -109,11 +107,10 @@ org.dspace.eperson.Subscribe.title org.dspace.statistics.util.LocationUtils.unknown-continent = Unknown Continent org.dspace.statistics.util.LocationUtils.unknown-country = Unknown Country org.dspace.xmlworkflow.XMLWorkflowService.untitled = Untitled -# used by discovery (standard sort index _sort) -search.sort-by.dc.title_sort = Title -# used by discovery (date sort index _dt) -search.sort-by.dc.date.issued_dt = Issue Date + +org.dspace.app.itemexport.no-result = The DSpace object that you specified has no items. org.dspace.app.requestitem.RequestItemHelpdeskStrategy.helpdeskname = Help Desk +org.dspace.app.util.SyndicationFeed.no-description = No Description # User exposed REST API error messages org.dspace.app.rest.exception.RESTEmptyWorkflowGroupException.message = Refused to delete user {0} because it is the only member of the \ diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V8.0_2023.08.07__qaevent_processed.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V8.0_2023.08.07__qaevent_processed.sql new file mode 100644 index 0000000000..467de85f85 --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V8.0_2023.08.07__qaevent_processed.sql @@ -0,0 +1,16 @@ +-- +-- 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/ +-- + +CREATE TABLE qaevent_processed ( + qaevent_id VARCHAR(255) NOT NULL, + qaevent_timestamp TIMESTAMP NULL, + eperson_uuid UUID NULL REFERENCES eperson(uuid), + item_uuid uuid NOT NULL REFERENCES item(uuid) +); + +CREATE INDEX item_uuid_idx ON qaevent_processed(item_uuid); diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V8.0_2024.02.14__ldn_tables.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V8.0_2024.02.14__ldn_tables.sql new file mode 100644 index 0000000000..f5ea59254f --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V8.0_2024.02.14__ldn_tables.sql @@ -0,0 +1,90 @@ +-- +-- 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/ +-- + +----------------------------------------------------------------------------------- +-- CREATE notifyservice table +----------------------------------------------------------------------------------- + + +CREATE SEQUENCE if NOT EXISTS notifyservice_id_seq; + +CREATE TABLE notifyservice ( + id INTEGER PRIMARY KEY, + name VARCHAR(255), + description TEXT, + url VARCHAR(255), + ldn_url VARCHAR(255), + enabled BOOLEAN NOT NULL, + score NUMERIC(6, 5), + lower_ip VARCHAR(45), + upper_ip VARCHAR(45), + CONSTRAINT ldn_url_unique UNIQUE (ldn_url) +); + +----------------------------------------------------------------------------------- +-- CREATE notifyservice_inbound_pattern_id_seq table +----------------------------------------------------------------------------------- + +CREATE SEQUENCE if NOT EXISTS notifyservice_inbound_pattern_id_seq; + +CREATE TABLE notifyservice_inbound_pattern ( + id INTEGER PRIMARY KEY, + service_id INTEGER REFERENCES notifyservice(id) ON DELETE CASCADE, + pattern VARCHAR(255), + constraint_name VARCHAR(255), + automatic BOOLEAN +); + +CREATE INDEX notifyservice_inbound_idx ON notifyservice_inbound_pattern (service_id); + + +------------------------------------------------------------------------------- +-- Table to store LDN messages +------------------------------------------------------------------------------- + +CREATE TABLE ldn_message +( + id VARCHAR(255) PRIMARY KEY, + object uuid, + message TEXT, + type VARCHAR(255), + origin INTEGER, + target INTEGER, + inReplyTo VARCHAR(255), + context uuid, + activity_stream_type VARCHAR(255), + coar_notify_type VARCHAR(255), + queue_status INTEGER DEFAULT NULL, + queue_attempts INTEGER DEFAULT 0, + queue_last_start_time TIMESTAMP, + queue_timeout TIMESTAMP, + source_ip VARCHAR(45), + FOREIGN KEY (object) REFERENCES dspaceobject (uuid) ON DELETE SET NULL, + FOREIGN KEY (context) REFERENCES dspaceobject (uuid) ON DELETE SET NULL, + FOREIGN KEY (origin) REFERENCES notifyservice (id) ON DELETE SET NULL, + FOREIGN KEY (target) REFERENCES notifyservice (id) ON DELETE SET NULL, + FOREIGN KEY (inReplyTo) REFERENCES ldn_message (id) ON DELETE SET NULL +); + + +------------------------------------------------------------------------------- +-- Table to store notify patterns that will be triggered +------------------------------------------------------------------------------- + +CREATE SEQUENCE if NOT EXISTS notifypatterns_to_trigger_id_seq; + +CREATE TABLE notifypatterns_to_trigger +( + id INTEGER PRIMARY KEY, + item_id UUID REFERENCES Item(uuid) ON DELETE CASCADE, + service_id INTEGER REFERENCES notifyservice(id) ON DELETE CASCADE, + pattern VARCHAR(255) +); + +CREATE INDEX notifypatterns_to_trigger_item_idx ON notifypatterns_to_trigger (item_id); +CREATE INDEX notifypatterns_to_trigger_service_idx ON notifypatterns_to_trigger (service_id); diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V8.0_2023.08.07__qaevent_processed.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V8.0_2023.08.07__qaevent_processed.sql new file mode 100644 index 0000000000..5c3f0fac73 --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V8.0_2023.08.07__qaevent_processed.sql @@ -0,0 +1,19 @@ +-- +-- 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/ +-- + +CREATE TABLE qaevent_processed ( + qaevent_id VARCHAR(255) NOT NULL, + qaevent_timestamp TIMESTAMP NULL, + eperson_uuid UUID NULL, + item_uuid UUID NULL, + CONSTRAINT qaevent_pk PRIMARY KEY (qaevent_id), + CONSTRAINT eperson_uuid_fkey FOREIGN KEY (eperson_uuid) REFERENCES eperson (uuid), + CONSTRAINT item_uuid_fkey FOREIGN KEY (item_uuid) REFERENCES item (uuid) +); + +CREATE INDEX item_uuid_idx ON qaevent_processed(item_uuid); diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V8.0_2024.02.14__ldn_tables.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V8.0_2024.02.14__ldn_tables.sql new file mode 100644 index 0000000000..f5ea59254f --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V8.0_2024.02.14__ldn_tables.sql @@ -0,0 +1,90 @@ +-- +-- 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/ +-- + +----------------------------------------------------------------------------------- +-- CREATE notifyservice table +----------------------------------------------------------------------------------- + + +CREATE SEQUENCE if NOT EXISTS notifyservice_id_seq; + +CREATE TABLE notifyservice ( + id INTEGER PRIMARY KEY, + name VARCHAR(255), + description TEXT, + url VARCHAR(255), + ldn_url VARCHAR(255), + enabled BOOLEAN NOT NULL, + score NUMERIC(6, 5), + lower_ip VARCHAR(45), + upper_ip VARCHAR(45), + CONSTRAINT ldn_url_unique UNIQUE (ldn_url) +); + +----------------------------------------------------------------------------------- +-- CREATE notifyservice_inbound_pattern_id_seq table +----------------------------------------------------------------------------------- + +CREATE SEQUENCE if NOT EXISTS notifyservice_inbound_pattern_id_seq; + +CREATE TABLE notifyservice_inbound_pattern ( + id INTEGER PRIMARY KEY, + service_id INTEGER REFERENCES notifyservice(id) ON DELETE CASCADE, + pattern VARCHAR(255), + constraint_name VARCHAR(255), + automatic BOOLEAN +); + +CREATE INDEX notifyservice_inbound_idx ON notifyservice_inbound_pattern (service_id); + + +------------------------------------------------------------------------------- +-- Table to store LDN messages +------------------------------------------------------------------------------- + +CREATE TABLE ldn_message +( + id VARCHAR(255) PRIMARY KEY, + object uuid, + message TEXT, + type VARCHAR(255), + origin INTEGER, + target INTEGER, + inReplyTo VARCHAR(255), + context uuid, + activity_stream_type VARCHAR(255), + coar_notify_type VARCHAR(255), + queue_status INTEGER DEFAULT NULL, + queue_attempts INTEGER DEFAULT 0, + queue_last_start_time TIMESTAMP, + queue_timeout TIMESTAMP, + source_ip VARCHAR(45), + FOREIGN KEY (object) REFERENCES dspaceobject (uuid) ON DELETE SET NULL, + FOREIGN KEY (context) REFERENCES dspaceobject (uuid) ON DELETE SET NULL, + FOREIGN KEY (origin) REFERENCES notifyservice (id) ON DELETE SET NULL, + FOREIGN KEY (target) REFERENCES notifyservice (id) ON DELETE SET NULL, + FOREIGN KEY (inReplyTo) REFERENCES ldn_message (id) ON DELETE SET NULL +); + + +------------------------------------------------------------------------------- +-- Table to store notify patterns that will be triggered +------------------------------------------------------------------------------- + +CREATE SEQUENCE if NOT EXISTS notifypatterns_to_trigger_id_seq; + +CREATE TABLE notifypatterns_to_trigger +( + id INTEGER PRIMARY KEY, + item_id UUID REFERENCES Item(uuid) ON DELETE CASCADE, + service_id INTEGER REFERENCES notifyservice(id) ON DELETE CASCADE, + pattern VARCHAR(255) +); + +CREATE INDEX notifypatterns_to_trigger_item_idx ON notifypatterns_to_trigger (item_id); +CREATE INDEX notifypatterns_to_trigger_service_idx ON notifypatterns_to_trigger (service_id); diff --git a/dspace-api/src/main/resources/org/dspace/workflow/workflow-curation.xsd b/dspace-api/src/main/resources/org/dspace/workflow/workflow-curation.xsd index 184ee4bfb6..3c56e2a323 100644 --- a/dspace-api/src/main/resources/org/dspace/workflow/workflow-curation.xsd +++ b/dspace-api/src/main/resources/org/dspace/workflow/workflow-curation.xsd @@ -11,9 +11,9 @@ + jaxb:version='3.0'> Workflow curation enables curation tasks to be assigned to diff --git a/dspace-api/src/main/resources/spring/spring-dspace-addon-import-services.xml b/dspace-api/src/main/resources/spring/spring-dspace-addon-import-services.xml index 6b0ef3e9b9..f7943fb232 100644 --- a/dspace-api/src/main/resources/spring/spring-dspace-addon-import-services.xml +++ b/dspace-api/src/main/resources/spring/spring-dspace-addon-import-services.xml @@ -30,10 +30,24 @@ + + + + + + + + + + doi + + + @@ -123,6 +137,20 @@ + + + + + + + + + + + @@ -150,6 +178,12 @@ + + + + + + diff --git a/dspace-api/src/main/resources/spring/spring-dspace-addon-suggestion-services.xml b/dspace-api/src/main/resources/spring/spring-dspace-addon-suggestion-services.xml new file mode 100644 index 0000000000..fb720137c4 --- /dev/null +++ b/dspace-api/src/main/resources/spring/spring-dspace-addon-suggestion-services.xml @@ -0,0 +1,23 @@ + + + + + + + + diff --git a/dspace-api/src/test/data/dspaceFolder/config/item-submission.xml b/dspace-api/src/test/data/dspaceFolder/config/item-submission.xml index 2b4cee0449..feb3c7c12b 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/item-submission.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/item-submission.xml @@ -24,8 +24,11 @@ + + + @@ -179,6 +182,19 @@ submission + + + submit.progressbar.duplicates + org.dspace.app.rest.submit.step.DuplicateDetectionStep + duplicates + + + + submit.progressbar.coarnotify + org.dspace.app.rest.submit.step.NotifyStep + coarnotify + + @@ -211,6 +227,9 @@ + + + @@ -259,6 +278,14 @@ + + + + + + + + @@ -267,6 +294,12 @@ + + + + + + diff --git a/dspace-api/src/test/data/dspaceFolder/config/local.cfg b/dspace-api/src/test/data/dspaceFolder/config/local.cfg index 35ed8a235b..b44f319a35 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/local.cfg +++ b/dspace-api/src/test/data/dspaceFolder/config/local.cfg @@ -95,14 +95,14 @@ loglevel.dspace = INFO # IIIF TEST SETTINGS # ######################## iiif.enabled = true -event.dispatcher.default.consumers = versioning, discovery, eperson, orcidqueue, iiif +event.dispatcher.default.consumers = versioning, discovery, eperson, orcidqueue, iiif, qaeventsdelete, ldnmessage ########################################### # CUSTOM UNIT / INTEGRATION TEST SETTINGS # ########################################### # custom dispatcher to be used by dspace-api IT that doesn't need SOLR event.dispatcher.exclude-discovery.class = org.dspace.event.BasicDispatcher -event.dispatcher.exclude-discovery.consumers = versioning, eperson +event.dispatcher.exclude-discovery.consumers = versioning, eperson, qaeventsdelete, ldnmessage # Configure authority control for Unit Testing (in DSpaceControlledVocabularyTest) # (This overrides default, commented out settings in dspace.cfg) @@ -174,3 +174,21 @@ authority.controlled.dspace.object.owner = true # Configuration required for thorough testing of browse links webui.browse.link.1 = author:dc.contributor.* webui.browse.link.2 = subject:dc.subject.* + +# Enable duplicate detection for tests +duplicate.enable = true + +########################################### +# LDN CONFIGURATIONS # +########################################### +ldn.enabled = true +qaevents.enabled = true +ldn.ip-range.enabled = true +ldn.notify.inbox.block-untrusted = true +ldn.notify.inbox.block-untrusted-ip = true + +########################################### +# ERROR LOGGING # +########################################### +# Log full stacktrace of other common 4xx errors (for easier debugging of these errors in tests) +logging.server.include-stacktrace-for-httpcode = 422, 400 \ No newline at end of file diff --git a/dspace-api/src/test/data/dspaceFolder/config/spring/api/external-openaire.xml b/dspace-api/src/test/data/dspaceFolder/config/spring/api/external-openaire.xml index f1e6c30d13..8a5277ab2d 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/spring/api/external-openaire.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/spring/api/external-openaire.xml @@ -7,7 +7,7 @@ http://www.springframework.org/schema/util/spring-util.xsd" default-lazy-init="true"> - + @@ -15,10 +15,10 @@ - - + + diff --git a/dspace-api/src/test/data/dspaceFolder/config/spring/api/external-services.xml b/dspace-api/src/test/data/dspaceFolder/config/spring/api/external-services.xml index 37e1fb5089..83d45b38cc 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/spring/api/external-services.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/spring/api/external-services.xml @@ -90,6 +90,8 @@ + + diff --git a/dspace-api/src/test/data/dspaceFolder/config/spring/api/item-filters.xml b/dspace-api/src/test/data/dspaceFolder/config/spring/api/item-filters.xml index 4107202be5..8af8bcff22 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/spring/api/item-filters.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/spring/api/item-filters.xml @@ -286,7 +286,7 @@ - @@ -328,7 +328,7 @@ - @@ -365,4 +365,12 @@ + + + + + + + diff --git a/dspace-api/src/test/data/dspaceFolder/config/spring/api/qaevents-test.xml b/dspace-api/src/test/data/dspaceFolder/config/spring/api/qaevents-test.xml new file mode 100644 index 0000000000..8738d6cbef --- /dev/null +++ b/dspace-api/src/test/data/dspaceFolder/config/spring/api/qaevents-test.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dspace-api/src/test/data/dspaceFolder/config/spring/api/scripts.xml b/dspace-api/src/test/data/dspaceFolder/config/spring/api/scripts.xml index 808d22a5bf..a197b2910b 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/spring/api/scripts.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/spring/api/scripts.xml @@ -64,6 +64,11 @@ + + + + + diff --git a/dspace-api/src/test/data/dspaceFolder/config/spring/api/solr-services.xml b/dspace-api/src/test/data/dspaceFolder/config/spring/api/solr-services.xml index 32ab90b2cc..d60dea2c34 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/spring/api/solr-services.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/spring/api/solr-services.xml @@ -38,8 +38,6 @@ - @@ -47,7 +45,20 @@ + + + + + + + + + Connection to an embedded Solr instance. + diff --git a/dspace-api/src/test/data/dspaceFolder/config/spring/api/suggestions.xml b/dspace-api/src/test/data/dspaceFolder/config/spring/api/suggestions.xml new file mode 100644 index 0000000000..a3ae1cb875 --- /dev/null +++ b/dspace-api/src/test/data/dspaceFolder/config/spring/api/suggestions.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/dspace-api/src/test/java/org/dspace/AbstractDSpaceIntegrationTest.java b/dspace-api/src/test/java/org/dspace/AbstractDSpaceIntegrationTest.java index 5a5ce8bf6d..516f13bfd8 100644 --- a/dspace-api/src/test/java/org/dspace/AbstractDSpaceIntegrationTest.java +++ b/dspace-api/src/test/java/org/dspace/AbstractDSpaceIntegrationTest.java @@ -21,8 +21,12 @@ import org.dspace.builder.AbstractBuilder; import org.dspace.discovery.SearchUtils; import org.dspace.servicemanager.DSpaceKernelImpl; import org.dspace.servicemanager.DSpaceKernelInit; +import org.junit.After; import org.junit.AfterClass; +import org.junit.Before; import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.rules.TestName; /** * Abstract Test class copied from DSpace API @@ -46,6 +50,12 @@ public class AbstractDSpaceIntegrationTest { */ protected static DSpaceKernelImpl kernelImpl; + /** + * Obtain the TestName from JUnit, so that we can print it out in the test logs (see below) + */ + @Rule + public TestName testName = new TestName(); + /** * Default constructor */ @@ -60,7 +70,7 @@ public class AbstractDSpaceIntegrationTest { @BeforeClass public static void initTestEnvironment() { try { - //Stops System.exit(0) throws exception instead of exitting + //Stops System.exit(0) throws exception instead of exiting System.setSecurityManager(new NoExitSecurityManager()); //set a standard time zone for the tests @@ -90,6 +100,20 @@ public class AbstractDSpaceIntegrationTest { } } + @Before + public void printTestMethodBefore() { + // Log the test method being executed. Put lines around it to make it stand out. + log.info("---"); + log.info("Starting execution of test method: {}()", testName.getMethodName()); + log.info("---"); + } + + @After + public void printTestMethodAfter() { + // Log the test method just completed. + log.info("Finished execution of test method: {}()", testName.getMethodName()); + } + /** * This method will be run after all tests finish as per @AfterClass. It * will clean resources initialized by the @BeforeClass methods. diff --git a/dspace-api/src/test/java/org/dspace/AbstractDSpaceTest.java b/dspace-api/src/test/java/org/dspace/AbstractDSpaceTest.java index 36477556d3..136af83f07 100644 --- a/dspace-api/src/test/java/org/dspace/AbstractDSpaceTest.java +++ b/dspace-api/src/test/java/org/dspace/AbstractDSpaceTest.java @@ -18,9 +18,13 @@ import java.util.TimeZone; import org.apache.logging.log4j.Logger; import org.dspace.servicemanager.DSpaceKernelImpl; import org.dspace.servicemanager.DSpaceKernelInit; +import org.junit.After; import org.junit.AfterClass; +import org.junit.Before; import org.junit.BeforeClass; import org.junit.Ignore; +import org.junit.Rule; +import org.junit.rules.TestName; import org.junit.runner.RunWith; import org.mockito.junit.MockitoJUnitRunner; @@ -62,6 +66,12 @@ public class AbstractDSpaceTest { */ protected static DSpaceKernelImpl kernelImpl; + /** + * Obtain the TestName from JUnit, so that we can print it out in the test logs (see below) + */ + @Rule + public TestName testName = new TestName(); + /** * This method will be run before the first test as per @BeforeClass. It will * initialize shared resources required for all tests of this class. @@ -94,6 +104,19 @@ public class AbstractDSpaceTest { } } + @Before + public void printTestMethodBefore() { + // Log the test method being executed. Put lines around it to make it stand out. + log.info("---"); + log.info("Starting execution of test method: {}()", testName.getMethodName()); + log.info("---"); + } + + @After + public void printTestMethodAfter() { + // Log the test method just completed. + log.info("Finished execution of test method: {}()", testName.getMethodName()); + } /** * This method will be run after all tests finish as per @AfterClass. It diff --git a/dspace-api/src/test/java/org/dspace/AbstractIntegrationTestWithDatabase.java b/dspace-api/src/test/java/org/dspace/AbstractIntegrationTestWithDatabase.java index e27fb19a68..9bacbb97ee 100644 --- a/dspace-api/src/test/java/org/dspace/AbstractIntegrationTestWithDatabase.java +++ b/dspace-api/src/test/java/org/dspace/AbstractIntegrationTestWithDatabase.java @@ -9,7 +9,10 @@ package org.dspace; import static org.junit.Assert.fail; +import java.sql.Connection; import java.sql.SQLException; +import java.sql.Statement; +import javax.sql.DataSource; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -29,6 +32,8 @@ import org.dspace.eperson.factory.EPersonServiceFactory; import org.dspace.eperson.service.EPersonService; import org.dspace.eperson.service.GroupService; import org.dspace.kernel.ServiceManager; +import org.dspace.qaevent.MockQAEventService; +import org.dspace.qaevent.service.QAEventService; import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.statistics.MockSolrLoggerServiceImpl; import org.dspace.statistics.MockSolrStatisticsCore; @@ -89,6 +94,14 @@ public class AbstractIntegrationTestWithDatabase extends AbstractDSpaceIntegrati try { // Update/Initialize the database to latest version (via Flyway) DatabaseUtils.updateDatabase(); + + // Register custom functions in the H2 database + DataSource dataSource = DSpaceServicesFactory.getInstance() + .getServiceManager() + .getServiceByName("dataSource", DataSource.class); + try (Connection c = dataSource.getConnection(); Statement stmt = c.createStatement()) { + stmt.execute("CREATE ALIAS IF NOT EXISTS matches FOR 'org.dspace.util.DSpaceH2Dialect.matches'"); + } } catch (SQLException se) { log.error("Error initializing database", se); fail("Error initializing database: " + se.getMessage() @@ -177,25 +190,38 @@ public class AbstractIntegrationTestWithDatabase extends AbstractDSpaceIntegrati AbstractBuilder.cleanupObjects(); parentCommunity = null; cleanupContext(); + } catch (Exception e) { + throw new RuntimeException("Error cleaning up builder objects & context object", e); + } - ServiceManager serviceManager = DSpaceServicesFactory.getInstance().getServiceManager(); - // Clear the search core. - MockSolrSearchCore searchService = serviceManager - .getServiceByName(null, MockSolrSearchCore.class); - searchService.reset(); - // Clear the statistics core. - serviceManager - .getServiceByName(SolrStatisticsCore.class.getName(), MockSolrStatisticsCore.class) - .reset(); + ServiceManager serviceManager = DSpaceServicesFactory.getInstance().getServiceManager(); - MockSolrLoggerServiceImpl statisticsService = serviceManager - .getServiceByName("solrLoggerService", MockSolrLoggerServiceImpl.class); - statisticsService.reset(); + // Clear the search core. + MockSolrSearchCore searchService = serviceManager + .getServiceByName(null, MockSolrSearchCore.class); + searchService.reset(); - MockAuthoritySolrServiceImpl authorityService = serviceManager - .getServiceByName(AuthoritySearchService.class.getName(), MockAuthoritySolrServiceImpl.class); - authorityService.reset(); + // Clear the statistics core. + serviceManager + .getServiceByName(SolrStatisticsCore.class.getName(), MockSolrStatisticsCore.class) + .reset(); + // Reset the statistics logger service + MockSolrLoggerServiceImpl loggerService = serviceManager + .getServiceByName("solrLoggerService", MockSolrLoggerServiceImpl.class); + loggerService.reset(); + + // Clear the authority core + MockAuthoritySolrServiceImpl authorityService = serviceManager + .getServiceByName(AuthoritySearchService.class.getName(), MockAuthoritySolrServiceImpl.class); + authorityService.reset(); + + // Clear the QA events core + MockQAEventService qaEventService = serviceManager + .getServiceByName(QAEventService.class.getName(), MockQAEventService.class); + qaEventService.reset(); + + try { // Reload our ConfigurationService (to reset configs to defaults again) DSpaceServicesFactory.getInstance().getConfigurationService().reloadConfig(); @@ -204,7 +230,7 @@ public class AbstractIntegrationTestWithDatabase extends AbstractDSpaceIntegrati // NOTE: we explicitly do NOT destroy our default eperson & admin as they // are cached and reused for all tests. This speeds up all tests. } catch (Exception e) { - throw new RuntimeException(e); + throw new RuntimeException("Error reloading configuration & resetting builders", e); } } diff --git a/dspace-api/src/test/java/org/dspace/access/status/DefaultAccessStatusHelperTest.java b/dspace-api/src/test/java/org/dspace/access/status/DefaultAccessStatusHelperTest.java index 59dfbb2095..bc1fa8a9bb 100644 --- a/dspace-api/src/test/java/org/dspace/access/status/DefaultAccessStatusHelperTest.java +++ b/dspace-api/src/test/java/org/dspace/access/status/DefaultAccessStatusHelperTest.java @@ -14,6 +14,8 @@ import static org.junit.Assert.fail; import java.io.ByteArrayInputStream; import java.nio.charset.StandardCharsets; import java.sql.SQLException; +import java.time.LocalDate; +import java.time.ZoneId; import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -42,7 +44,6 @@ import org.dspace.core.Constants; import org.dspace.eperson.Group; import org.dspace.eperson.factory.EPersonServiceFactory; import org.dspace.eperson.service.GroupService; -import org.joda.time.LocalDate; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -129,7 +130,7 @@ public class DefaultAccessStatusHelperTest extends AbstractUnitTest { fail("SQL Error in init: " + ex.getMessage()); } helper = new DefaultAccessStatusHelper(); - threshold = new LocalDate(10000, 1, 1).toDate(); + threshold = dateFrom(10000, 1, 1); } /** @@ -265,7 +266,7 @@ public class DefaultAccessStatusHelperTest extends AbstractUnitTest { ResourcePolicy policy = resourcePolicyService.create(context, null, group); policy.setRpName("Embargo"); policy.setAction(Constants.READ); - policy.setStartDate(new LocalDate(9999, 12, 31).toDate()); + policy.setStartDate(dateFrom(9999, 12, 31)); policies.add(policy); authorizeService.removeAllPolicies(context, bitstream); authorizeService.addPolicies(context, policies, bitstream); @@ -293,7 +294,7 @@ public class DefaultAccessStatusHelperTest extends AbstractUnitTest { ResourcePolicy policy = resourcePolicyService.create(context, null, group); policy.setRpName("Restriction"); policy.setAction(Constants.READ); - policy.setStartDate(new LocalDate(10000, 1, 1).toDate()); + policy.setStartDate(dateFrom(10000, 1, 1)); policies.add(policy); authorizeService.removeAllPolicies(context, bitstream); authorizeService.addPolicies(context, policies, bitstream); @@ -381,7 +382,7 @@ public class DefaultAccessStatusHelperTest extends AbstractUnitTest { ResourcePolicy policy = resourcePolicyService.create(context, null, group); policy.setRpName("Embargo"); policy.setAction(Constants.READ); - policy.setStartDate(new LocalDate(9999, 12, 31).toDate()); + policy.setStartDate(dateFrom(9999, 12, 31)); policies.add(policy); authorizeService.removeAllPolicies(context, primaryBitstream); authorizeService.addPolicies(context, policies, primaryBitstream); @@ -411,7 +412,7 @@ public class DefaultAccessStatusHelperTest extends AbstractUnitTest { ResourcePolicy policy = resourcePolicyService.create(context, null, group); policy.setRpName("Embargo"); policy.setAction(Constants.READ); - policy.setStartDate(new LocalDate(9999, 12, 31).toDate()); + policy.setStartDate(dateFrom(9999, 12, 31)); policies.add(policy); authorizeService.removeAllPolicies(context, anotherBitstream); authorizeService.addPolicies(context, policies, anotherBitstream); @@ -421,4 +422,19 @@ public class DefaultAccessStatusHelperTest extends AbstractUnitTest { String embargoDate = helper.getEmbargoFromItem(context, itemWithEmbargo, threshold); assertThat("testWithNoPrimaryAndMultipleBitstreams 1", embargoDate, equalTo(null)); } + + /** + * Create a Date from local year, month, day. + * + * @param year the year. + * @param month the month. + * @param day the day. + * @return the assembled date. + */ + private Date dateFrom(int year, int month, int day) { + return Date.from(LocalDate.of(year, month, day) + .atStartOfDay() + .atZone(ZoneId.systemDefault()) + .toInstant()); + } } diff --git a/dspace-api/src/test/java/org/dspace/administer/StructBuilderIT.java b/dspace-api/src/test/java/org/dspace/administer/StructBuilderIT.java index 63340698ac..ead338bc8e 100644 --- a/dspace-api/src/test/java/org/dspace/administer/StructBuilderIT.java +++ b/dspace-api/src/test/java/org/dspace/administer/StructBuilderIT.java @@ -23,11 +23,12 @@ import javax.xml.transform.stream.StreamSource; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.dspace.AbstractIntegrationTest; +import org.dspace.AbstractIntegrationTestWithDatabase; import org.dspace.authorize.AuthorizeException; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; import org.dspace.content.Collection; import org.dspace.content.Community; -import org.dspace.content.MetadataSchemaEnum; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.CollectionService; import org.dspace.content.service.CommunityService; @@ -38,7 +39,6 @@ import org.junit.BeforeClass; import org.junit.Test; import org.w3c.dom.Attr; import org.w3c.dom.Node; -import org.xml.sax.SAXException; import org.xmlunit.builder.DiffBuilder; import org.xmlunit.diff.Comparison; import org.xmlunit.diff.ComparisonFormatter; @@ -52,7 +52,7 @@ import org.xmlunit.diff.Difference; * @author Mark H. Wood */ public class StructBuilderIT - extends AbstractIntegrationTest { + extends AbstractIntegrationTestWithDatabase { private static final Logger log = LogManager.getLogger(); private static final CommunityService communityService @@ -79,7 +79,8 @@ public class StructBuilderIT * @throws IOException passed through. */ @Before - public void setUp() throws SQLException, AuthorizeException, IOException { + public void setUp() throws Exception { + super.setUp(); // Clear out all communities and collections. context.turnOffAuthorisationSystem(); for (Community community : communityService.findAllTop(context)) { @@ -285,19 +286,15 @@ public class StructBuilderIT * @throws org.dspace.authorize.AuthorizeException passed through. */ @Test - public void testExportStructure() - throws ParserConfigurationException, SAXException, IOException, - SQLException, AuthorizeException { + public void testExportStructure() { // Create some structure to test. context.turnOffAuthorisationSystem(); - Community community0 = communityService.create(null, context); - communityService.setMetadataSingleValue(context, community0, - MetadataSchemaEnum.DC.getName(), "title", null, - null, "Top Community 0"); - Collection collection0_0 = collectionService.create(context, community0); - collectionService.setMetadataSingleValue(context, collection0_0, - MetadataSchemaEnum.DC.getName(), "title", null, - null, "Collection 0.0"); + // Top level community + Community community0 = CommunityBuilder.createCommunity(context) + .withName("Top Community 0").build(); + // Collection below top level community + Collection collection0_0 = CollectionBuilder.createCollection(context, community0) + .withName("Collection 0.0").build(); // Export the current structure. System.out.println("exportStructure"); diff --git a/dspace-api/src/test/java/org/dspace/app/bulkaccesscontrol/BulkAccessControlIT.java b/dspace-api/src/test/java/org/dspace/app/bulkaccesscontrol/BulkAccessControlIT.java index 73f02e4049..c115c04f6c 100644 --- a/dspace-api/src/test/java/org/dspace/app/bulkaccesscontrol/BulkAccessControlIT.java +++ b/dspace-api/src/test/java/org/dspace/app/bulkaccesscontrol/BulkAccessControlIT.java @@ -943,7 +943,7 @@ public class BulkAccessControlIT extends AbstractIntegrationTestWithDatabase { Bitstream bitstreamOne; try (InputStream is = IOUtils.toInputStream(bitstreamOneContent, CharEncoding.UTF_8)) { bitstreamOne = BitstreamBuilder.createBitstream(context, bundle, is) - .withName("bistream one") + .withName("bitstream one") .build(); } @@ -951,7 +951,7 @@ public class BulkAccessControlIT extends AbstractIntegrationTestWithDatabase { Bitstream bitstreamTwo; try (InputStream is = IOUtils.toInputStream(bitstreamTwoContent, CharEncoding.UTF_8)) { bitstreamTwo = BitstreamBuilder.createBitstream(context, bundle, is) - .withName("bistream two") + .withName("bitstream two") .build(); } @@ -1188,7 +1188,7 @@ public class BulkAccessControlIT extends AbstractIntegrationTestWithDatabase { String bitstreamContent = "Dummy content"; try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { BitstreamBuilder.createBitstream(context, bundle, is) - .withName("bistream") + .withName("bitstream") .build(); } } @@ -1297,7 +1297,7 @@ public class BulkAccessControlIT extends AbstractIntegrationTestWithDatabase { String bitstreamContent = "Dummy content"; try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { BitstreamBuilder.createBitstream(context, bundle, is) - .withName("bistream") + .withName("bitstream") .build(); } } @@ -1404,7 +1404,7 @@ public class BulkAccessControlIT extends AbstractIntegrationTestWithDatabase { String bitstreamContent = "Dummy content"; try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { BitstreamBuilder.createBitstream(context, bundle, is) - .withName("bistream") + .withName("bitstream") .build(); } } @@ -1753,7 +1753,7 @@ public class BulkAccessControlIT extends AbstractIntegrationTestWithDatabase { try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { bitstreamOne = BitstreamBuilder.createBitstream(context, bundleOne, is) - .withName("bistream of bundle one") + .withName("bitstream of bundle one") .build(); } diff --git a/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataImportIT.java b/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataImportIT.java index ae079df560..de1dcc91c9 100644 --- a/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataImportIT.java +++ b/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataImportIT.java @@ -75,6 +75,31 @@ public class MetadataImportIT extends AbstractIntegrationTestWithDatabase { context.restoreAuthSystemState(); } + @Test + public void metadataImportTestWithDuplicateHeader() { + String[] csv = {"id,collection,dc.title,dc.title,dc.contributor.author", + "+," + collection.getHandle() + ",\"Test Import 1\",\"Test Import 2\"," + "\"Donald, SmithImported\"," + + "+," + collection.getHandle() + ",\"Test Import 3\",\"Test Import 4\"," + "\"Donald, SmithImported\""}; + // Should throw an exception because of duplicate header + try { + performImportScript(csv); + } catch (Exception e) { + assertTrue(e instanceof MetadataImportInvalidHeadingException); + } + } + + @Test + public void metadataImportTestWithAnyLanguage() { + String[] csv = {"id,collection,dc.title[*],dc.contributor.author", + "+," + collection.getHandle() + ",\"Test Import 1\"," + "\"Donald, SmithImported\""}; + // Should throw an exception because of invalid ANY language (*) in metadata field + try { + performImportScript(csv); + } catch (Exception e) { + assertTrue(e instanceof MetadataImportInvalidHeadingException); + } + } + @Test public void metadataImportTest() throws Exception { String[] csv = {"id,collection,dc.title,dc.contributor.author", @@ -230,7 +255,7 @@ public class MetadataImportIT extends AbstractIntegrationTestWithDatabase { itemService.getMetadata(item, "dc", "contributor", "author", Item.ANY).get(0).getValue(), "TestAuthorToRemove")); - String[] csv = {"id,collection,dc.title,dc.contributor.author[*]", + String[] csv = {"id,collection,dc.title,dc.contributor.author", item.getID().toString() + "," + personCollection.getHandle() + "," + item.getName() + ","}; performImportScript(csv); item = findItemByName(itemTitle); diff --git a/dspace-api/src/test/java/org/dspace/app/csv/CSVMetadataImportReferenceIT.java b/dspace-api/src/test/java/org/dspace/app/csv/CSVMetadataImportReferenceIT.java index aee4b4d267..4329092b98 100644 --- a/dspace-api/src/test/java/org/dspace/app/csv/CSVMetadataImportReferenceIT.java +++ b/dspace-api/src/test/java/org/dspace/app/csv/CSVMetadataImportReferenceIT.java @@ -721,7 +721,7 @@ public class CSVMetadataImportReferenceIT extends AbstractIntegrationTestWithDat * * @param value the value of the dc.identifier.other to query for * - * @return first retrived UUID + * @return first retrieved UUID */ private UUID getUUIDByIdentifierOther(String value) throws Exception { ArrayList uuidList = new ArrayList<>(); diff --git a/dspace-api/src/test/java/org/dspace/app/itemexport/ItemExportCLIIT.java b/dspace-api/src/test/java/org/dspace/app/itemexport/ItemExportCLIIT.java index 6db37bdbcd..af12cfd553 100644 --- a/dspace-api/src/test/java/org/dspace/app/itemexport/ItemExportCLIIT.java +++ b/dspace-api/src/test/java/org/dspace/app/itemexport/ItemExportCLIIT.java @@ -83,9 +83,9 @@ public class ItemExportCLIIT extends AbstractIntegrationTestWithDatabase { @After @Override public void destroy() throws Exception { - PathUtils.deleteDirectory(tempDir); + PathUtils.deleteOnExit(tempDir); for (Path path : Files.list(workDir).collect(Collectors.toList())) { - PathUtils.delete(path); + PathUtils.deleteOnExit(path); } super.destroy(); } diff --git a/dspace-api/src/test/java/org/dspace/app/itemimport/ItemImportCLIIT.java b/dspace-api/src/test/java/org/dspace/app/itemimport/ItemImportCLIIT.java index 08ae3af4ae..d6daa06aaa 100644 --- a/dspace-api/src/test/java/org/dspace/app/itemimport/ItemImportCLIIT.java +++ b/dspace-api/src/test/java/org/dspace/app/itemimport/ItemImportCLIIT.java @@ -92,9 +92,9 @@ public class ItemImportCLIIT extends AbstractIntegrationTestWithDatabase { @After @Override public void destroy() throws Exception { - PathUtils.deleteDirectory(tempDir); + PathUtils.deleteOnExit(tempDir); for (Path path : Files.list(workDir).collect(Collectors.toList())) { - PathUtils.delete(path); + PathUtils.deleteOnExit(path); } super.destroy(); } diff --git a/dspace-api/src/test/java/org/dspace/app/ldn/LDNMessageConsumerIT.java b/dspace-api/src/test/java/org/dspace/app/ldn/LDNMessageConsumerIT.java new file mode 100644 index 0000000000..4f5fa6762d --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/app/ldn/LDNMessageConsumerIT.java @@ -0,0 +1,451 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.ldn; + +import static org.dspace.app.ldn.LDNMessageEntity.QUEUE_STATUS_QUEUED; +import static org.dspace.matcher.NotifyServiceEntityMatcher.matchesNotifyServiceEntity; +import static org.hamcrest.CoreMatchers.containsString; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; + +import java.io.InputStream; +import java.sql.SQLException; +import java.util.List; +import java.util.Set; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.collections4.CollectionUtils; +import org.dspace.AbstractIntegrationTestWithDatabase; +import org.dspace.app.ldn.factory.NotifyServiceFactory; +import org.dspace.app.ldn.model.Notification; +import org.dspace.app.ldn.service.LDNMessageService; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.EPersonBuilder; +import org.dspace.builder.NotifyServiceBuilder; +import org.dspace.builder.NotifyServiceInboundPatternBuilder; +import org.dspace.builder.WorkspaceItemBuilder; +import org.dspace.content.Collection; +import org.dspace.content.Item; +import org.dspace.content.WorkspaceItem; +import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.content.service.ItemService; +import org.dspace.core.Constants; +import org.dspace.eperson.EPerson; +import org.dspace.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; +import org.dspace.workflow.WorkflowItem; +import org.dspace.workflow.WorkflowService; +import org.dspace.workflow.factory.WorkflowServiceFactory; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +/** + * Integration Tests against {@link LDNMessageConsumer} + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + */ +public class LDNMessageConsumerIT extends AbstractIntegrationTestWithDatabase { + + private Collection collection; + private EPerson submitter; + + private LDNMessageService ldnMessageService = NotifyServiceFactory.getInstance().getLDNMessageService(); + private ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); + private WorkflowService workflowService = WorkflowServiceFactory.getInstance().getWorkflowService(); + private ItemService itemService = ContentServiceFactory.getInstance().getItemService(); + + @Before + public void setUp() throws Exception { + super.setUp(); + context.turnOffAuthorisationSystem(); + //** GIVEN ** + //1. create a normal user to use as submitter + submitter = EPersonBuilder.createEPerson(context) + .withEmail("submitter@example.com") + .withPassword(password) + .build(); + + //2. A community with one collection. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .withSubmitterGroup(submitter) + .build(); + context.setCurrentUser(submitter); + + context.restoreAuthSystemState(); + } + + @Test + public void testLDNMessageConsumerRequestReview() throws Exception { + context.turnOffAuthorisationSystem(); + + NotifyServiceEntity notifyService = + NotifyServiceBuilder.createNotifyServiceBuilder(context, "service name") + .withDescription("service description") + .withUrl("https://service.ldn.org/about") + .withLdnUrl("https://service.ldn.org/inbox") + .build(); + + //3. a workspace item ready to go + WorkspaceItem workspaceItem = + WorkspaceItemBuilder.createWorkspaceItem(context, collection) + .withTitle("Submission Item") + .withIssueDate("2023-11-20") + .withCOARNotifyService(notifyService, "request-review") + .withFulltext("test.txt", "test", InputStream.nullInputStream()) + .grantLicense() + .build(); + + WorkflowItem workflowItem = workflowService.start(context, workspaceItem); + Item item = workflowItem.getItem(); + context.dispatchEvents(); + context.restoreAuthSystemState(); + + LDNMessageEntity ldnMessage = + ldnMessageService.findAll(context).stream().findFirst().orElse(null); + + + assertThat(notifyService, matchesNotifyServiceEntity(ldnMessage.getTarget())); + assertEquals(workflowItem.getItem().getID(), ldnMessage.getObject().getID()); + assertEquals(QUEUE_STATUS_QUEUED, ldnMessage.getQueueStatus()); + assertNull(ldnMessage.getOrigin()); + assertNotNull(ldnMessage.getMessage()); + + ObjectMapper mapper = new ObjectMapper(); + Notification notification = mapper.readValue(ldnMessage.getMessage(), Notification.class); + + // check id + assertThat(notification.getId(), containsString("urn:uuid:")); + + // check object + assertEquals(notification.getObject().getId(), + configurationService.getProperty("dspace.ui.url") + "/handle/" + item.getHandle()); + assertEquals(notification.getObject().getIetfCiteAs(), + itemService.getMetadataByMetadataString(item, "dc.identifier.uri").get(0).getValue()); + assertEquals(notification.getObject().getUrl().getId(), + configurationService.getProperty("dspace.ui.url") + "/bitstreams/" + + item.getBundles(Constants.CONTENT_BUNDLE_NAME).get(0).getBitstreams().get(0).getID() + "/download"); + + // check target + assertEquals(notification.getTarget().getId(), notifyService.getUrl()); + assertEquals(notification.getTarget().getInbox(), notifyService.getLdnUrl()); + assertEquals(notification.getTarget().getType(), Set.of("Service")); + + // check origin + assertEquals(notification.getOrigin().getId(), configurationService.getProperty("dspace.ui.url")); + assertEquals(notification.getOrigin().getInbox(), configurationService.getProperty("ldn.notify.inbox")); + assertEquals(notification.getOrigin().getType(), Set.of("Service")); + + // check actor + assertEquals(notification.getActor().getId(), configurationService.getProperty("dspace.ui.url")); + assertEquals(notification.getActor().getName(), configurationService.getProperty("dspace.name")); + assertEquals(notification.getOrigin().getType(), Set.of("Service")); + + // check types + assertEquals(notification.getType(), Set.of("coar-notify:ReviewAction", "Offer")); + + } + + @Test + public void testLDNMessageConsumerRequestReviewAutomatic() throws Exception { + context.turnOffAuthorisationSystem(); + + NotifyServiceEntity notifyService = + NotifyServiceBuilder.createNotifyServiceBuilder(context, "service name") + .withDescription("service description") + .withUrl("https://service.ldn.org/about") + .withLdnUrl("https://service.ldn.org/inbox") + .build(); + + NotifyServiceInboundPatternBuilder.createNotifyServiceInboundPatternBuilder(context, notifyService) + .withPattern("request-review") + .withConstraint("simple-demo_filter") + .isAutomatic(true) + .build(); + + //3. a workspace item ready to go + WorkspaceItem workspaceItem = + WorkspaceItemBuilder.createWorkspaceItem(context, collection) + .withTitle("demo Item") + .withIssueDate("2023-11-20") + .withFulltext("test.txt", "test", InputStream.nullInputStream()) + .grantLicense() + .build(); + + WorkflowItem workflowItem = workflowService.start(context, workspaceItem); + Item item = workflowItem.getItem(); + context.dispatchEvents(); + context.restoreAuthSystemState(); + + LDNMessageEntity ldnMessage = + ldnMessageService.findAll(context).stream().findFirst().orElse(null); + + + assertThat(notifyService, matchesNotifyServiceEntity(ldnMessage.getTarget())); + assertEquals(workflowItem.getItem().getID(), ldnMessage.getObject().getID()); + assertEquals(QUEUE_STATUS_QUEUED, ldnMessage.getQueueStatus()); + assertNull(ldnMessage.getOrigin()); + assertNotNull(ldnMessage.getMessage()); + + ObjectMapper mapper = new ObjectMapper(); + Notification notification = mapper.readValue(ldnMessage.getMessage(), Notification.class); + + // check id + assertThat(notification.getId(), containsString("urn:uuid:")); + + // check object + assertEquals(notification.getObject().getId(), + configurationService.getProperty("dspace.ui.url") + "/handle/" + item.getHandle()); + assertEquals(notification.getObject().getIetfCiteAs(), + itemService.getMetadataByMetadataString(item, "dc.identifier.uri").get(0).getValue()); + assertEquals(notification.getObject().getUrl().getId(), + configurationService.getProperty("dspace.ui.url") + "/bitstreams/" + + item.getBundles(Constants.CONTENT_BUNDLE_NAME).get(0).getBitstreams().get(0).getID() + "/download"); + + // check target + assertEquals(notification.getTarget().getId(), notifyService.getUrl()); + assertEquals(notification.getTarget().getInbox(), notifyService.getLdnUrl()); + assertEquals(notification.getTarget().getType(), Set.of("Service")); + + // check origin + assertEquals(notification.getOrigin().getId(), configurationService.getProperty("dspace.ui.url")); + assertEquals(notification.getOrigin().getInbox(), configurationService.getProperty("ldn.notify.inbox")); + assertEquals(notification.getOrigin().getType(), Set.of("Service")); + + // check actor + assertEquals(notification.getActor().getId(), configurationService.getProperty("dspace.ui.url")); + assertEquals(notification.getActor().getName(), configurationService.getProperty("dspace.name")); + assertEquals(notification.getOrigin().getType(), Set.of("Service")); + + // check types + assertEquals(notification.getType(), Set.of("coar-notify:ReviewAction", "Offer")); + + } + + @Test + public void testLDNMessageConsumerRequestEndorsement() throws Exception { + context.turnOffAuthorisationSystem(); + + NotifyServiceEntity notifyService = + NotifyServiceBuilder.createNotifyServiceBuilder(context, "service name") + .withDescription("service description") + .withUrl("https://service.ldn.org/about") + .withLdnUrl("https://service.ldn.org/inbox") + .build(); + + //3. a workspace item ready to go + WorkspaceItem workspaceItem = + WorkspaceItemBuilder.createWorkspaceItem(context, collection) + .withTitle("Submission Item") + .withIssueDate("2023-11-20") + .withCOARNotifyService(notifyService, "request-endorsement") + .withFulltext("test.txt", "test", InputStream.nullInputStream()) + .grantLicense() + .build(); + + WorkflowItem workflowItem = workflowService.start(context, workspaceItem); + Item item = workflowItem.getItem(); + context.dispatchEvents(); + context.restoreAuthSystemState(); + + LDNMessageEntity ldnMessage = + ldnMessageService.findAll(context).stream().findFirst().orElse(null); + + + assertThat(notifyService, matchesNotifyServiceEntity(ldnMessage.getTarget())); + assertEquals(workflowItem.getItem().getID(), ldnMessage.getObject().getID()); + assertEquals(QUEUE_STATUS_QUEUED, ldnMessage.getQueueStatus()); + assertNull(ldnMessage.getOrigin()); + assertNotNull(ldnMessage.getMessage()); + + ObjectMapper mapper = new ObjectMapper(); + Notification notification = mapper.readValue(ldnMessage.getMessage(), Notification.class); + + // check id + assertThat(notification.getId(), containsString("urn:uuid:")); + + // check object + assertEquals(notification.getObject().getId(), + configurationService.getProperty("dspace.ui.url") + "/handle/" + item.getHandle()); + assertEquals(notification.getObject().getIetfCiteAs(), + itemService.getMetadataByMetadataString(item, "dc.identifier.uri").get(0).getValue()); + assertEquals(notification.getObject().getUrl().getId(), + configurationService.getProperty("dspace.ui.url") + "/bitstreams/" + + item.getBundles(Constants.CONTENT_BUNDLE_NAME).get(0).getBitstreams().get(0).getID() + "/download"); + + // check target + assertEquals(notification.getTarget().getId(), notifyService.getUrl()); + assertEquals(notification.getTarget().getInbox(), notifyService.getLdnUrl()); + assertEquals(notification.getTarget().getType(), Set.of("Service")); + + // check origin + assertEquals(notification.getOrigin().getId(), configurationService.getProperty("dspace.ui.url")); + assertEquals(notification.getOrigin().getInbox(), configurationService.getProperty("ldn.notify.inbox")); + assertEquals(notification.getOrigin().getType(), Set.of("Service")); + + // check actor + assertEquals(notification.getActor().getId(), configurationService.getProperty("dspace.ui.url")); + assertEquals(notification.getActor().getName(), configurationService.getProperty("dspace.name")); + assertEquals(notification.getOrigin().getType(), Set.of("Service")); + + // check types + assertEquals(notification.getType(), Set.of("coar-notify:EndorsementAction", "Offer")); + + } + + @Test + public void testLDNMessageConsumerRequestIngest() throws Exception { + context.turnOffAuthorisationSystem(); + + NotifyServiceEntity notifyService = + NotifyServiceBuilder.createNotifyServiceBuilder(context, "service name") + .withDescription("service description") + .withUrl("https://service.ldn.org/about") + .withLdnUrl("https://service.ldn.org/inbox") + .build(); + + //3. a workspace item ready to go + WorkspaceItem workspaceItem = + WorkspaceItemBuilder.createWorkspaceItem(context, collection) + .withTitle("Submission Item") + .withIssueDate("2023-11-20") + .withCOARNotifyService(notifyService, "request-ingest") + .withFulltext("test.txt", "test", InputStream.nullInputStream()) + .grantLicense() + .build(); + + WorkflowItem workflowItem = workflowService.start(context, workspaceItem); + Item item = workflowItem.getItem(); + context.dispatchEvents(); + context.restoreAuthSystemState(); + + LDNMessageEntity ldnMessage = + ldnMessageService.findAll(context).stream().findFirst().orElse(null); + + + assertThat(notifyService, matchesNotifyServiceEntity(ldnMessage.getTarget())); + assertEquals(workflowItem.getItem().getID(), ldnMessage.getObject().getID()); + assertEquals(QUEUE_STATUS_QUEUED, ldnMessage.getQueueStatus()); + assertNull(ldnMessage.getOrigin()); + assertNotNull(ldnMessage.getMessage()); + + ObjectMapper mapper = new ObjectMapper(); + Notification notification = mapper.readValue(ldnMessage.getMessage(), Notification.class); + + // check id + assertThat(notification.getId(), containsString("urn:uuid:")); + + // check object + assertEquals(notification.getObject().getId(), + configurationService.getProperty("dspace.ui.url") + "/handle/" + item.getHandle()); + assertEquals(notification.getObject().getIetfCiteAs(), + itemService.getMetadataByMetadataString(item, "dc.identifier.uri").get(0).getValue()); + assertEquals(notification.getObject().getUrl().getId(), + configurationService.getProperty("dspace.ui.url") + "/bitstreams/" + + item.getBundles(Constants.CONTENT_BUNDLE_NAME).get(0).getBitstreams().get(0).getID() + "/download"); + + // check target + assertEquals(notification.getTarget().getId(), notifyService.getUrl()); + assertEquals(notification.getTarget().getInbox(), notifyService.getLdnUrl()); + assertEquals(notification.getTarget().getType(), Set.of("Service")); + + // check origin + assertEquals(notification.getOrigin().getId(), configurationService.getProperty("dspace.ui.url")); + assertEquals(notification.getOrigin().getInbox(), configurationService.getProperty("ldn.notify.inbox")); + assertEquals(notification.getOrigin().getType(), Set.of("Service")); + + // check actor + assertEquals(notification.getActor().getId(), configurationService.getProperty("dspace.ui.url")); + assertEquals(notification.getActor().getName(), configurationService.getProperty("dspace.name")); + assertEquals(notification.getOrigin().getType(), Set.of("Service")); + + // check types + assertEquals(notification.getType(), Set.of("coar-notify:IngestAction", "Offer")); + + } + + @Test + public void testLDNMessageConsumerRequestFake() throws Exception { + context.turnOffAuthorisationSystem(); + + NotifyServiceEntity notifyService = + NotifyServiceBuilder.createNotifyServiceBuilder(context, "service name") + .withDescription("service description") + .withUrl("https://service.ldn.org/about") + .withLdnUrl("https://service.ldn.org/inbox") + .build(); + + //3. a workspace item ready to go + WorkspaceItem workspaceItem = + WorkspaceItemBuilder.createWorkspaceItem(context, collection) + .withTitle("Submission Item") + .withIssueDate("2023-11-20") + .withCOARNotifyService(notifyService, "request-fake") + .withFulltext("test.txt", "test", InputStream.nullInputStream()) + .grantLicense() + .build(); + + workflowService.start(context, workspaceItem); + context.dispatchEvents(); + context.restoreAuthSystemState(); + + LDNMessageEntity ldnMessage = + ldnMessageService.findAll(context).stream().findFirst().orElse(null); + + assertNull(ldnMessage); + + } + + @Test + public void testLDNMessageConsumerNoRequests() throws Exception { + context.turnOffAuthorisationSystem(); + + //3. a workspace item ready to go + WorkspaceItem workspaceItem = + WorkspaceItemBuilder.createWorkspaceItem(context, collection) + .withTitle("Submission Item") + .withIssueDate("2023-11-20") + .grantLicense() + .build(); + + workflowService.start(context, workspaceItem); + context.dispatchEvents(); + context.restoreAuthSystemState(); + + LDNMessageEntity ldnMessage = + ldnMessageService.findAll(context).stream().findFirst().orElse(null); + + assertNull(ldnMessage); + } + + @Override + @After + public void destroy() throws Exception { + List ldnMessageEntities = ldnMessageService.findAll(context); + if (CollectionUtils.isNotEmpty(ldnMessageEntities)) { + ldnMessageEntities.forEach(ldnMessage -> { + try { + ldnMessageService.delete(context, ldnMessage); + } catch (SQLException e) { + throw new RuntimeException(e); + } + }); + } + + super.destroy(); + } +} + diff --git a/dspace-api/src/test/java/org/dspace/app/ldn/action/SendLDNMessageActionIT.java b/dspace-api/src/test/java/org/dspace/app/ldn/action/SendLDNMessageActionIT.java new file mode 100644 index 0000000000..73f97b2a6a --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/app/ldn/action/SendLDNMessageActionIT.java @@ -0,0 +1,252 @@ +/** + * 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 org.dspace.app.ldn.action.LDNActionStatus.ABORT; +import static org.dspace.app.ldn.action.LDNActionStatus.CONTINUE; +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.InputStream; +import java.sql.SQLException; +import java.util.List; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.http.HttpStatus; +import org.apache.http.StatusLine; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.message.BasicStatusLine; +import org.dspace.AbstractIntegrationTestWithDatabase; +import org.dspace.app.ldn.LDNMessageEntity; +import org.dspace.app.ldn.NotifyServiceEntity; +import org.dspace.app.ldn.factory.NotifyServiceFactory; +import org.dspace.app.ldn.model.Notification; +import org.dspace.app.ldn.service.LDNMessageService; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.EPersonBuilder; +import org.dspace.builder.NotifyServiceBuilder; +import org.dspace.builder.WorkspaceItemBuilder; +import org.dspace.content.Collection; +import org.dspace.content.Item; +import org.dspace.content.WorkspaceItem; +import org.dspace.eperson.EPerson; +import org.dspace.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; +import org.dspace.workflow.WorkflowItem; +import org.dspace.workflow.WorkflowService; +import org.dspace.workflow.factory.WorkflowServiceFactory; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +/** + * Integration Tests against {@link SendLDNMessageAction} + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + */ +public class SendLDNMessageActionIT extends AbstractIntegrationTestWithDatabase { + + private Collection collection; + private EPerson submitter; + private ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); + private LDNMessageService ldnMessageService = NotifyServiceFactory.getInstance().getLDNMessageService(); + private WorkflowService workflowService = WorkflowServiceFactory.getInstance().getWorkflowService(); + private SendLDNMessageAction sendLDNMessageAction; + + @Before + public void setUp() throws Exception { + super.setUp(); + configurationService.setProperty("ldn.enabled", "true"); + sendLDNMessageAction = new SendLDNMessageAction(); + context.turnOffAuthorisationSystem(); + //** GIVEN ** + //1. create a normal user to use as submitter + submitter = EPersonBuilder.createEPerson(context) + .withEmail("submitter@example.com") + .withPassword(password) + .build(); + + //2. A community with one collection. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .withSubmitterGroup(submitter) + .build(); + context.setCurrentUser(submitter); + + context.restoreAuthSystemState(); + } + + @Test + public void testLDNMessageConsumerRequestReview() throws Exception { + CloseableHttpResponse response = mock(CloseableHttpResponse.class); + StatusLine sl = mock(BasicStatusLine.class); + when(response.getStatusLine()).thenReturn(sl); + when(sl.getStatusCode()).thenReturn(HttpStatus.SC_ACCEPTED); + CloseableHttpClient mockedClient = mock(CloseableHttpClient.class); + when(mockedClient.execute(any(HttpPost.class))). + thenReturn(response); + ObjectMapper mapper = new ObjectMapper(); + + context.turnOffAuthorisationSystem(); + + NotifyServiceEntity notifyService = + NotifyServiceBuilder.createNotifyServiceBuilder(context, "service name") + .withDescription("service description") + .withUrl("https://www.notify-inbox.info/") + .withLdnUrl("https://notify-inbox.info/inbox/") + .build(); + + //3. a workspace item ready to go + WorkspaceItem workspaceItem = + WorkspaceItemBuilder.createWorkspaceItem(context, collection) + .withTitle("Submission Item") + .withIssueDate("2023-11-20") + .withCOARNotifyService(notifyService, "request-review") + .withFulltext("test.txt", "test", InputStream.nullInputStream()) + .grantLicense() + .build(); + + WorkflowItem workflowItem = workflowService.start(context, workspaceItem); + Item item = workflowItem.getItem(); + context.dispatchEvents(); + context.restoreAuthSystemState(); + + LDNMessageEntity ldnMessage = + ldnMessageService.findAll(context).stream().findFirst().orElse(null); + + ldnMessage.getQueueStatus(); + + Notification notification = mapper.readValue(ldnMessage.getMessage(), Notification.class); + + sendLDNMessageAction = new SendLDNMessageAction(mockedClient); + assertEquals(sendLDNMessageAction.execute(context, notification, item), CONTINUE); + mockedClient.close(); + response.close(); + } + + @Test + public void testLDNMessageConsumerRequestReviewGotRedirection() throws Exception { + CloseableHttpResponse response = mock(CloseableHttpResponse.class); + StatusLine sl = mock(BasicStatusLine.class); + when(response.getStatusLine()).thenReturn(sl); + when(sl.getStatusCode()).thenReturn(HttpStatus.SC_ACCEPTED); + CloseableHttpClient mockedClient = mock(CloseableHttpClient.class); + when(mockedClient.execute(any(HttpPost.class))). + thenReturn(response); + ObjectMapper mapper = new ObjectMapper(); + + context.turnOffAuthorisationSystem(); + + // ldnUrl should be https://notify-inbox.info/inbox/ + // but used https://notify-inbox.info/inbox for redirection + NotifyServiceEntity notifyService = + NotifyServiceBuilder.createNotifyServiceBuilder(context, "service name") + .withDescription("service description") + .withUrl("https://www.notify-inbox.info/") + .withLdnUrl("https://notify-inbox.info/inbox") + .build(); + + //3. a workspace item ready to go + WorkspaceItem workspaceItem = + WorkspaceItemBuilder.createWorkspaceItem(context, collection) + .withTitle("Submission Item") + .withIssueDate("2023-11-20") + .withCOARNotifyService(notifyService, "request-review") + .withFulltext("test.txt", "test", InputStream.nullInputStream()) + .grantLicense() + .build(); + + WorkflowItem workflowItem = workflowService.start(context, workspaceItem); + Item item = workflowItem.getItem(); + context.dispatchEvents(); + context.restoreAuthSystemState(); + + LDNMessageEntity ldnMessage = + ldnMessageService.findAll(context).stream().findFirst().orElse(null); + + Notification notification = mapper.readValue(ldnMessage.getMessage(), Notification.class); + + sendLDNMessageAction = new SendLDNMessageAction(mockedClient); + assertEquals(sendLDNMessageAction.execute(context, notification, item), CONTINUE); + mockedClient.close(); + response.close(); + } + + @Test + public void testLDNMessageConsumerRequestReviewWithInvalidLdnUrl() throws Exception { + CloseableHttpResponse response = mock(CloseableHttpResponse.class); + StatusLine sl = mock(BasicStatusLine.class); + when(response.getStatusLine()).thenReturn(sl); + when(sl.getStatusCode()).thenReturn(HttpStatus.SC_NOT_FOUND); + CloseableHttpClient mockedClient = mock(CloseableHttpClient.class); + when(mockedClient.execute(any(HttpPost.class))). + thenReturn(response); + ObjectMapper mapper = new ObjectMapper(); + + context.turnOffAuthorisationSystem(); + + NotifyServiceEntity notifyService = + NotifyServiceBuilder.createNotifyServiceBuilder(context, "service name") + .withDescription("service description") + .withUrl("https://www.notify-inbox.info/") + .withLdnUrl("https://notify-inbox.info/invalidLdnUrl/") + .build(); + + //3. a workspace item ready to go + WorkspaceItem workspaceItem = + WorkspaceItemBuilder.createWorkspaceItem(context, collection) + .withTitle("Submission Item") + .withIssueDate("2023-11-20") + .withCOARNotifyService(notifyService, "request-review") + .withFulltext("test.txt", "test", InputStream.nullInputStream()) + .grantLicense() + .build(); + + WorkflowItem workflowItem = workflowService.start(context, workspaceItem); + Item item = workflowItem.getItem(); + context.dispatchEvents(); + context.restoreAuthSystemState(); + + LDNMessageEntity ldnMessage = + ldnMessageService.findAll(context).stream().findFirst().orElse(null); + + Notification notification = mapper.readValue(ldnMessage.getMessage(), Notification.class); + sendLDNMessageAction = new SendLDNMessageAction(mockedClient); + assertEquals(sendLDNMessageAction.execute(context, notification, item), ABORT); + mockedClient.close(); + response.close(); + } + + @Override + @After + public void destroy() throws Exception { + List ldnMessageEntities = ldnMessageService.findAll(context); + if (CollectionUtils.isNotEmpty(ldnMessageEntities)) { + ldnMessageEntities.forEach(ldnMessage -> { + try { + ldnMessageService.delete(context, ldnMessage); + } catch (SQLException e) { + throw new RuntimeException(e); + } + }); + } + + super.destroy(); + } +} + diff --git a/dspace-api/src/test/java/org/dspace/app/mediafilter/MediaFilterIT.java b/dspace-api/src/test/java/org/dspace/app/mediafilter/MediaFilterIT.java index aef2476fdc..938dc92de4 100644 --- a/dspace-api/src/test/java/org/dspace/app/mediafilter/MediaFilterIT.java +++ b/dspace-api/src/test/java/org/dspace/app/mediafilter/MediaFilterIT.java @@ -199,9 +199,9 @@ public class MediaFilterIT extends AbstractIntegrationTestWithDatabase { List bitstreams = textBundles.get(0).getBitstreams(); assertTrue("The item " + item.getName() + " has NOT exactly 1 bitstream in the TEXT bundle", bitstreams.size() == 1); - assertTrue("The text bistream in the " + item.getName() + " is NOT named properly [" + expectedFileName + "]", + assertTrue("The text bitstream in the " + item.getName() + " is NOT named properly [" + expectedFileName + "]", StringUtils.equals(bitstreams.get(0).getName(), expectedFileName)); - assertTrue("The text bistream in the " + item.getName() + " doesn't contain the proper content [" + assertTrue("The text bitstream in the " + item.getName() + " doesn't contain the proper content [" + expectedContent + "]", StringUtils.contains(getContent(bitstreams.get(0)), expectedContent)); } diff --git a/dspace-api/src/test/java/org/dspace/app/packager/PackagerIT.java b/dspace-api/src/test/java/org/dspace/app/packager/PackagerIT.java index 2cddbb511f..d9ee0fb9bf 100644 --- a/dspace-api/src/test/java/org/dspace/app/packager/PackagerIT.java +++ b/dspace-api/src/test/java/org/dspace/app/packager/PackagerIT.java @@ -24,6 +24,7 @@ import org.dspace.AbstractIntegrationTestWithDatabase; import org.dspace.builder.CollectionBuilder; import org.dspace.builder.CommunityBuilder; import org.dspace.builder.ItemBuilder; +import org.dspace.builder.WorkspaceItemBuilder; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.Item; @@ -159,7 +160,7 @@ public class PackagerIT extends AbstractIntegrationTestWithDatabase { performExportScript(article.getHandle(), tempFile); UUID id = article.getID(); itemService.delete(context, article); - WorkspaceItem workspaceItem = workspaceItemService.create(context, col1, id, false, false); + WorkspaceItem workspaceItem = WorkspaceItemBuilder.createWorkspaceItem(context, col1, id).build(); installItemService.installItem(context, workspaceItem, "123456789/0100"); performImportNoForceScript(tempFile); Iterator items = itemService.findByCollection(context, col1); @@ -171,7 +172,7 @@ public class PackagerIT extends AbstractIntegrationTestWithDatabase { } private String getID() throws IOException, MetadataValidationException { - //this method gets the UUID from the mets file thats stored in the attribute element + //this method gets the UUID from the mets file that's stored in the attribute element METSManifest manifest = null; ZipFile zip = new ZipFile(tempFile); ZipEntry manifestEntry = zip.getEntry(METSManifest.MANIFEST_FILE); diff --git a/dspace-api/src/test/java/org/dspace/app/requestitem/JavaMailTestTransport.java b/dspace-api/src/test/java/org/dspace/app/requestitem/JavaMailTestTransport.java index 96cf00c312..f6fbfe02f3 100644 --- a/dspace-api/src/test/java/org/dspace/app/requestitem/JavaMailTestTransport.java +++ b/dspace-api/src/test/java/org/dspace/app/requestitem/JavaMailTestTransport.java @@ -7,12 +7,12 @@ */ package org.dspace.app.requestitem; -import javax.mail.Address; -import javax.mail.Message; -import javax.mail.MessagingException; -import javax.mail.Session; -import javax.mail.Transport; -import javax.mail.URLName; +import jakarta.mail.Address; +import jakarta.mail.Message; +import jakarta.mail.MessagingException; +import jakarta.mail.Session; +import jakarta.mail.Transport; +import jakarta.mail.URLName; /** * A dummy load for SMTP transport, which saves the last message "sent" for diff --git a/dspace-api/src/test/java/org/dspace/app/requestitem/RequestItemEmailNotifierTest.java b/dspace-api/src/test/java/org/dspace/app/requestitem/RequestItemEmailNotifierTest.java index 713e007c58..0868017ed9 100644 --- a/dspace-api/src/test/java/org/dspace/app/requestitem/RequestItemEmailNotifierTest.java +++ b/dspace-api/src/test/java/org/dspace/app/requestitem/RequestItemEmailNotifierTest.java @@ -12,12 +12,11 @@ import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.instanceOf; import static org.junit.Assert.assertEquals; -import javax.mail.Address; -import javax.mail.Message; -import javax.mail.Provider; -import javax.mail.Session; -import javax.mail.internet.InternetAddress; - +import jakarta.mail.Address; +import jakarta.mail.Message; +import jakarta.mail.Provider; +import jakarta.mail.Session; +import jakarta.mail.internet.InternetAddress; import org.dspace.AbstractUnitTest; import org.dspace.app.requestitem.factory.RequestItemServiceFactory; import org.dspace.app.requestitem.service.RequestItemService; @@ -34,6 +33,7 @@ import org.dspace.handle.factory.HandleServiceFactory; import org.dspace.handle.service.HandleService; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; +import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Test; @@ -78,6 +78,13 @@ public class RequestItemEmailNotifierTest = RequestItemServiceFactory.getInstance().getRequestItemService(); } + @AfterClass + public static void tearDownClass() throws Exception { + // AbstractUnitTest doesn't do this for us. + AbstractBuilder.cleanupObjects(); + AbstractBuilder.destroy(); + } + /** * Test of sendRequest method, of class RequestItemEmailNotifier. * @throws java.lang.Exception passed through. @@ -154,7 +161,7 @@ public class RequestItemEmailNotifierTest assertThat("To: should be an Internet address", myAddresses[0], instanceOf(InternetAddress.class)); String address = ((InternetAddress)myAddresses[0]).getAddress(); - assertEquals("To: address should match requestor.", + assertEquals("To: address should match requester.", ri.getReqEmail(), address); // Check the message body. @@ -239,7 +246,7 @@ public class RequestItemEmailNotifierTest assertThat("To: should be an Internet address", myAddresses[0], instanceOf(InternetAddress.class)); String address = ((InternetAddress)myAddresses[0]).getAddress(); - assertEquals("To: address should match requestor.", + assertEquals("To: address should match requester.", ri.getReqEmail(), address); // Check the message body. diff --git a/dspace-api/src/test/java/org/dspace/app/requestitem/RequestItemHelpdeskStrategyTest.java b/dspace-api/src/test/java/org/dspace/app/requestitem/RequestItemHelpdeskStrategyTest.java index b03d7576f9..816756f9d5 100644 --- a/dspace-api/src/test/java/org/dspace/app/requestitem/RequestItemHelpdeskStrategyTest.java +++ b/dspace-api/src/test/java/org/dspace/app/requestitem/RequestItemHelpdeskStrategyTest.java @@ -67,8 +67,10 @@ public class RequestItemHelpdeskStrategyTest } @AfterClass - public static void tearDownClass() { - AbstractBuilder.destroy(); // AbstractUnitTest doesn't do this for us. + public static void tearDownClass() throws Exception { + // AbstractUnitTest doesn't do this for us. + AbstractBuilder.cleanupObjects(); + AbstractBuilder.destroy(); } @Before diff --git a/dspace-api/src/test/java/org/dspace/app/requestitem/RequestItemSubmitterStrategyTest.java b/dspace-api/src/test/java/org/dspace/app/requestitem/RequestItemSubmitterStrategyTest.java index f485a591b0..964b3fe303 100644 --- a/dspace-api/src/test/java/org/dspace/app/requestitem/RequestItemSubmitterStrategyTest.java +++ b/dspace-api/src/test/java/org/dspace/app/requestitem/RequestItemSubmitterStrategyTest.java @@ -56,8 +56,10 @@ public class RequestItemSubmitterStrategyTest } @AfterClass - public static void tearDownClass() { - AbstractBuilder.destroy(); // AbstractUnitTest doesn't do this for us. + public static void tearDownClass() throws Exception { + // AbstractUnitTest doesn't do this for us. + AbstractBuilder.cleanupObjects(); + AbstractBuilder.destroy(); } @Before diff --git a/dspace-api/src/test/java/org/dspace/app/scripts/handler/impl/TestDSpaceRunnableHandler.java b/dspace-api/src/test/java/org/dspace/app/scripts/handler/impl/TestDSpaceRunnableHandler.java index aced81cbdf..7cc1e8cb45 100644 --- a/dspace-api/src/test/java/org/dspace/app/scripts/handler/impl/TestDSpaceRunnableHandler.java +++ b/dspace-api/src/test/java/org/dspace/app/scripts/handler/impl/TestDSpaceRunnableHandler.java @@ -61,6 +61,12 @@ public class TestDSpaceRunnableHandler extends CommandLineDSpaceRunnableHandler errorMessages.add(message); } + @Override + public void logError(String message, Throwable throwable) { + super.logError(message, throwable); + errorMessages.add(message); + } + public List getInfoMessages() { return infoMessages; } diff --git a/dspace-api/src/test/java/org/dspace/app/sherpa/MockSHERPAService.java b/dspace-api/src/test/java/org/dspace/app/sherpa/MockSHERPAService.java index 239d2864bf..5c5e9f7364 100644 --- a/dspace-api/src/test/java/org/dspace/app/sherpa/MockSHERPAService.java +++ b/dspace-api/src/test/java/org/dspace/app/sherpa/MockSHERPAService.java @@ -43,7 +43,7 @@ public class MockSHERPAService extends SHERPAService { "https://v2.sherpa.ac.uk/cgi/retrieve"); String apiKey = configurationService.getProperty("sherpa.romeo.apikey"); - // Rather than search, we will simply attempt to build the URI using the real pepare method + // Rather than search, we will simply attempt to build the URI using the real prepare method // so that any errors there are caught, and will return a valid response for The Lancet InputStream content = null; try { @@ -100,7 +100,7 @@ public class MockSHERPAService extends SHERPAService { "https://v2.sherpa.ac.uk/cgi/retrieve"); String apiKey = configurationService.getProperty("sherpa.romeo.apikey"); - // Rather than search, we will simply attempt to build the URI using the real pepare method + // Rather than search, we will simply attempt to build the URI using the real prepare method // so that any errors there are caught, and will return a valid response for The Lancet InputStream content = null; try { diff --git a/dspace-api/src/test/java/org/dspace/app/sherpa/SHERPADataProviderTest.java b/dspace-api/src/test/java/org/dspace/app/sherpa/SHERPADataProviderTest.java index 6b9666c830..cbea55ea07 100644 --- a/dspace-api/src/test/java/org/dspace/app/sherpa/SHERPADataProviderTest.java +++ b/dspace-api/src/test/java/org/dspace/app/sherpa/SHERPADataProviderTest.java @@ -8,6 +8,7 @@ package org.dspace.app.sherpa; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @@ -352,4 +353,88 @@ public class SHERPADataProviderTest extends AbstractDSpaceTest { // Does dc.identifier.other match the expected value? assertEquals("Publisher URL must equal " + validUrl, validUrl, url); } + + /** + * Perform the same essential test as prior, but making sure the new comparator and equals methods + * in MetadataValueDTO and ExternalDataObject properly compare objects (even when DTO values are not strictly + * in the same order) + * The provider is configured to use the Mock SHERPAService. + */ + @Test + public void testComparePublisherExternalObjects() { + // Get a response with a single valid ISSN, using the mock service which will return a response based on + // thelancet.json stored response in test resources + // We expect to see the following values set correctly: + // dc.title = Public Library of Science + // dc.identifier.sherpaPublisher 112 + // dc.identifier.other http://www.plos.org/ + + // Set expected values + String validName = "Public Library of Science"; + String validIdentifier = "112"; + String validUrl = "http://www.plos.org/"; + + // First exemplar object should be identical + ExternalDataObject exemplarDataObject = new ExternalDataObject(); + exemplarDataObject.setSource("sherpaPublisher"); + exemplarDataObject.setId(validIdentifier); + exemplarDataObject.setValue(validName); + exemplarDataObject.setDisplayValue(validName); + exemplarDataObject.addMetadata(new MetadataValueDTO("dc", "title", null, null, + validName)); + exemplarDataObject.addMetadata(new MetadataValueDTO("dc", "identifier", "sherpaPublisher", null, + validIdentifier)); + exemplarDataObject.addMetadata(new MetadataValueDTO("dc", "identifier", "other", null, + validUrl)); + + // Exemplar object 2 has a different order of metadata values + // (we still expect it to be 'equal' when comparing since there is no concept of place for DTOs) + ExternalDataObject exemplarDataObject2 = new ExternalDataObject(); + exemplarDataObject2.setSource("sherpaPublisher"); + exemplarDataObject2.setId(validIdentifier); + exemplarDataObject2.setValue(validName); + exemplarDataObject2.setDisplayValue(validName); + exemplarDataObject2.addMetadata(new MetadataValueDTO("dc", "identifier", "other", null, + validUrl)); + exemplarDataObject2.addMetadata(new MetadataValueDTO("dc", "title", null, null, + validName)); + exemplarDataObject2.addMetadata(new MetadataValueDTO("dc", "identifier", "sherpaPublisher", null, + validIdentifier)); + + // Nonequal object should NOT evaluate as equal to our data + ExternalDataObject nonEqualObject = new ExternalDataObject(); + nonEqualObject.setSource("sherpaPublisher"); + nonEqualObject.setId(validIdentifier); + nonEqualObject.setValue(validName); + nonEqualObject.setDisplayValue(validName); + nonEqualObject.addMetadata(new MetadataValueDTO("dc", "title", null, null, + "Private Library of Science")); + nonEqualObject.addMetadata(new MetadataValueDTO("dc", "identifier", "sherpaPublisher", null, + validIdentifier)); + nonEqualObject.addMetadata(new MetadataValueDTO("dc", "identifier", "other", null, + validUrl)); + + + // Retrieve the dataobject(s) from the data provider + List externalDataObjects = + sherpaPublisherProvider.searchExternalDataObjects(validName, 0, 1); + + // Assert that the response is valid and not empty + assertTrue("Couldn't find a data object for publication name " + validName, + externalDataObjects != null && !externalDataObjects.isEmpty()); + + ExternalDataObject dataObject = externalDataObjects.get(0); + + // Assert that the data object itself is not null + assertNotNull("External data object must not be null", dataObject); + + // Assert equality to the exemplar object + assertEquals(exemplarDataObject, dataObject); + + // Assert equality to the 2nd exemplar object + assertEquals(exemplarDataObject2, dataObject); + + // Assert NON-equality to the 3rd object + assertNotEquals(nonEqualObject, dataObject); + } } \ No newline at end of file diff --git a/dspace-api/src/test/java/org/dspace/app/suggestion/MockSolrSuggestionProvider.java b/dspace-api/src/test/java/org/dspace/app/suggestion/MockSolrSuggestionProvider.java new file mode 100644 index 0000000000..af890da455 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/app/suggestion/MockSolrSuggestionProvider.java @@ -0,0 +1,20 @@ +/** + * 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.apache.commons.lang3.StringUtils; +import org.dspace.core.Context; +import org.dspace.external.model.ExternalDataObject; + +public class MockSolrSuggestionProvider extends SolrSuggestionProvider { + + @Override + protected boolean isExternalDataObjectPotentiallySuggested(Context context, ExternalDataObject externalDataObject) { + return StringUtils.equals(MockSuggestionExternalDataSource.NAME, externalDataObject.getSource()); + } +} diff --git a/dspace-api/src/test/java/org/dspace/app/suggestion/MockSolrSuggestionStorageService.java b/dspace-api/src/test/java/org/dspace/app/suggestion/MockSolrSuggestionStorageService.java new file mode 100644 index 0000000000..1c843026d4 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/app/suggestion/MockSolrSuggestionStorageService.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.suggestion; + +import org.dspace.solr.MockSolrServer; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.stereotype.Service; + +/** + * Mock SOLR service for the suggestion Core. + */ +@Service +public class MockSolrSuggestionStorageService extends SolrSuggestionStorageServiceImpl + implements InitializingBean, DisposableBean { + private MockSolrServer mockSolrServer; + + @Override + public void afterPropertiesSet() throws Exception { + mockSolrServer = new MockSolrServer("suggestion"); + solrSuggestionClient = mockSolrServer.getSolrServer(); + } + + /** Clear all records from the search core. */ + public void reset() { + mockSolrServer.reset(); + } + + @Override + public void destroy() throws Exception { + mockSolrServer.destroy(); + } +} \ No newline at end of file diff --git a/dspace-api/src/test/java/org/dspace/app/suggestion/MockSuggestionExternalDataSource.java b/dspace-api/src/test/java/org/dspace/app/suggestion/MockSuggestionExternalDataSource.java new file mode 100644 index 0000000000..cf0303debd --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/app/suggestion/MockSuggestionExternalDataSource.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.suggestion; + +import java.util.List; +import java.util.Optional; + +import org.apache.commons.codec.binary.StringUtils; +import org.dspace.core.Context; +import org.dspace.external.model.ExternalDataObject; +import org.dspace.external.provider.AbstractExternalDataProvider; +import org.dspace.services.RequestService; +import org.dspace.services.model.Request; +import org.dspace.utils.DSpace; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class MockSuggestionExternalDataSource extends AbstractExternalDataProvider { + public static final String NAME = "suggestion"; + + @Autowired + private SuggestionService suggestionService; + + @Override + public String getSourceIdentifier() { + return NAME; + } + + @Override + public Optional getExternalDataObject(String id) { + RequestService requestService = new DSpace().getRequestService(); + Request currentRequest = requestService.getCurrentRequest(); + Context context = (Context) currentRequest.getAttribute("dspace.context"); + Suggestion suggestion = suggestionService.findUnprocessedSuggestion(context, id); + if (suggestion != null) { + ExternalDataObject extDataObj = new ExternalDataObject(NAME); + extDataObj.setDisplayValue(suggestion.getDisplay()); + extDataObj.setId(suggestion.getExternalSourceUri() + .substring(suggestion.getExternalSourceUri().lastIndexOf("/") + 1)); + extDataObj.setMetadata(suggestion.getMetadata()); + return Optional.of(extDataObj); + } + return null; + } + + @Override + public List searchExternalDataObjects(String query, int start, int limit) { + return null; + } + + @Override + public boolean supports(String source) { + return StringUtils.equals(NAME, source); + } + + @Override + public int getNumberOfResults(String query) { + return 0; + } + +} diff --git a/dspace-api/src/test/java/org/dspace/app/suggestion/SuggestionUtilsIT.java b/dspace-api/src/test/java/org/dspace/app/suggestion/SuggestionUtilsIT.java new file mode 100644 index 0000000000..98abfdb7d0 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/app/suggestion/SuggestionUtilsIT.java @@ -0,0 +1,218 @@ +/** + * 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 java.util.Optional.of; +import static org.dspace.app.suggestion.SuggestionUtils.getFirstEntryByMetadatum; +import static org.dspace.orcid.model.OrcidProfileSectionType.EXTERNAL_IDS; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.greaterThan; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.File; +import java.net.URL; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Optional; + +import jakarta.xml.bind.JAXBContext; +import jakarta.xml.bind.Unmarshaller; +import org.apache.commons.collections.CollectionUtils; +import org.dspace.AbstractIntegrationTestWithDatabase; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.content.Collection; +import org.dspace.content.Item; +import org.dspace.content.MetadataValue; +import org.dspace.content.dto.MetadataValueDTO; +import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.content.service.ItemService; +import org.dspace.external.factory.ExternalServiceFactory; +import org.dspace.external.model.ExternalDataObject; +import org.dspace.external.provider.ExternalDataProvider; +import org.dspace.external.provider.impl.OrcidPublicationDataProvider; +import org.dspace.external.service.ExternalDataService; +import org.dspace.kernel.ServiceManager; +import org.dspace.orcid.client.OrcidClient; +import org.dspace.orcid.client.OrcidConfiguration; +import org.dspace.orcid.factory.OrcidServiceFactory; +import org.dspace.orcid.model.OrcidTokenResponseDTO; +import org.dspace.orcid.service.OrcidProfileSectionFactoryService; +import org.dspace.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; +import org.dspace.utils.DSpace; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.orcid.jaxb.model.v3.release.record.Work; +import org.orcid.jaxb.model.v3.release.record.WorkBulk; +import org.orcid.jaxb.model.v3.release.record.summary.Works; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Tests for suggestion utilities @see SuggestionUtils + * @author Francesco Bacchelli (francesco.bacchelli at 4science.it) + */ +public class SuggestionUtilsIT extends AbstractIntegrationTestWithDatabase { + + private static ConfigurationService cfg; + private static final String ORCID = "0000-1111-2222-3333"; + private static final String ACCESS_TOKEN = "32c83ccb-c6d5-4981-b6ea-6a34a36de8ab"; + private static final String BASE_XML_DIR_PATH = "org/dspace/app/orcid-works/"; + private OrcidPublicationDataProvider dataProvider; + private SolrSuggestionProvider solrSuggestionProvider; + private OrcidProfileSectionFactoryService profileSectionFactoryService; + private ItemService itemService; + private Collection collection; + private ExternalDataProvider primaryProvider; + private Collection persons; + private OrcidConfiguration orcidConfiguration; + private OrcidClient orcidClientMock; + private OrcidClient orcidClient; + private String originalClientId; + + @Autowired + private SuggestionService suggestionService; + + @Before + public void setup() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + persons = CollectionBuilder.createCollection(context, parentCommunity) + .withEntityType("Person") + .withName("Profiles") + .build(); + + profileSectionFactoryService = OrcidServiceFactory.getInstance().getOrcidProfileSectionFactoryService(); + itemService = ContentServiceFactory.getInstance().getItemService(); + + context.restoreAuthSystemState(); + + cfg = DSpaceServicesFactory.getInstance().getConfigurationService(); + + ServiceManager serviceManager = DSpaceServicesFactory.getInstance().getServiceManager(); + HashMap providers = serviceManager.getServiceByName("suggestionProviders", + HashMap.class); + solrSuggestionProvider = (SolrSuggestionProvider) providers.get("scopus"); + dataProvider = new DSpace().getServiceManager() + .getServiceByName("orcidPublicationDataProvider", OrcidPublicationDataProvider.class); + ExternalDataService externalDataService = ExternalServiceFactory.getInstance().getExternalDataService(); + primaryProvider = externalDataService.getExternalDataProvider("openaireFunding"); + + orcidConfiguration = new DSpace().getServiceManager() + .getServiceByName("org.dspace.orcid.client.OrcidConfiguration", OrcidConfiguration.class); + + orcidClientMock = mock(OrcidClient.class); + orcidClient = dataProvider.getOrcidClient(); + + dataProvider.setReadPublicAccessToken(null); + dataProvider.setOrcidClient(orcidClientMock); + + originalClientId = orcidConfiguration.getClientId(); + orcidConfiguration.setClientId("DSPACE-CLIENT-ID"); + orcidConfiguration.setClientSecret("DSPACE-CLIENT-SECRET"); + + when(orcidClientMock.getReadPublicAccessToken()).thenReturn(buildTokenResponse(ACCESS_TOKEN)); + + when(orcidClientMock.getWorks(any(), eq(ORCID))).thenReturn(unmarshall("works.xml", Works.class)); + when(orcidClientMock.getWorks(eq(ORCID))).thenReturn(unmarshall("works.xml", Works.class)); + + when(orcidClientMock.getObject(any(), eq(ORCID), any(), eq(Work.class))) + .then((invocation) -> of(unmarshall("work-" + invocation.getArgument(2) + ".xml", Work.class))); + when(orcidClientMock.getObject(eq(ORCID), any(), eq(Work.class))) + .then((invocation) -> of(unmarshall("work-" + invocation.getArgument(1) + ".xml", Work.class))); + + when(orcidClientMock.getWorkBulk(any(), eq(ORCID), any())) + .then((invocation) -> unmarshallWorkBulk(invocation.getArgument(2))); + when(orcidClientMock.getWorkBulk(eq(ORCID), any())) + .then((invocation) -> unmarshallWorkBulk(invocation.getArgument(1))); + } + + @After + public void after() { + dataProvider.setOrcidClient(orcidClient); + orcidConfiguration.setClientId(originalClientId); + } + + @Test + public void testGetAllEntriesByMetadatum() { + context.turnOffAuthorisationSystem(); + Item item = ItemBuilder.createItem(context, persons) + .withTitle("Test profile") + .withScopusAuthorIdentifier("SCOPUS-123456") + .withResearcherIdentifier("R-ID-01") + .build(); + context.restoreAuthSystemState(); + + List values = List.of(getMetadata(item, "person.identifier.scopus-author-id", 0)); + + Object firstOrcidObject = profileSectionFactoryService.createOrcidObject(context, values, EXTERNAL_IDS); + Optional optional = dataProvider.getExternalDataObject(ORCID + "::277902"); + + ExternalDataObject externalDataObject = optional.get(); + String openAireId = externalDataObject.getId(); + Suggestion suggestion = new Suggestion(solrSuggestionProvider.getSourceName(), item, openAireId); + suggestion.getMetadata().add( + new MetadataValueDTO("dc", "title", null, null, "dcTitle")); + suggestion.setDisplay(getFirstEntryByMetadatum(externalDataObject, "dc", "title", null)); + suggestion.getMetadata().add(new MetadataValueDTO("dc", "date", "issued", null, new Date().toString())); + suggestion.getMetadata().add(new MetadataValueDTO("dc", "description", "abstract", null, "description")); + suggestion.setExternalSourceUri(cfg.getProperty("dspace.server.url") + + "/api/integration/externalsources/" + primaryProvider.getSourceIdentifier() + "/entryValues/" + + openAireId); + List result = SuggestionUtils.getAllEntriesByMetadatum(externalDataObject, "dc", "title", null); + + assertTrue(result != null && !result.isEmpty()); + + assertTrue(CollectionUtils.isEqualCollection( + SuggestionUtils.getAllEntriesByMetadatum(externalDataObject, "dc.title"), + result)); + + String firstResult = SuggestionUtils.getFirstEntryByMetadatum(externalDataObject, "dc", "title", null); + assertTrue("Another cautionary tale.".equalsIgnoreCase(firstResult)); + firstResult = SuggestionUtils.getFirstEntryByMetadatum(externalDataObject, "dc.title"); + assertTrue("Another cautionary tale.".equalsIgnoreCase(firstResult)); + } + + private MetadataValue getMetadata(Item item, String metadataField, int place) { + List values = itemService.getMetadataByMetadataString(item, metadataField); + assertThat(values.size(), greaterThan(place)); + return values.get(place); + } + + private OrcidTokenResponseDTO buildTokenResponse(String accessToken) { + OrcidTokenResponseDTO response = new OrcidTokenResponseDTO(); + response.setAccessToken(accessToken); + return response; + } + + private WorkBulk unmarshallWorkBulk(List putCodes) throws Exception { + return unmarshall("workBulk-" + String.join("-", putCodes) + ".xml", WorkBulk.class); + } + + @SuppressWarnings("unchecked") + private T unmarshall(String fileName, Class clazz) throws Exception { + JAXBContext jaxbContext = JAXBContext.newInstance(clazz); + Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); + URL resource = getClass().getClassLoader().getResource(BASE_XML_DIR_PATH + fileName); + if (resource == null) { + throw new IllegalStateException("No resource found named " + BASE_XML_DIR_PATH + fileName); + } + return (T) unmarshaller.unmarshal(new File(resource.getFile())); + } +} diff --git a/dspace-api/src/test/java/org/dspace/app/util/GoogleBitstreamComparatorTest.java b/dspace-api/src/test/java/org/dspace/app/util/GoogleBitstreamComparatorTest.java index 78142c9258..4058974d41 100644 --- a/dspace-api/src/test/java/org/dspace/app/util/GoogleBitstreamComparatorTest.java +++ b/dspace-api/src/test/java/org/dspace/app/util/GoogleBitstreamComparatorTest.java @@ -368,8 +368,8 @@ public class GoogleBitstreamComparatorTest extends AbstractUnitTest { "bitstream2 has a type with a priority higher than bitstream1 (size is ignored) and should come second", "bitstream2", toSort.get(1).getName()); assertEquals( - "bitstream1 has a type with the lowest priority in this bundle eventhough it is the largest bitstream and" + - " should come last", + "bitstream1 has a type with the lowest priority in this bundle even though it is the largest bitstream" + + " and should come last", "bitstream1", toSort.get(2).getName()); } diff --git a/dspace-api/src/test/java/org/dspace/app/util/GoogleMetadataTest.java b/dspace-api/src/test/java/org/dspace/app/util/GoogleMetadataTest.java index ee6723480e..9baf0fe3e9 100644 --- a/dspace-api/src/test/java/org/dspace/app/util/GoogleMetadataTest.java +++ b/dspace-api/src/test/java/org/dspace/app/util/GoogleMetadataTest.java @@ -16,11 +16,15 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.sql.SQLException; +import java.time.Period; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; import java.util.Date; import java.util.List; import java.util.Map; import com.google.common.base.Splitter; +import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.AbstractUnitTest; import org.dspace.authorize.AuthorizeException; @@ -41,10 +45,6 @@ import org.dspace.core.Constants; import org.dspace.eperson.Group; import org.dspace.eperson.factory.EPersonServiceFactory; import org.dspace.eperson.service.GroupService; -import org.joda.time.DateTime; -import org.joda.time.DateTimeZone; -import org.joda.time.MutablePeriod; -import org.joda.time.format.PeriodFormat; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -54,7 +54,7 @@ public class GoogleMetadataTest extends AbstractUnitTest { /** * log4j category */ - private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(GoogleMetadataTest.class); + private static final Logger log = LogManager.getLogger(); /** * Item instance for the tests @@ -125,19 +125,17 @@ public class GoogleMetadataTest extends AbstractUnitTest { Bitstream b = bitstreamService.create( context, new ByteArrayInputStream("Bitstream 1".getBytes(StandardCharsets.UTF_8))); b.setName(context, "Word"); - b.setFormat(context, bitstreamFormatService.create(context)); - b.getFormat(context).setMIMEType("application/msword"); + b.setFormat(context, bitstreamFormatService.findByMIMEType(context, "application/msword")); bundleService.addBitstream(context, bundle, b); Bitstream b2 = bitstreamService.create( context, new ByteArrayInputStream("Bitstream 2".getBytes(StandardCharsets.UTF_8))); b2.setName(context, "Pdf"); - b2.setFormat(context, bitstreamFormatService.create(context)); - b2.getFormat(context).setMIMEType("application/pdf"); + b2.setFormat(context, bitstreamFormatService.findByMIMEType(context, "application/pdf")); bundleService.addBitstream(context, bundle, b2); Bitstream b3 = bitstreamService.create( context, new ByteArrayInputStream("Bitstream 3".getBytes(StandardCharsets.UTF_8))); b3.setName(context, "Rtf"); - b3.setFormat(context, bitstreamFormatService.create(context)); + b3.setFormat(context, bitstreamFormatService.findByMIMEType(context, "text/richtext")); b3.getFormat(context).setMIMEType("text/richtext"); bundleService.addBitstream(context, bundle, b3); context.restoreAuthSystemState(); @@ -160,20 +158,17 @@ public class GoogleMetadataTest extends AbstractUnitTest { Bitstream b = bitstreamService.create( context, new ByteArrayInputStream("123456789".getBytes(StandardCharsets.UTF_8))); b.setName(context, "size9"); - b.setFormat(context, bitstreamFormatService.create(context)); - b.getFormat(context).setMIMEType("application/pdf"); + b.setFormat(context, bitstreamFormatService.findByMIMEType(context, "application/pdf")); bundleService.addBitstream(context, bundle, b); Bitstream b2 = bitstreamService.create( context, new ByteArrayInputStream("1".getBytes(StandardCharsets.UTF_8))); b2.setName(context, "size1"); - b2.setFormat(context, bitstreamFormatService.create(context)); - b2.getFormat(context).setMIMEType("application/pdf"); + b2.setFormat(context, bitstreamFormatService.findByMIMEType(context, "application/pdf")); bundleService.addBitstream(context, bundle, b2); Bitstream b3 = bitstreamService.create( context, new ByteArrayInputStream("12345".getBytes(StandardCharsets.UTF_8))); b3.setName(context, "size5"); - b3.setFormat(context, bitstreamFormatService.create(context)); - b3.getFormat(context).setMIMEType("text/richtext"); + b3.setFormat(context, bitstreamFormatService.findByMIMEType(context, "text/richtext")); bundleService.addBitstream(context, bundle, b3); context.restoreAuthSystemState(); context.commit(); @@ -195,20 +190,17 @@ public class GoogleMetadataTest extends AbstractUnitTest { Bitstream b = bitstreamService.create( context, new ByteArrayInputStream("1".getBytes(StandardCharsets.UTF_8))); b.setName(context, "first"); - b.setFormat(context, bitstreamFormatService.create(context)); - b.getFormat(context).setMIMEType("application/pdf"); + b.setFormat(context, bitstreamFormatService.findByMIMEType(context, "application/pdf")); bundleService.addBitstream(context, bundle, b); Bitstream b2 = bitstreamService.create( context, new ByteArrayInputStream("1".getBytes(StandardCharsets.UTF_8))); b2.setName(context, "second"); - b2.setFormat(context, bitstreamFormatService.create(context)); - b2.getFormat(context).setMIMEType("application/pdf"); + b2.setFormat(context, bitstreamFormatService.findByMIMEType(context, "application/pdf")); bundleService.addBitstream(context, bundle, b2); Bitstream b3 = bitstreamService.create( context, new ByteArrayInputStream("1".getBytes(StandardCharsets.UTF_8))); b3.setName(context, "third"); - b3.setFormat(context, bitstreamFormatService.create(context)); - b3.getFormat(context).setMIMEType("application/pdf"); + b3.setFormat(context, bitstreamFormatService.findByMIMEType(context, "application/pdf")); bundleService.addBitstream(context, bundle, b3); context.restoreAuthSystemState(); context.commit(); @@ -230,20 +222,17 @@ public class GoogleMetadataTest extends AbstractUnitTest { Bitstream b = bitstreamService.create( context, new ByteArrayInputStream("Larger file than primary".getBytes(StandardCharsets.UTF_8))); b.setName(context, "first"); - b.setFormat(context, bitstreamFormatService.create(context)); - b.getFormat(context).setMIMEType("unknown"); + b.setFormat(context, bitstreamFormatService.findUnknown(context)); bundleService.addBitstream(context, bundle, b); Bitstream b2 = bitstreamService.create(context, new ByteArrayInputStream( "Bitstream with more prioritized mimetype than primary".getBytes(StandardCharsets.UTF_8))); b2.setName(context, "second"); - b2.setFormat(context, bitstreamFormatService.create(context)); - b2.getFormat(context).setMIMEType("application/pdf"); + b2.setFormat(context, bitstreamFormatService.findByMIMEType(context, "application/pdf")); bundleService.addBitstream(context, bundle, b2); Bitstream b3 = bitstreamService.create( context, new ByteArrayInputStream("1".getBytes(StandardCharsets.UTF_8))); b3.setName(context, "primary"); - b3.setFormat(context, bitstreamFormatService.create(context)); - b3.getFormat(context).setMIMEType("Primary"); + b3.setFormat(context, bitstreamFormatService.findByMIMEType(context, "text/richtext")); bundleService.addBitstream(context, bundle, b3); bundle.setPrimaryBitstreamID(b3); context.restoreAuthSystemState(); @@ -267,20 +256,17 @@ public class GoogleMetadataTest extends AbstractUnitTest { Bitstream b = bitstreamService.create( context, new ByteArrayInputStream("12".getBytes(StandardCharsets.UTF_8))); b.setName(context, "small"); - b.setFormat(context, bitstreamFormatService.create(context)); - b.getFormat(context).setMIMEType("unknown type 1"); + b.setFormat(context, bitstreamFormatService.findUnknown(context)); bundleService.addBitstream(context, bundle, b); Bitstream b2 = bitstreamService.create( context, new ByteArrayInputStream("12121212".getBytes(StandardCharsets.UTF_8))); b2.setName(context, "medium"); - b2.setFormat(context, bitstreamFormatService.create(context)); - b2.getFormat(context).setMIMEType("unknown type 2"); + b2.setFormat(context, bitstreamFormatService.findUnknown(context)); bundleService.addBitstream(context, bundle, b2); Bitstream b3 = bitstreamService.create( context, new ByteArrayInputStream("12121212121212".getBytes(StandardCharsets.UTF_8))); b3.setName(context, "large"); - b3.setFormat(context, bitstreamFormatService.create(context)); - b3.getFormat(context).setMIMEType("unknown type 3"); + b3.setFormat(context, bitstreamFormatService.findUnknown(context)); bundleService.addBitstream(context, bundle, b3); context.restoreAuthSystemState(); context.commit(); @@ -319,6 +305,7 @@ public class GoogleMetadataTest extends AbstractUnitTest { /** * Test empty bitstreams + * @throws java.lang.Exception passed through. */ @Test public void testGetPDFURLWithEmptyBitstreams() throws Exception { @@ -327,18 +314,15 @@ public class GoogleMetadataTest extends AbstractUnitTest { Bitstream b = bitstreamService.create(context, new ByteArrayInputStream("".getBytes(StandardCharsets.UTF_8))); b.setName(context, "small"); - b.setFormat(context, bitstreamFormatService.create(context)); - b.getFormat(context).setMIMEType("unknown type 1"); + b.setFormat(context, bitstreamFormatService.findUnknown(context)); bundleService.addBitstream(context, bundle, b); Bitstream b2 = bitstreamService.create(context, new ByteArrayInputStream("".getBytes(StandardCharsets.UTF_8))); b2.setName(context, "medium"); - b2.setFormat(context, bitstreamFormatService.create(context)); - b2.getFormat(context).setMIMEType("unknown type 2"); + b2.setFormat(context, bitstreamFormatService.findUnknown(context)); bundleService.addBitstream(context, bundle, b2); Bitstream b3 = bitstreamService.create(context, new ByteArrayInputStream("".getBytes(StandardCharsets.UTF_8))); b3.setName(context, "large"); - b3.setFormat(context, bitstreamFormatService.create(context)); - b3.getFormat(context).setMIMEType("unknown type 3"); + b3.setFormat(context, bitstreamFormatService.findUnknown(context)); bundleService.addBitstream(context, bundle, b3); context.restoreAuthSystemState(); context.commit(); @@ -348,8 +332,9 @@ public class GoogleMetadataTest extends AbstractUnitTest { } /** - * Verify there is no mapping for {@link GoogleMetadata#PDF} if there are only embargoed (non-publically accessible - * bitstream) files + * Verify there is no mapping for {@link GoogleMetadata#PDF} if there are + * only embargoed (non-publicly accessible bitstream) files. + * @throws java.lang.Exception passed through. */ @Test public void testGetPdfUrlOfEmbargoed() throws Exception { @@ -359,12 +344,13 @@ public class GoogleMetadataTest extends AbstractUnitTest { Bitstream b = bitstreamService.create( context, new ByteArrayInputStream("Larger file than primary".getBytes(StandardCharsets.UTF_8))); b.setName(context, "first"); - b.setFormat(context, bitstreamFormatService.create(context)); - b.getFormat(context).setMIMEType("unknown"); + b.setFormat(context, bitstreamFormatService.findUnknown(context)); bundleService.addBitstream(context, bundle, b); // Set 3 month embargo on pdf - MutablePeriod period = PeriodFormat.getDefault().parseMutablePeriod("3 months"); - Date embargoDate = DateTime.now(DateTimeZone.UTC).plus(period).toDate(); + Period period = Period.ofMonths(3); + Date embargoDate = Date.from(ZonedDateTime.now(ZoneOffset.UTC) + .plus(period) + .toInstant()); Group anonGroup = groupService.findByName(context, Group.ANONYMOUS); authorizeService.removeAllPolicies(context, b); resourcePolicyService.removeAllPolicies(context, b); diff --git a/dspace-api/src/test/java/org/dspace/app/util/SubmissionConfigIT.java b/dspace-api/src/test/java/org/dspace/app/util/SubmissionConfigIT.java index f171c45328..73d4434abf 100644 --- a/dspace-api/src/test/java/org/dspace/app/util/SubmissionConfigIT.java +++ b/dspace-api/src/test/java/org/dspace/app/util/SubmissionConfigIT.java @@ -26,22 +26,36 @@ import org.junit.Test; public class SubmissionConfigIT extends AbstractIntegrationTestWithDatabase { @Test - public void testSubmissionConfigMapByCollectionOrEntityType() + public void testSubmissionMapByCommunityHandleSubmissionConfig() throws SubmissionConfigReaderException { context.turnOffAuthorisationSystem(); - // Sep up a structure with one top community and two collections + // Sep up a structure with one top community and two subcommunities with collections Community topcom = CommunityBuilder.createCommunity(context, "123456789/topcommunity-test") .withName("Parent Community") .build(); - // col1 should use the item submission form directly mapped for this collection - Collection col1 = CollectionBuilder.createCollection(context, topcom, "123456789/collection-test") + Community subcom1 = CommunityBuilder.createSubCommunity(context, topcom, "123456789/subcommunity-test") + .withName("Subcommunity 1") + .build(); + Community subcom2 = CommunityBuilder.createSubCommunity(context, topcom, "123456789/not-mapped3") + .withName("Subcommunity 2") + .build(); + // col1 should use the form item submission form mapped for subcom1 + Collection col1 = CollectionBuilder.createCollection(context, subcom1, "123456789/not-mapped1") .withName("Collection 1") + .build(); + // col2 should use the item submission form mapped for the top community + Collection col2 = CollectionBuilder.createCollection(context, subcom2, "123456789/not-mapped2") + .withName("Collection 2") + .build(); + // col3 should use the item submission form directly mapped for this collection + Collection col3 = CollectionBuilder.createCollection(context, subcom1, "123456789/collection-test") + .withName("Collection 3") .withEntityType("CustomEntityType") .build(); - // col2 should use the item submission form mapped for the entity type CustomEntityType - Collection col2 = CollectionBuilder.createCollection(context, topcom, "123456789/not-mapped1") - .withName("Collection 2") + // col4 should use the item submission form mapped for the entity type CustomEntityType + Collection col4 = CollectionBuilder.createCollection(context, subcom1, "123456789/not-mapped4") + .withName("Collection 4") .withEntityType("CustomEntityType") .build(); context.restoreAuthSystemState(); @@ -49,12 +63,20 @@ public class SubmissionConfigIT extends AbstractIntegrationTestWithDatabase { SubmissionConfigService submissionConfigService = SubmissionServiceFactory.getInstance() .getSubmissionConfigService(); - // for col1, it should return the item submission form defined directly for the collection + // for col1, it should return the item submission form defined for their parent subcom1 SubmissionConfig submissionConfig1 = submissionConfigService.getSubmissionConfigByCollection(col1); - assertEquals("collectiontest", submissionConfig1.getSubmissionName()); + assertEquals("subcommunitytest", submissionConfig1.getSubmissionName()); - // for col2, it should return the item submission form defined for the entitytype CustomEntityType + // for col2, it should return the item submission form defined for topcom SubmissionConfig submissionConfig2 = submissionConfigService.getSubmissionConfigByCollection(col2); - assertEquals("entitytypetest", submissionConfig2.getSubmissionName()); + assertEquals("topcommunitytest", submissionConfig2.getSubmissionName()); + + // for col3, it should return the item submission form defined directly for the collection + SubmissionConfig submissionConfig3 = submissionConfigService.getSubmissionConfigByCollection(col3); + assertEquals("collectiontest", submissionConfig3.getSubmissionName()); + + // for col4, it should return the item submission form defined for the entitytype CustomEntityType + SubmissionConfig submissionConfig4 = submissionConfigService.getSubmissionConfigByCollection(col4); + assertEquals("entitytypetest", submissionConfig4.getSubmissionName()); } -} \ No newline at end of file +} diff --git a/dspace-api/src/test/java/org/dspace/authority/AuthorityValueTest.java b/dspace-api/src/test/java/org/dspace/authority/AuthorityValueTest.java new file mode 100644 index 0000000000..0857d07fde --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/authority/AuthorityValueTest.java @@ -0,0 +1,52 @@ +/** + * 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.authority; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.util.Date; + +import org.junit.Test; + +/** + * + * @author mwood + */ +public class AuthorityValueTest { + /** + * Test of stringToDate method, of class AuthorityValue. + */ + @Test + public void testStringToDate() { + Date expected; + Date actual; + + // Test an invalid date. + actual = AuthorityValue.stringToDate("not a date"); + assertNull("Unparsable date should return null", actual); + + // Test a date-time without zone or offset. + expected = Date.from(LocalDateTime.of(1957, 01, 27, 01, 23, 45) + .atZone(ZoneId.systemDefault()) + .toInstant()); + actual = AuthorityValue.stringToDate("1957-01-27T01:23:45"); + assertEquals("Local date-time should convert", expected, actual); + + // Test a date-time with milliseconds and offset from UTC. + expected = Date.from(LocalDateTime.of(1957, 01, 27, 01, 23, 45, 678_000_000) + .atZone(ZoneOffset.of("-05")) + .toInstant()); + actual = AuthorityValue.stringToDate("1957-01-27T01:23:45.678-05"); + assertEquals("Zoned date-time with milliseconds should convert", + expected, actual); + } +} diff --git a/dspace-api/src/test/java/org/dspace/builder/AbstractBuilder.java b/dspace-api/src/test/java/org/dspace/builder/AbstractBuilder.java index f84d17fc7a..93c23564cb 100644 --- a/dspace-api/src/test/java/org/dspace/builder/AbstractBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/AbstractBuilder.java @@ -14,8 +14,14 @@ import org.apache.commons.collections4.CollectionUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.alerts.service.SystemWideAlertService; +import org.dspace.app.ldn.factory.NotifyServiceFactory; +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.app.requestitem.factory.RequestItemServiceFactory; import org.dspace.app.requestitem.service.RequestItemService; +import org.dspace.app.suggestion.SolrSuggestionStorageService; import org.dspace.app.util.SubmissionConfigReaderException; import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.factory.AuthorizeServiceFactory; @@ -45,10 +51,13 @@ import org.dspace.eperson.service.EPersonService; import org.dspace.eperson.service.GroupService; import org.dspace.eperson.service.RegistrationDataService; import org.dspace.eperson.service.SubscribeService; +import org.dspace.identifier.factory.IdentifierServiceFactory; +import org.dspace.identifier.service.DOIService; import org.dspace.orcid.factory.OrcidServiceFactory; import org.dspace.orcid.service.OrcidHistoryService; import org.dspace.orcid.service.OrcidQueueService; import org.dspace.orcid.service.OrcidTokenService; +import org.dspace.qaevent.service.QAEventService; import org.dspace.scripts.factory.ScriptServiceFactory; import org.dspace.scripts.service.ProcessService; import org.dspace.services.factory.DSpaceServicesFactory; @@ -56,6 +65,7 @@ import org.dspace.submit.factory.SubmissionServiceFactory; import org.dspace.submit.service.SubmissionConfigService; import org.dspace.supervision.factory.SupervisionOrderServiceFactory; import org.dspace.supervision.service.SupervisionOrderService; +import org.dspace.utils.DSpace; import org.dspace.versioning.factory.VersionServiceFactory; import org.dspace.versioning.service.VersionHistoryService; import org.dspace.versioning.service.VersioningService; @@ -106,6 +116,7 @@ public abstract class AbstractBuilder { static ProcessService processService; static RequestItemService requestItemService; static VersioningService versioningService; + static DOIService doiService; static OrcidHistoryService orcidHistoryService; static OrcidQueueService orcidQueueService; static OrcidTokenService orcidTokenService; @@ -113,7 +124,13 @@ public abstract class AbstractBuilder { static SubmissionConfigService submissionConfigService; static SubscribeService subscribeService; static SupervisionOrderService supervisionOrderService; + static NotifyService notifyService; + static NotifyServiceInboundPatternService inboundPatternService; + static NotifyPatternToTriggerService notifyPatternToTriggerService; + static QAEventService qaEventService; + static SolrSuggestionStorageService solrSuggestionService; + static LDNMessageService ldnMessageService; protected Context context; @@ -164,6 +181,7 @@ public abstract class AbstractBuilder { requestItemService = RequestItemServiceFactory.getInstance().getRequestItemService(); versioningService = DSpaceServicesFactory.getInstance().getServiceManager() .getServiceByName(VersioningService.class.getName(), VersioningService.class); + doiService = IdentifierServiceFactory.getInstance().getDOIService(); // Temporarily disabled claimedTaskService = XmlWorkflowServiceFactory.getInstance().getClaimedTaskService(); @@ -182,6 +200,12 @@ public abstract class AbstractBuilder { } subscribeService = ContentServiceFactory.getInstance().getSubscribeService(); supervisionOrderService = SupervisionOrderServiceFactory.getInstance().getSupervisionOrderService(); + notifyService = NotifyServiceFactory.getInstance().getNotifyService(); + inboundPatternService = NotifyServiceFactory.getInstance().getNotifyServiceInboundPatternService(); + notifyPatternToTriggerService = NotifyServiceFactory.getInstance().getNotifyPatternToTriggerService(); + qaEventService = new DSpace().getSingletonService(QAEventService.class); + solrSuggestionService = new DSpace().getSingletonService(SolrSuggestionStorageService.class); + ldnMessageService = NotifyServiceFactory.getInstance().getLDNMessageService(); } @@ -214,11 +238,17 @@ public abstract class AbstractBuilder { processService = null; requestItemService = null; versioningService = null; + doiService = null; orcidTokenService = null; systemWideAlertService = null; submissionConfigService = null; subscribeService = null; supervisionOrderService = null; + notifyService = null; + inboundPatternService = null; + notifyPatternToTriggerService = null; + qaEventService = null; + ldnMessageService = null; } diff --git a/dspace-api/src/test/java/org/dspace/builder/AbstractDSpaceObjectBuilder.java b/dspace-api/src/test/java/org/dspace/builder/AbstractDSpaceObjectBuilder.java index b20515017a..fa7306ad99 100644 --- a/dspace-api/src/test/java/org/dspace/builder/AbstractDSpaceObjectBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/AbstractDSpaceObjectBuilder.java @@ -8,29 +8,28 @@ package org.dspace.builder; import java.sql.SQLException; +import java.time.Instant; +import java.time.LocalDate; +import java.time.Period; +import java.time.ZoneId; import java.util.Date; import org.apache.logging.log4j.Logger; import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.ResourcePolicy; import org.dspace.content.DSpaceObject; -import org.dspace.content.Item; import org.dspace.content.service.DSpaceObjectService; import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; -import org.joda.time.DateTime; -import org.joda.time.DateTimeZone; -import org.joda.time.MutablePeriod; -import org.joda.time.format.PeriodFormat; -import org.joda.time.format.PeriodFormatter; /** * Abstract builder to construct DSpace Objects * * @author Tom Desair (tom dot desair at atmire dot com) * @author Raf Ponsaerts (raf dot ponsaerts at atmire dot com) + * @param concrete type of DSpaceObject */ public abstract class AbstractDSpaceObjectBuilder extends AbstractBuilder { @@ -103,7 +102,7 @@ public abstract class AbstractDSpaceObjectBuilder final String qualifier, final String value) { try { - getService().setMetadataSingleValue(context, dso, schema, element, qualifier, Item.ANY, value); + getService().setMetadataSingleValue(context, dso, schema, element, qualifier, null, value); } catch (Exception e) { return handleException(e); } @@ -112,21 +111,27 @@ public abstract class AbstractDSpaceObjectBuilder } /** - * Support method to grant the {@link Constants#READ} permission over an object only to the {@link Group#ANONYMOUS} - * after the specified embargoPeriod. Any other READ permissions will be removed + * Support method to grant the {@link Constants#READ} permission over an + * object only to the {@link Group#ANONYMOUS} after the specified + * embargoPeriod. Any other READ permissions will be removed. * + * @param type of this Builder. * @param embargoPeriod - * the embargo period after which the READ permission will be active. It is parsed using the - * {@link PeriodFormatter#parseMutablePeriod(String)} method of the joda library - * @param dso - * the DSpaceObject on which grant the permission - * @return the builder properly configured to retain read permission on the object only for the specified group + * the embargo period after which the READ permission will be + * active. + * @param dso the DSpaceObject on which to grant the permission. + * @return the builder properly configured to retain read permission on the + * object only for the specified group. */ - protected > B setEmbargo(String embargoPeriod, DSpaceObject dso) { + protected > B setEmbargo(Period embargoPeriod, DSpaceObject dso) { // add policy just for anonymous try { - MutablePeriod period = PeriodFormat.getDefault().parseMutablePeriod(embargoPeriod); - Date embargoDate = DateTime.now(DateTimeZone.UTC).plus(period).toDate(); + Instant embargoInstant = LocalDate.now() + .plus(embargoPeriod) + .atStartOfDay() + .atZone(ZoneId.systemDefault()) + .toInstant(); + Date embargoDate = Date.from(embargoInstant); return setOnlyReadPermission(dso, groupService.findByName(context, Group.ANONYMOUS), embargoDate); } catch (Exception e) { @@ -135,14 +140,19 @@ public abstract class AbstractDSpaceObjectBuilder } /** - * Support method to grant the {@link Constants#READ} permission over an object only to a specific group. Any other - * READ permissions will be removed + * Support method to grant the {@link Constants#READ} permission over an + * object only to a specific group.Any other READ permissions will be + * removed. * + * @param type of this Builder. * @param dso * the DSpaceObject on which grant the permission * @param group * the EPersonGroup that will be granted of the permission - * @return the builder properly configured to retain read permission on the object only for the specified group + * @param startDate + * the date on which access begins. + * @return the builder properly configured to retain read permission on the + * object only for the specified group. */ protected > B setOnlyReadPermission(DSpaceObject dso, Group group, Date startDate) { @@ -161,15 +171,20 @@ public abstract class AbstractDSpaceObjectBuilder } return (B) this; } + /** - * Support method to grant the {@link Constants#ADMIN} permission over an object only to a specific eperson. - * If another ADMIN policy is in place for an eperson it will be replaced + * Support method to grant the {@link Constants#READ} permission over an + * object only to a specific EPerson. Any other READ permissions will be + * removed. * + * @param type of this Builder. * @param dso * the DSpaceObject on which grant the permission * @param eperson - * the eperson that will be granted of the permission - * @return the builder properly configured to build the object with the additional admin permission + * the EPerson that will be granted of the permission + * @param startDate the date on which access begins. + * @return the builder properly configured to build the object with the + * additional admin permission. */ protected > B setAdminPermission(DSpaceObject dso, EPerson eperson, Date startDate) { @@ -191,6 +206,7 @@ public abstract class AbstractDSpaceObjectBuilder /** * Support method to grant {@link Constants#REMOVE} permission to a specific eperson * + * @param type of this Builder. * @param dso * the DSpaceObject on which grant the permission * @param eperson @@ -220,6 +236,7 @@ public abstract class AbstractDSpaceObjectBuilder /** * Support method to grant {@link Constants#ADD} permission to a specific eperson * + * @param type of this Builder. * @param dso * the DSpaceObject on which grant the permission * @param eperson @@ -249,6 +266,7 @@ public abstract class AbstractDSpaceObjectBuilder /** * Support method to grant {@link Constants#WRITE} permission to a specific eperson * + * @param type of this Builder. * @param dso * the DSpaceObject on which grant the permission * @param eperson diff --git a/dspace-api/src/test/java/org/dspace/builder/BitstreamBuilder.java b/dspace-api/src/test/java/org/dspace/builder/BitstreamBuilder.java index 3222fe9ed8..987a367282 100644 --- a/dspace-api/src/test/java/org/dspace/builder/BitstreamBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/BitstreamBuilder.java @@ -10,6 +10,7 @@ package org.dspace.builder; import java.io.IOException; import java.io.InputStream; import java.sql.SQLException; +import java.time.Period; import java.util.List; import org.dspace.authorize.AuthorizeException; @@ -182,17 +183,11 @@ public class BitstreamBuilder extends AbstractDSpaceObjectBuilder { } public BitstreamBuilder withFormat(String format) throws SQLException { - - bitstreamService.addMetadata(context, bitstream, "dc", "format", null, null, format); - - return this; + return withMetadata("dc", "format", null, null, format); } public BitstreamBuilder withProvenance(String provenance) throws SQLException { - - bitstreamService.addMetadata(context, bitstream, "dc", "description", "provenance", null, provenance); - - return this; + return withMetadata("dc", "description", "provenance", null, provenance); } @@ -202,22 +197,24 @@ public class BitstreamBuilder extends AbstractDSpaceObjectBuilder { } public BitstreamBuilder withIIIFLabel(String label) throws SQLException { - bitstreamService.addMetadata(context, bitstream, "iiif", "label", null, null, label); - return this; + return withMetadata("iiif", "label", null, null, label); } public BitstreamBuilder withIIIFCanvasWidth(int i) throws SQLException { - bitstreamService.addMetadata(context, bitstream, "iiif", "image", "width", null, String.valueOf(i)); - return this; + return withMetadata("iiif", "image", "width", null, String.valueOf(i)); } public BitstreamBuilder withIIIFCanvasHeight(int i) throws SQLException { - bitstreamService.addMetadata(context, bitstream, "iiif", "image", "height", null, String.valueOf(i)); - return this; + return withMetadata("iiif", "image", "height", null, String.valueOf(i)); } public BitstreamBuilder withIIIFToC(String toc) throws SQLException { - bitstreamService.addMetadata(context, bitstream, "iiif", "toc", null, null, toc); + return withMetadata("iiif", "toc", null, null, toc); + } + + public BitstreamBuilder withMetadata(String schema, String element, String qualifier, String lang, String value) + throws SQLException { + bitstreamService.addMetadata(context, bitstream, schema, element, qualifier, lang, value); return this; } @@ -236,7 +233,7 @@ public class BitstreamBuilder extends AbstractDSpaceObjectBuilder { return targetBundle; } - public BitstreamBuilder withEmbargoPeriod(String embargoPeriod) { + public BitstreamBuilder withEmbargoPeriod(Period embargoPeriod) { return setEmbargo(embargoPeriod, bitstream); } diff --git a/dspace-api/src/test/java/org/dspace/builder/DOIBuilder.java b/dspace-api/src/test/java/org/dspace/builder/DOIBuilder.java new file mode 100644 index 0000000000..cbfcd798c3 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/builder/DOIBuilder.java @@ -0,0 +1,90 @@ +/** + * 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.builder; + +import java.sql.SQLException; + +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.DSpaceObject; +import org.dspace.core.Context; +import org.dspace.identifier.DOI; +import org.dspace.identifier.service.DOIService; + +/** + * Builder for {@link DOI} entities. + */ +public class DOIBuilder extends AbstractBuilder { + + private DOI doi; + + protected DOIBuilder(Context context) { + super(context); + } + + public static DOIBuilder createDOI(final Context context) { + DOIBuilder builder = new DOIBuilder(context); + return builder.create(context); + } + + private DOIBuilder create(final Context context) { + try { + this.doi = doiService.create(context); + } catch (SQLException e) { + throw new RuntimeException(e); + } + return this; + } + + public DOIBuilder withDoi(final String doi) { + this.doi.setDoi(doi); + return this; + } + + public DOIBuilder withDSpaceObject(final DSpaceObject dSpaceObject) { + this.doi.setDSpaceObject(dSpaceObject); + return this; + } + + public DOIBuilder withStatus(final Integer status) { + this.doi.setStatus(status); + return this; + } + + @Override + public DOI build() throws SQLException, AuthorizeException { + return this.doi; + } + + @Override + public void delete(Context c, DOI doi) throws Exception { + try { + doiService.delete(c, doi); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + @Override + public void cleanup() throws Exception { + try (Context context = new Context()) { + context.setDispatcher("noindex"); + context.turnOffAuthorisationSystem(); + this.doi = context.reloadEntity(this.doi); + if (this.doi != null) { + delete(context, this.doi); + context.complete(); + } + } + } + + @Override + protected DOIService getService() { + return doiService; + } + +} diff --git a/dspace-api/src/test/java/org/dspace/builder/GroupBuilder.java b/dspace-api/src/test/java/org/dspace/builder/GroupBuilder.java index b3447dd8bd..c16fb696b0 100644 --- a/dspace-api/src/test/java/org/dspace/builder/GroupBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/GroupBuilder.java @@ -12,6 +12,9 @@ import java.sql.SQLException; import java.util.UUID; import org.dspace.authorize.AuthorizeException; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.DSpaceObject; import org.dspace.content.service.DSpaceObjectService; import org.dspace.core.Context; import org.dspace.eperson.EPerson; @@ -51,6 +54,33 @@ public class GroupBuilder extends AbstractDSpaceObjectBuilder { return builder.create(context); } + public static GroupBuilder createCollectionAdminGroup(final Context context, Collection collection) { + GroupBuilder builder = new GroupBuilder(context); + return builder.createAdminGroup(context, collection); + } + + public static GroupBuilder createCollectionSubmitterGroup(final Context context, Collection collection) { + GroupBuilder builder = new GroupBuilder(context); + return builder.createSubmitterGroup(context, collection); + } + + public static GroupBuilder createCollectionDefaultReadGroup(final Context context, Collection collection, + String typeOfGroupString, int defaultRead) { + GroupBuilder builder = new GroupBuilder(context); + return builder.createDefaultReadGroup(context, collection, typeOfGroupString, defaultRead); + } + + public static GroupBuilder createCollectionWorkflowRoleGroup(final Context context, Collection collection, + String roleName) { + GroupBuilder builder = new GroupBuilder(context); + return builder.createWorkflowRoleGroup(context, collection, roleName); + } + + public static GroupBuilder createCommunityAdminGroup(final Context context, Community community) { + GroupBuilder builder = new GroupBuilder(context); + return builder.createAdminGroup(context, community); + } + private GroupBuilder create(final Context context) { this.context = context; try { @@ -61,6 +91,54 @@ public class GroupBuilder extends AbstractDSpaceObjectBuilder { return this; } + private GroupBuilder createAdminGroup(final Context context, DSpaceObject container) { + this.context = context; + try { + if (container instanceof Collection) { + group = collectionService.createAdministrators(context, (Collection) container); + } else if (container instanceof Community) { + group = communityService.createAdministrators(context, (Community) container); + } else { + handleException(new IllegalArgumentException("DSpaceObject must be collection or community. " + + "Type: " + container.getType())); + } + } catch (Exception e) { + return handleException(e); + } + return this; + } + + private GroupBuilder createSubmitterGroup(final Context context, Collection collection) { + this.context = context; + try { + group = collectionService.createSubmitters(context, collection); + } catch (Exception e) { + return handleException(e); + } + return this; + } + + private GroupBuilder createDefaultReadGroup(final Context context, Collection collection, + String typeOfGroupString, int defaultRead) { + this.context = context; + try { + group = collectionService.createDefaultReadGroup(context, collection, typeOfGroupString, defaultRead); + } catch (Exception e) { + return handleException(e); + } + return this; + } + + private GroupBuilder createWorkflowRoleGroup(final Context context, Collection collection, String roleName) { + this.context = context; + try { + group = workflowService.createWorkflowRoleGroup(context, collection, roleName); + } catch (Exception e) { + return handleException(e); + } + return this; + } + @Override protected DSpaceObjectService getService() { return groupService; diff --git a/dspace-api/src/test/java/org/dspace/builder/ItemBuilder.java b/dspace-api/src/test/java/org/dspace/builder/ItemBuilder.java index 3e5ab0f38f..5e9545fcaf 100644 --- a/dspace-api/src/test/java/org/dspace/builder/ItemBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/ItemBuilder.java @@ -13,6 +13,7 @@ import static org.dspace.content.authority.Choices.CF_ACCEPTED; import java.io.IOException; import java.sql.SQLException; +import java.time.Period; import java.util.UUID; import org.dspace.authorize.AuthorizeException; @@ -185,6 +186,10 @@ public class ItemBuilder extends AbstractDSpaceObjectBuilder { return addMetadataValue(item, "iiif", "image", "height", String.valueOf(i)); } + public ItemBuilder withDSpaceObjectOwner(String name, String authority) { + return addMetadataValue(item, "dspace", "object", "owner", null, name, authority, 600); + } + public ItemBuilder withMetadata(final String schema, final String element, final String qualifier, final String value) { return addMetadataValue(item, schema, element, qualifier, value); @@ -281,8 +286,8 @@ public class ItemBuilder extends AbstractDSpaceObjectBuilder { } /** - * Withdrawn the item under build. Please note that an user need to be loggedin the context to avoid NPE during the - * creation of the provenance metadata + * Withdraw the item under build. Please note that the Context must be + * logged in to avoid NPE during the creation of the provenance metadata. * * @return the ItemBuilder */ @@ -291,7 +296,13 @@ public class ItemBuilder extends AbstractDSpaceObjectBuilder { return this; } - public ItemBuilder withEmbargoPeriod(String embargoPeriod) { + /** + * Set an embargo to end after some time from "now". + * + * @param embargoPeriod embargo starting "now", for this long. + * @return the ItemBuilder. + */ + public ItemBuilder withEmbargoPeriod(Period embargoPeriod) { return setEmbargo(embargoPeriod, item); } diff --git a/dspace-api/src/test/java/org/dspace/builder/LDNMessageBuilder.java b/dspace-api/src/test/java/org/dspace/builder/LDNMessageBuilder.java new file mode 100644 index 0000000000..1ed3694573 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/builder/LDNMessageBuilder.java @@ -0,0 +1,127 @@ +/** + * 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.builder; + +import java.sql.SQLException; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.dspace.app.ldn.LDNMessageEntity; +import org.dspace.app.ldn.model.Notification; +import org.dspace.app.ldn.service.LDNMessageService; +import org.dspace.core.Context; +import org.dspace.discovery.SearchServiceException; + +/** + * Builder for {@link LDNMessageEntity} entities. + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + * + */ +public class LDNMessageBuilder extends AbstractBuilder { + + /* Log4j logger*/ + private static final Logger log = LogManager.getLogger(); + + private LDNMessageEntity ldnMessageEntity; + + protected LDNMessageBuilder(Context context) { + super(context); + } + + @Override + protected LDNMessageService getService() { + return ldnMessageService; + } + + @Override + public void cleanup() throws Exception { + try (Context c = new Context()) { + c.setDispatcher("noindex"); + c.turnOffAuthorisationSystem(); + // Ensure object and any related objects are reloaded before checking to see what needs cleanup + ldnMessageEntity = c.reloadEntity(ldnMessageEntity); + if (ldnMessageEntity != null) { + delete(ldnMessageEntity); + } + c.complete(); + indexingService.commit(); + } + } + + @Override + public void delete(Context c, LDNMessageEntity ldnMessageEntity) throws Exception { + if (ldnMessageEntity != null) { + getService().delete(c, ldnMessageEntity); + } + } + + @Override + public LDNMessageEntity build() { + try { + + ldnMessageService.update(context, ldnMessageEntity); + context.dispatchEvents(); + + indexingService.commit(); + } catch (SearchServiceException | SQLException e) { + log.error(e); + } + return ldnMessageEntity; + } + + public void delete(LDNMessageEntity ldnMessageEntity) throws Exception { + try (Context c = new Context()) { + c.turnOffAuthorisationSystem(); + LDNMessageEntity nsEntity = c.reloadEntity(ldnMessageEntity); + if (nsEntity != null) { + getService().delete(c, nsEntity); + } + c.complete(); + } + + indexingService.commit(); + } + + public static LDNMessageBuilder createNotifyServiceBuilder(Context context, String id) { + LDNMessageBuilder ldnMessageServiceBuilder = new LDNMessageBuilder(context); + return ldnMessageServiceBuilder.create(context, id); + } + + public static LDNMessageBuilder createNotifyServiceBuilder(Context context, Notification notification) { + LDNMessageBuilder ldnMessageServiceBuilder = new LDNMessageBuilder(context); + return ldnMessageServiceBuilder.create(context, notification); + } + + private LDNMessageBuilder create(Context context, String id) { + try { + + this.context = context; + this.ldnMessageEntity = ldnMessageService.create(context, id); + + } catch (SQLException e) { + log.warn("Failed to create the NotifyService", e); + } + + return this; + } + + private LDNMessageBuilder create(Context context, Notification notification) { + try { + + this.context = context; + this.ldnMessageEntity = ldnMessageService.create(context, notification, "127.0.0.1"); + + } catch (SQLException e) { + log.warn("Failed to create the NotifyService", e); + } + + return this; + } + +} \ No newline at end of file diff --git a/dspace-api/src/test/java/org/dspace/builder/NotifyServiceBuilder.java b/dspace-api/src/test/java/org/dspace/builder/NotifyServiceBuilder.java new file mode 100644 index 0000000000..44cf0be092 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/builder/NotifyServiceBuilder.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.builder; + +import java.math.BigDecimal; +import java.sql.SQLException; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.dspace.app.ldn.NotifyServiceEntity; +import org.dspace.app.ldn.service.NotifyService; +import org.dspace.core.Context; +import org.dspace.discovery.SearchServiceException; + +/** + * Builder for {@link NotifyServiceEntity} entities. + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + * + */ +public class NotifyServiceBuilder extends AbstractBuilder { + + /* Log4j logger*/ + private static final Logger log = LogManager.getLogger(); + + private NotifyServiceEntity notifyServiceEntity; + + protected NotifyServiceBuilder(Context context) { + super(context); + } + + @Override + protected NotifyService getService() { + return notifyService; + } + + @Override + public void cleanup() throws Exception { + try (Context c = new Context()) { + c.setDispatcher("noindex"); + c.turnOffAuthorisationSystem(); + // Ensure object and any related objects are reloaded before checking to see what needs cleanup + notifyServiceEntity = c.reloadEntity(notifyServiceEntity); + if (notifyServiceEntity != null) { + delete(notifyServiceEntity); + } + c.complete(); + indexingService.commit(); + } + } + + @Override + public void delete(Context c, NotifyServiceEntity notifyServiceEntity) throws Exception { + if (notifyServiceEntity != null) { + getService().delete(c, notifyServiceEntity); + } + } + + @Override + public NotifyServiceEntity build() { + try { + + notifyService.update(context, notifyServiceEntity); + context.dispatchEvents(); + + indexingService.commit(); + } catch (SearchServiceException | SQLException e) { + log.error(e); + } + return notifyServiceEntity; + } + + public void delete(NotifyServiceEntity notifyServiceEntity) throws Exception { + try (Context c = new Context()) { + c.turnOffAuthorisationSystem(); + NotifyServiceEntity nsEntity = c.reloadEntity(notifyServiceEntity); + if (nsEntity != null) { + getService().delete(c, nsEntity); + } + c.complete(); + } + + indexingService.commit(); + } + + public static NotifyServiceBuilder createNotifyServiceBuilder(Context context, String name) { + NotifyServiceBuilder notifyServiceBuilder = new NotifyServiceBuilder(context); + return notifyServiceBuilder.create(context, name); + } + + private NotifyServiceBuilder create(Context context, String name) { + try { + + this.context = context; + this.notifyServiceEntity = notifyService.create(context, name); + + } catch (SQLException e) { + log.warn("Failed to create the NotifyService", e); + } + + return this; + } + + public NotifyServiceBuilder withDescription(String description) { + notifyServiceEntity.setDescription(description); + return this; + } + + public NotifyServiceBuilder withUrl(String url) { + notifyServiceEntity.setUrl(url); + return this; + } + + public NotifyServiceBuilder withLdnUrl(String ldnUrl) { + notifyServiceEntity.setLdnUrl(ldnUrl); + return this; + } + + public NotifyServiceBuilder withStatus(boolean enabled) { + notifyServiceEntity.setEnabled(enabled); + return this; + } + + public NotifyServiceBuilder withScore(BigDecimal score) { + notifyServiceEntity.setScore(score); + return this; + } + + public NotifyServiceBuilder isEnabled(boolean enabled) { + notifyServiceEntity.setEnabled(enabled); + return this; + } + + public NotifyServiceBuilder withLowerIp(String lowerIp) { + notifyServiceEntity.setLowerIp(lowerIp); + return this; + } + + public NotifyServiceBuilder withUpperIp(String upperIp) { + notifyServiceEntity.setUpperIp(upperIp); + return this; + } + + /** + * Delete the Test NotifyServiceEntity referred to by the given ID + * @param id ID of NotifyServiceEntity to delete + * @throws SQLException if error occurs + */ + public static void deleteNotifyService(Integer id) throws SQLException { + try (Context c = new Context()) { + c.turnOffAuthorisationSystem(); + NotifyServiceEntity notifyServiceEntity = notifyService.find(c, id); + if (notifyServiceEntity != null) { + notifyService.delete(c, notifyServiceEntity); + } + c.complete(); + } + } + +} \ No newline at end of file diff --git a/dspace-api/src/test/java/org/dspace/builder/NotifyServiceInboundPatternBuilder.java b/dspace-api/src/test/java/org/dspace/builder/NotifyServiceInboundPatternBuilder.java new file mode 100644 index 0000000000..5ae20b0001 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/builder/NotifyServiceInboundPatternBuilder.java @@ -0,0 +1,126 @@ +/** + * 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.builder; + +import java.sql.SQLException; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.dspace.app.ldn.NotifyServiceEntity; +import org.dspace.app.ldn.NotifyServiceInboundPattern; +import org.dspace.app.ldn.service.NotifyServiceInboundPatternService; +import org.dspace.core.Context; +import org.dspace.discovery.SearchServiceException; + +/** + * Builder for {@link NotifyServiceInboundPattern} entities. + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + * + */ +public class NotifyServiceInboundPatternBuilder + extends AbstractBuilder { + + /* Log4j logger*/ + private static final Logger log = LogManager.getLogger(); + + private NotifyServiceInboundPattern notifyServiceInboundPattern; + + protected NotifyServiceInboundPatternBuilder(Context context) { + super(context); + } + + @Override + protected NotifyServiceInboundPatternService getService() { + return inboundPatternService; + } + + @Override + public void cleanup() throws Exception { + try (Context c = new Context()) { + c.setDispatcher("noindex"); + c.turnOffAuthorisationSystem(); + // Ensure object and any related objects are reloaded before checking to see what needs cleanup + notifyServiceInboundPattern = c.reloadEntity(notifyServiceInboundPattern); + if (notifyServiceInboundPattern != null) { + delete(notifyServiceInboundPattern); + } + c.complete(); + indexingService.commit(); + } + } + + @Override + public void delete(Context c, NotifyServiceInboundPattern notifyServiceInboundPattern) throws Exception { + if (notifyServiceInboundPattern != null) { + getService().delete(c, notifyServiceInboundPattern); + } + } + + @Override + public NotifyServiceInboundPattern build() { + try { + + inboundPatternService.update(context, notifyServiceInboundPattern); + context.dispatchEvents(); + + indexingService.commit(); + } catch (SearchServiceException | SQLException e) { + log.error(e); + } + return notifyServiceInboundPattern; + } + + public void delete(NotifyServiceInboundPattern notifyServiceInboundPattern) throws Exception { + try (Context c = new Context()) { + c.turnOffAuthorisationSystem(); + NotifyServiceInboundPattern nsEntity = c.reloadEntity(notifyServiceInboundPattern); + if (nsEntity != null) { + getService().delete(c, nsEntity); + } + c.complete(); + } + + indexingService.commit(); + } + + public static NotifyServiceInboundPatternBuilder createNotifyServiceInboundPatternBuilder( + Context context, NotifyServiceEntity service) { + NotifyServiceInboundPatternBuilder notifyServiceBuilder = new NotifyServiceInboundPatternBuilder(context); + return notifyServiceBuilder.create(context, service); + } + + private NotifyServiceInboundPatternBuilder create(Context context, NotifyServiceEntity service) { + try { + + this.context = context; + this.notifyServiceInboundPattern = inboundPatternService.create(context, service); + + } catch (SQLException e) { + log.warn("Failed to create the NotifyService", e); + } + + return this; + } + + public NotifyServiceInboundPatternBuilder isAutomatic(boolean automatic) { + notifyServiceInboundPattern.setAutomatic(automatic); + return this; + } + + public NotifyServiceInboundPatternBuilder withPattern(String pattern) { + notifyServiceInboundPattern.setPattern(pattern); + return this; + } + + public NotifyServiceInboundPatternBuilder withConstraint(String constraint) { + notifyServiceInboundPattern.setConstraint(constraint); + return this; + } + +} \ No newline at end of file diff --git a/dspace-api/src/test/java/org/dspace/builder/ProcessBuilder.java b/dspace-api/src/test/java/org/dspace/builder/ProcessBuilder.java index 0631e1b55a..fe8f7b8167 100644 --- a/dspace-api/src/test/java/org/dspace/builder/ProcessBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/ProcessBuilder.java @@ -68,8 +68,8 @@ public class ProcessBuilder extends AbstractBuilder { public ProcessBuilder withStartAndEndTime(String startTime, String endTime) throws ParseException { SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd/MM/yyyy"); - process.setStartTime(simpleDateFormat.parse(startTime)); - process.setFinishedTime(simpleDateFormat.parse(endTime)); + process.setStartTime(startTime == null ? null : simpleDateFormat.parse(startTime)); + process.setFinishedTime(endTime == null ? null : simpleDateFormat.parse(endTime)); return this; } diff --git a/dspace-api/src/test/java/org/dspace/builder/QAEventBuilder.java b/dspace-api/src/test/java/org/dspace/builder/QAEventBuilder.java new file mode 100644 index 0000000000..823080516d --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/builder/QAEventBuilder.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.builder; + +import java.util.Date; + +import org.dspace.content.Collection; +import org.dspace.content.Item; +import org.dspace.content.QAEvent; +import org.dspace.core.Context; +import org.dspace.qaevent.service.QAEventService; + +/** + * Builder to construct Quality Assurance Broker Event objects + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + */ +public class QAEventBuilder extends AbstractBuilder { + + private Item item; + private QAEvent target; + private String source = QAEvent.OPENAIRE_SOURCE; + /** + * the title of the DSpace object + * */ + private String title; + /** + * the name of the Quality Assurance Event Topic + * */ + private String topic; + /** + * thr original QA Event imported + * */ + private String message; + /** + * uuid of the targeted DSpace object + * */ + private String relatedItem; + private double trust = 0.5; + private Date lastUpdate = new Date(); + + protected QAEventBuilder(Context context) { + super(context); + } + + public static QAEventBuilder createTarget(final Context context, final Collection col, final String name) { + QAEventBuilder builder = new QAEventBuilder(context); + return builder.create(context, col, name); + } + + public static QAEventBuilder createTarget(final Context context, final Item item) { + QAEventBuilder builder = new QAEventBuilder(context); + return builder.create(context, item); + } + + private QAEventBuilder create(final Context context, final Collection col, final String name) { + this.context = context; + + try { + ItemBuilder itemBuilder = ItemBuilder.createItem(context, col).withTitle(name); + item = itemBuilder.build(); + this.title = name; + context.dispatchEvents(); + indexingService.commit(); + } catch (Exception e) { + return handleException(e); + } + return this; + } + + private QAEventBuilder create(final Context context, final Item item) { + this.context = context; + this.item = item; + return this; + } + + public QAEventBuilder withTopic(final String topic) { + this.topic = topic; + return this; + } + public QAEventBuilder withSource(final String source) { + this.source = source; + return this; + } + public QAEventBuilder withTitle(final String title) { + this.title = title; + return this; + } + public QAEventBuilder withMessage(final String message) { + this.message = message; + return this; + } + public QAEventBuilder withTrust(final double trust) { + this.trust = trust; + return this; + } + public QAEventBuilder withLastUpdate(final Date lastUpdate) { + this.lastUpdate = lastUpdate; + return this; + } + + public QAEventBuilder withRelatedItem(String relatedItem) { + this.relatedItem = relatedItem; + return this; + } + + @Override + public QAEvent build() { + target = new QAEvent(source, "oai:www.dspace.org:" + item.getHandle(), item.getID().toString(), title, topic, + trust, message, lastUpdate); + target.setRelated(relatedItem); + try { + qaEventService.store(context, target); + } catch (Exception e) { + e.printStackTrace(); + } + return target; + } + + @Override + public void cleanup() throws Exception { + qaEventService.deleteEventByEventId(target.getEventId()); + } + + @Override + protected QAEventService getService() { + return qaEventService; + } + + @Override + public void delete(Context c, QAEvent dso) throws Exception { + qaEventService.deleteEventByEventId(target.getEventId()); + +// qaEventService.deleteTarget(dso); + } +} \ No newline at end of file diff --git a/dspace-api/src/test/java/org/dspace/builder/RequestItemBuilder.java b/dspace-api/src/test/java/org/dspace/builder/RequestItemBuilder.java index 66e6245ff6..8481b17e14 100644 --- a/dspace-api/src/test/java/org/dspace/builder/RequestItemBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/RequestItemBuilder.java @@ -10,8 +10,8 @@ package org.dspace.builder; import java.sql.SQLException; import java.util.Date; -import javax.validation.constraints.NotNull; +import jakarta.validation.constraints.NotNull; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.app.requestitem.RequestItem; diff --git a/dspace-api/src/test/java/org/dspace/builder/SuggestionTargetBuilder.java b/dspace-api/src/test/java/org/dspace/builder/SuggestionTargetBuilder.java new file mode 100644 index 0000000000..f9671bba60 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/builder/SuggestionTargetBuilder.java @@ -0,0 +1,161 @@ +/** + * 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.builder; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.apache.solr.client.solrj.SolrServerException; +import org.dspace.app.suggestion.MockSuggestionExternalDataSource; +import org.dspace.app.suggestion.SolrSuggestionStorageService; +import org.dspace.app.suggestion.Suggestion; +import org.dspace.app.suggestion.SuggestionEvidence; +import org.dspace.app.suggestion.SuggestionTarget; +import org.dspace.content.Collection; +import org.dspace.content.Item; +import org.dspace.content.dto.MetadataValueDTO; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; + +/** + * Builder to construct Item objects + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + */ +public class SuggestionTargetBuilder extends AbstractBuilder { + public final static String EVIDENCE_MOCK_NAME = "MockEvidence"; + public final static String EVIDENCE_MOCK_NOTE = "Generated for testing purpose..."; + private Item item; + private SuggestionTarget target; + private List suggestions; + private String source; + private int total; + + protected SuggestionTargetBuilder(Context context) { + super(context); + } + + public static SuggestionTargetBuilder createTarget(final Context context, final Collection col, final String name) { + return createTarget(context, col, name, null); + } + + public static SuggestionTargetBuilder createTarget(final Context context, final Collection col, final String name, + final EPerson eperson) { + SuggestionTargetBuilder builder = new SuggestionTargetBuilder(context); + return builder.create(context, col, name, eperson); + } + + public static SuggestionTargetBuilder createTarget(final Context context, final Item item) { + SuggestionTargetBuilder builder = new SuggestionTargetBuilder(context); + return builder.create(context, item); + } + + private SuggestionTargetBuilder create(final Context context, final Collection col, final String name) { + return create(context, col, name, null); + } + + private SuggestionTargetBuilder create(final Context context, final Collection col, final String name, + final EPerson eperson) { + this.context = context; + + try { + ItemBuilder itemBuilder = ItemBuilder.createItem(context, col).withTitle(name); + if (eperson != null) { + itemBuilder = itemBuilder.withDSpaceObjectOwner(eperson.getFullName(), eperson.getID().toString()); + } + item = itemBuilder.build(); + context.dispatchEvents(); + indexingService.commit(); + } catch (Exception e) { + return handleException(e); + } + return this; + } + + private SuggestionTargetBuilder create(final Context context, final Item item) { + this.context = context; + this.item = item; + return this; + } + + public SuggestionTargetBuilder withSuggestionCount(final String source, final int total) { + this.source = source; + this.total = total; + return this; + } + + @Override + public SuggestionTarget build() { + target = new SuggestionTarget(item); + target.setTotal(total); + target.setSource(source); + suggestions = generateAllSuggestion(); + try { + for (Suggestion s : suggestions) { + solrSuggestionService.addSuggestion(s, false, false); + } + solrSuggestionService.commit(); + } catch (SolrServerException | IOException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + return target; + } + + @Override + public void cleanup() throws Exception { + solrSuggestionService.deleteTarget(target); + } + + @Override + protected SolrSuggestionStorageService getService() { + return solrSuggestionService; + } + + @Override + public void delete(Context c, SuggestionTarget dso) throws Exception { + solrSuggestionService.deleteTarget(dso); + } + + private List generateAllSuggestion() { + List allSuggestions = new ArrayList(); + for (int idx = 0; idx < target.getTotal(); idx++) { + String idPartStr = String.valueOf(idx + 1); + Suggestion sug = new Suggestion(source, item, idPartStr); + sug.setDisplay("Suggestion " + source + " " + idPartStr); + MetadataValueDTO mTitle = new MetadataValueDTO(); + mTitle.setSchema("dc"); + mTitle.setElement("title"); + mTitle.setValue("Title Suggestion " + idPartStr); + + MetadataValueDTO mSource1 = new MetadataValueDTO(); + mSource1.setSchema("dc"); + mSource1.setElement("source"); + mSource1.setValue("Source 1"); + + MetadataValueDTO mSource2 = new MetadataValueDTO(); + mSource2.setSchema("dc"); + mSource2.setElement("source"); + mSource2.setValue("Source 2"); + + sug.getMetadata().add(mTitle); + sug.getMetadata().add(mSource1); + sug.getMetadata().add(mSource2); + + sug.setExternalSourceUri( + "http://localhost/api/integration/externalsources/" + MockSuggestionExternalDataSource.NAME + + "/entryValues/" + idPartStr); + sug.getEvidences().add(new SuggestionEvidence(EVIDENCE_MOCK_NAME, + idx % 2 == 0 ? 100 - idx : (double) idx / 2, EVIDENCE_MOCK_NOTE)); + allSuggestions.add(sug); + } + return allSuggestions; + } + +} diff --git a/dspace-api/src/test/java/org/dspace/builder/WorkspaceItemBuilder.java b/dspace-api/src/test/java/org/dspace/builder/WorkspaceItemBuilder.java index 9d786d4761..67d8894338 100644 --- a/dspace-api/src/test/java/org/dspace/builder/WorkspaceItemBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/WorkspaceItemBuilder.java @@ -10,7 +10,10 @@ package org.dspace.builder; import java.io.IOException; import java.io.InputStream; import java.sql.SQLException; +import java.util.UUID; +import org.dspace.app.ldn.NotifyPatternToTrigger; +import org.dspace.app.ldn.NotifyServiceEntity; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Bitstream; import org.dspace.content.Collection; @@ -41,14 +44,31 @@ public class WorkspaceItemBuilder extends AbstractBuilder potentialDuplicates = duplicateDetectionService.getPotentialDuplicates(context, item1); + + // Make sure result list is size 1 + int size = 1; + assertEquals("Potential duplicates of item1 should have size " + size, + size, potentialDuplicates.size()); + + // The only member should be Public item II (one distance from public item I) + assertEquals("Item II should be be the detected duplicate", + item2.getID(), potentialDuplicates.get(0).getUuid()); + + // Get potential duplicates of item2: + // Expected: BOTH other items should appear as they are both 1 distance away from "Public item II" + potentialDuplicates = duplicateDetectionService.getPotentialDuplicates(context, item2); + + // Sort by title + potentialDuplicates.sort(Comparator.comparing(PotentialDuplicate::getTitle)); + + // Make sure result list is size 1 + size = 2; + assertEquals("Potential duplicates of item2 should have size " + size, + size, potentialDuplicates.size()); + + // The result list should contain both item1 and item3 in the expected order + assertEquals("item1 should be the first detected duplicate", + item1.getID(), potentialDuplicates.get(0).getUuid()); + assertEquals("item3 should be be the second detected duplicate", + item3.getID(), potentialDuplicates.get(1).getUuid()); + + // Check metadata is populated as per configuration, using item1 (first in results) + // Check for date + Optional foundDate = potentialDuplicates.get(0).getMetadataValueList().stream() + .filter(metadataValue -> metadataValue.getMetadataField().toString('.') + .equals("dc.date.issued")) + .map(MetadataValue::getValue).findFirst(); + assertThat("There should be an issue date found", foundDate.isPresent()); + assertEquals("item1 issue date should match the duplicate obj metadata issue date", + item1IssueDate, foundDate.get()); + // Check for subject + Optional foundSubject = potentialDuplicates.get(0).getMetadataValueList().stream() + .filter(metadataValue -> metadataValue.getMetadataField().toString('.').equals("dc.subject")) + .map(MetadataValue::getValue).findFirst(); + assertThat("There should be a subject found", foundSubject.isPresent()); + assertEquals("item1 subject should match the duplicate obj metadata subject", + item1Subject, foundSubject.get()); + + // Check for author, which was NOT configured to be copied + Optional foundAuthor = potentialDuplicates.get(0).getMetadataValueList().stream() + .filter(metadataValue -> metadataValue.getMetadataField().toString('.') + .equals("dc.contributor.author")) + .map(MetadataValue::getValue).findFirst(); + assertThat("There should NOT be an author found", foundAuthor.isEmpty()); + + } + + /** + * Test that a search for getPotentialDuplicates properly escapes Solr reserved characters + * e.g. + - && | | ! ( ) { } [ ] ^ " ~ * ? : \ + * + * @throws Exception + */ + @Test + public void testSearchDuplicatesWithReservedSolrCharacters() throws Exception { + + + + Item item4 = ItemBuilder.createItem(context, col) + .withTitle("Testing: An Important Development Step") + .withIssueDate(item1IssueDate) + .withAuthor(item1Author) + .withSubject(item1Subject) + .build(); + Item item5 = ItemBuilder.createItem(context, col) + .withTitle("Testing an important development step") + .withIssueDate("2012-10-17") + .withAuthor("Smith, Donald X.") + .withSubject("ExtraEntry 2") + .build(); + + // Get potential duplicates of item 4 and make sure no exceptions are thrown + List potentialDuplicates = new ArrayList<>(); + try { + potentialDuplicates = duplicateDetectionService.getPotentialDuplicates(context, item4); + } catch (SearchServiceException e) { + fail("Duplicate search with special characters should NOT result in search exception (" + + e.getMessage() + ")"); + } + + // Make sure result list is size 1 + int size = 1; + assertEquals("Potential duplicates of item4 (special characters) should have size " + size, + size, potentialDuplicates.size()); + + // The only member should be item 5 + assertEquals("Item 5 should be be the detected duplicate", + item5.getID(), potentialDuplicates.get(0).getUuid()); + + } + + //configurationService.setProperty("duplicate.comparison.metadata.field", new String[]{"dc.title"}); + + /** + * Test that a search for a very long title which also contains reserved characters + * + * @throws Exception + */ + @Test + public void testSearchDuplicatesWithVeryLongTitle() throws Exception { + + Item item6 = ItemBuilder.createItem(context, col) + .withTitle("Testing: This title is over 200 characters long and should behave just the same as a " + + "shorter title, with or without reserved characters. This integration test will prove that " + + "long titles are detected as potential duplicates.") + .withIssueDate(item1IssueDate) + .withAuthor(item1Author) + .withSubject(item1Subject) + .build(); + // This item is the same as above, just missing a comma from the title. + Item item7 = ItemBuilder.createItem(context, col) + .withTitle("Testing: This title is over 200 characters long and should behave just the same as a " + + "shorter title with or without reserved characters. This integration test will prove that " + + "long titles are detected as potential duplicates.") + .withIssueDate("2012-10-17") + .withAuthor("Smith, Donald X.") + .withSubject("ExtraEntry 2") + .build(); + + // Get potential duplicates of item 4 and make sure no exceptions are thrown + List potentialDuplicates = new ArrayList<>(); + try { + potentialDuplicates = duplicateDetectionService.getPotentialDuplicates(context, item6); + } catch (SearchServiceException e) { + fail("Duplicate search with special characters (long title) should NOT result in search exception (" + + e.getMessage() + ")"); + } + + // Make sure result list is size 1 + int size = 1; + assertEquals("Potential duplicates of item6 (long title) should have size " + size, + size, potentialDuplicates.size()); + + // The only member should be item 5 + assertEquals("Item 7's long title should match Item 6 as a potential duplicate", + item7.getID(), potentialDuplicates.get(0).getUuid()); + + } + + /** + * Test that a search for a very long title which also contains reserved characters + * + * @throws Exception + */ + @Test + public void testSearchDuplicatesExactMatch() throws Exception { + + // Set distance to 0 manually + configurationService.setProperty("duplicate.comparison.distance", 0); + + Item item8 = ItemBuilder.createItem(context, col) + .withTitle("This integration test will prove that the edit distance of 0 results in an exact match") + .withIssueDate(item1IssueDate) + .withAuthor(item1Author) + .withSubject(item1Subject) + .build(); + // This item is the same as above + Item item9 = ItemBuilder.createItem(context, col) + .withTitle("This integration test will prove that the edit distance of 0 results in an exact match") + .withIssueDate("2012-10-17") + .withAuthor("Smith, Donald X.") + .withSubject("ExtraEntry") + .build(); + // This item has one character different, greater than the edit distance + Item item10 = ItemBuilder.createItem(context, col) + .withTitle("This integration test will prove that the edit distance of 0 results in an exact match.") + .withIssueDate("2012-10-17") + .withAuthor("Smith, Donald X.") + .withSubject("ExtraEntry") + .build(); + + // Get potential duplicates of item 4 and make sure no exceptions are thrown + List potentialDuplicates = new ArrayList<>(); + try { + potentialDuplicates = duplicateDetectionService.getPotentialDuplicates(context, item8); + } catch (SearchServiceException e) { + fail("Duplicate search with special characters (long title) should NOT result in search exception (" + + e.getMessage() + ")"); + } + + // Make sure result list is size 1 - we do NOT expect item 10 to appear + int size = 1; + assertEquals("ONLY one exact match should be found (item 9) " + size, + size, potentialDuplicates.size()); + + // The only member should be item 9 + assertEquals("Item 9 should match Item 8 as a potential duplicate", + item9.getID(), potentialDuplicates.get(0).getUuid()); + + } + + @Test + public void testSearchDuplicatesInWorkflow() throws Exception { + // Get potential duplicates of item 1: + // Expected: Public item II should appear as it has the configured levenshtein distance of 1 + context.turnOffAuthorisationSystem(); + //context.setDispatcher("default"); + XmlWorkflowItem workflowItem1 = WorkflowItemBuilder.createWorkflowItem(context, workflowCol) + .withTitle("Unique title") + .withSubmitter(eperson) + .build(); + XmlWorkflowItem workflowItem2 = WorkflowItemBuilder.createWorkflowItem(context, workflowCol) + .withTitle("Unique title") + .withSubmitter(eperson) + .build(); + + //indexingService.commit(); + context.restoreAuthSystemState(); + context.setCurrentUser(admin); + List potentialDuplicates = + duplicateDetectionService.getPotentialDuplicates(context, workflowItem1.getItem()); + + // Make sure result list is size 1 + int size = 1; + assertEquals("Potential duplicates of item1 should have size " + size, + size, potentialDuplicates.size()); + + // The only member should be workflow item 2 + assertEquals("Workflow item 2 should be be the detected duplicate", + workflowItem2.getItem().getID(), potentialDuplicates.get(0).getUuid()); + } + + /** + * Test that a search for getPotentialDuplicates with multiple fields configured as comparison value + * gives the expected results + * + * @throws Exception + */ + @Test + public void testSearchDuplicatesWithMultipleFields() throws Exception { + // Set configure to use both title and author fields + configurationService.setProperty("duplicate.comparison.metadata.field", + new String[]{"dc.title", "dc.contributor.author"}); + + Item item10 = ItemBuilder.createItem(context, col) + .withTitle("Compare both title and author") + .withIssueDate(item1IssueDate) + .withAuthor("Surname, F.") + .withSubject(item1Subject) + .build(); + Item item11 = ItemBuilder.createItem(context, col) + .withTitle("Compare both title and author") + .withIssueDate("2012-10-17") + .withAuthor("Surname, F.") + .withSubject("ExtraEntry 2") + .build(); + + Item item12 = ItemBuilder.createItem(context, col) + .withTitle("Compare both title and author") + .withIssueDate("2012-10-17") + .withAuthor("Lastname, First.") + .withSubject("ExtraEntry 2") + .build(); + + // Get potential duplicates of item 10 and make sure no exceptions are thrown + List potentialDuplicates = new ArrayList<>(); + try { + potentialDuplicates = duplicateDetectionService.getPotentialDuplicates(context, item10); + } catch (SearchServiceException e) { + fail("Duplicate search with title and author (" + + e.getMessage() + ")"); + } + + // Make sure result list is size 1 + int size = 1; + assertEquals("Potential duplicates of item10 (title + author) should have size " + size, + size, potentialDuplicates.size()); + + // The only member should be item 11 since item 12 has a different author (but the same title + assertEquals("Item 11 should be be the detected duplicate", + item11.getID(), potentialDuplicates.get(0).getUuid()); + + } + +} diff --git a/dspace-api/src/test/java/org/dspace/content/EntityServiceImplTest.java b/dspace-api/src/test/java/org/dspace/content/EntityServiceImplTest.java index 305ebff697..11fbbb9f91 100644 --- a/dspace-api/src/test/java/org/dspace/content/EntityServiceImplTest.java +++ b/dspace-api/src/test/java/org/dspace/content/EntityServiceImplTest.java @@ -145,6 +145,7 @@ public class EntityServiceImplTest { // Declare objects utilized for this test Item item = mock(Item.class); Entity entity = mock(Entity.class); + EntityType entityType = mock(EntityType.class); RelationshipType relationshipType = mock(RelationshipType.class); relationshipType.setLeftType(leftType); relationshipType.setLeftType(rightType); @@ -156,7 +157,8 @@ public class EntityServiceImplTest { // Mock the state of objects utilized in getAllRelationshipTypes() // to meet the success criteria of the invocation when(entity.getItem()).thenReturn(item); - when(relationshipTypeService.findByEntityType(context, entityService.getType(context, entity), -1, -1)) + when(entityService.getType(context, entity)).thenReturn(entityType); + when(relationshipTypeService.findByEntityType(context, entityType, -1, -1)) .thenReturn(relationshipTypeList); // The relation(s) reported from our mocked Entity should match our relationshipList @@ -181,10 +183,9 @@ public class EntityServiceImplTest { // Mock the state of objects utilized in getLeftRelationshipTypes() // to meet the success criteria of the invocation - when(itemService.getMetadata(item, "dspace", "entity", "type", Item.ANY, false)).thenReturn(metsList); when(entity.getItem()).thenReturn(item); when(entityService.getType(context, entity)).thenReturn(entityType); - when(relationshipTypeService.findByEntityType(context, entityService.getType(context, entity), true, -1, -1)) + when(relationshipTypeService.findByEntityType(context, entityType, true, -1, -1)) .thenReturn(relationshipTypeList); // The left relationshipType(s) reported from our mocked Entity should match our relationshipList @@ -209,10 +210,9 @@ public class EntityServiceImplTest { // Mock the state of objects utilized in getRightRelationshipTypes() // to meet the success criteria of the invocation - when(itemService.getMetadata(item, "dspace", "entity", "type", Item.ANY, false)).thenReturn(metsList); when(entity.getItem()).thenReturn(item); when(entityService.getType(context, entity)).thenReturn(entityType); - when(relationshipTypeService.findByEntityType(context, entityService.getType(context, entity), false, -1, -1)) + when(relationshipTypeService.findByEntityType(context, entityType, false, -1, -1)) .thenReturn(relationshipTypeList); // The right relationshipType(s) reported from our mocked Entity should match our relationshipList diff --git a/dspace-api/src/test/java/org/dspace/content/EntityTypeServiceImplTest.java b/dspace-api/src/test/java/org/dspace/content/EntityTypeServiceImplTest.java index c54f0fc955..1137219103 100644 --- a/dspace-api/src/test/java/org/dspace/content/EntityTypeServiceImplTest.java +++ b/dspace-api/src/test/java/org/dspace/content/EntityTypeServiceImplTest.java @@ -122,7 +122,7 @@ public class EntityTypeServiceImplTest { } /** - * Helper method that reutrns new EntityType + * Helper method that returns new EntityType * @return new EntityType */ public EntityType makeEntityType() { diff --git a/dspace-api/src/test/java/org/dspace/content/ITCommunityCollection.java b/dspace-api/src/test/java/org/dspace/content/ITCommunityCollection.java index 03d0391ab8..668944fc97 100644 --- a/dspace-api/src/test/java/org/dspace/content/ITCommunityCollection.java +++ b/dspace-api/src/test/java/org/dspace/content/ITCommunityCollection.java @@ -200,7 +200,7 @@ public class ITCommunityCollection extends AbstractIntegrationTest { groupService.addMember(context, adminGroup, commAdmin); groupService.update(context, adminGroup); - // Create a hierachy of sub-Communities and Collections and Items. + // Create a hierarchy of sub-Communities and Collections and Items. Community child = communityService.createSubcommunity(context, parentCom); Community child2 = communityService.createSubcommunity(context, parentCom); Community child3 = communityService.createSubcommunity(context, parentCom); diff --git a/dspace-api/src/test/java/org/dspace/content/ItemComparatorTest.java b/dspace-api/src/test/java/org/dspace/content/ItemComparatorTest.java index 54ff9ce026..dc78008f3e 100644 --- a/dspace-api/src/test/java/org/dspace/content/ItemComparatorTest.java +++ b/dspace-api/src/test/java/org/dspace/content/ItemComparatorTest.java @@ -135,43 +135,43 @@ public class ItemComparatorTest extends AbstractUnitTest { int result; ItemComparator ic; - //one of the tiems has no value + //one of the items has no value ic = new ItemComparator("test", "one", Item.ANY, true); result = ic.compare(one, two); assertTrue("testCompare 0", result == 0); ic = new ItemComparator("test", "one", Item.ANY, true); - itemService.addMetadata(context, one, "dc", "test", "one", Item.ANY, "1"); + itemService.addMetadata(context, one, "dc", "test", "one", null, "1"); result = ic.compare(one, two); assertTrue("testCompare 1", result >= 1); itemService.clearMetadata(context, one, "dc", "test", "one", Item.ANY); ic = new ItemComparator("test", "one", Item.ANY, true); - itemService.addMetadata(context, two, "dc", "test", "one", Item.ANY, "1"); + itemService.addMetadata(context, two, "dc", "test", "one", null, "1"); result = ic.compare(one, two); assertTrue("testCompare 2", result <= -1); itemService.clearMetadata(context, two, "dc", "test", "one", Item.ANY); //value in both items ic = new ItemComparator("test", "one", Item.ANY, true); - itemService.addMetadata(context, one, "dc", "test", "one", Item.ANY, "1"); - itemService.addMetadata(context, two, "dc", "test", "one", Item.ANY, "2"); + itemService.addMetadata(context, one, "dc", "test", "one", null, "1"); + itemService.addMetadata(context, two, "dc", "test", "one", null, "2"); result = ic.compare(one, two); assertTrue("testCompare 3", result <= -1); itemService.clearMetadata(context, one, "dc", "test", "one", Item.ANY); itemService.clearMetadata(context, two, "dc", "test", "one", Item.ANY); ic = new ItemComparator("test", "one", Item.ANY, true); - itemService.addMetadata(context, one, "dc", "test", "one", Item.ANY, "1"); - itemService.addMetadata(context, two, "dc", "test", "one", Item.ANY, "1"); + itemService.addMetadata(context, one, "dc", "test", "one", null, "1"); + itemService.addMetadata(context, two, "dc", "test", "one", null, "1"); result = ic.compare(one, two); assertTrue("testCompare 4", result == 0); itemService.clearMetadata(context, one, "dc", "test", "one", Item.ANY); itemService.clearMetadata(context, two, "dc", "test", "one", Item.ANY); ic = new ItemComparator("test", "one", Item.ANY, true); - itemService.addMetadata(context, one, "dc", "test", "one", Item.ANY, "2"); - itemService.addMetadata(context, two, "dc", "test", "one", Item.ANY, "1"); + itemService.addMetadata(context, one, "dc", "test", "one", null, "2"); + itemService.addMetadata(context, two, "dc", "test", "one", null, "1"); result = ic.compare(one, two); assertTrue("testCompare 5", result >= 1); itemService.clearMetadata(context, one, "dc", "test", "one", Item.ANY); @@ -179,60 +179,60 @@ public class ItemComparatorTest extends AbstractUnitTest { //multiple values (min, max) ic = new ItemComparator("test", "one", Item.ANY, true); - itemService.addMetadata(context, one, "dc", "test", "one", Item.ANY, "0"); - itemService.addMetadata(context, one, "dc", "test", "one", Item.ANY, "1"); - itemService.addMetadata(context, two, "dc", "test", "one", Item.ANY, "2"); - itemService.addMetadata(context, two, "dc", "test", "one", Item.ANY, "3"); + itemService.addMetadata(context, one, "dc", "test", "one", null, "0"); + itemService.addMetadata(context, one, "dc", "test", "one", null, "1"); + itemService.addMetadata(context, two, "dc", "test", "one", null, "2"); + itemService.addMetadata(context, two, "dc", "test", "one", null, "3"); result = ic.compare(one, two); assertTrue("testCompare 3", result <= -1); itemService.clearMetadata(context, one, "dc", "test", "one", Item.ANY); itemService.clearMetadata(context, two, "dc", "test", "one", Item.ANY); ic = new ItemComparator("test", "one", Item.ANY, true); - itemService.addMetadata(context, one, "dc", "test", "one", Item.ANY, "0"); - itemService.addMetadata(context, one, "dc", "test", "one", Item.ANY, "1"); - itemService.addMetadata(context, two, "dc", "test", "one", Item.ANY, "-1"); - itemService.addMetadata(context, two, "dc", "test", "one", Item.ANY, "1"); + itemService.addMetadata(context, one, "dc", "test", "one", null, "0"); + itemService.addMetadata(context, one, "dc", "test", "one", null, "1"); + itemService.addMetadata(context, two, "dc", "test", "one", null, "-1"); + itemService.addMetadata(context, two, "dc", "test", "one", null, "1"); result = ic.compare(one, two); assertTrue("testCompare 4", result == 0); itemService.clearMetadata(context, one, "dc", "test", "one", Item.ANY); itemService.clearMetadata(context, two, "dc", "test", "one", Item.ANY); ic = new ItemComparator("test", "one", Item.ANY, true); - itemService.addMetadata(context, one, "dc", "test", "one", Item.ANY, "1"); - itemService.addMetadata(context, one, "dc", "test", "one", Item.ANY, "2"); - itemService.addMetadata(context, two, "dc", "test", "one", Item.ANY, "1"); - itemService.addMetadata(context, two, "dc", "test", "one", Item.ANY, "-1"); + itemService.addMetadata(context, one, "dc", "test", "one", null, "1"); + itemService.addMetadata(context, one, "dc", "test", "one", null, "2"); + itemService.addMetadata(context, two, "dc", "test", "one", null, "1"); + itemService.addMetadata(context, two, "dc", "test", "one", null, "-1"); result = ic.compare(one, two); assertTrue("testCompare 5", result >= 1); itemService.clearMetadata(context, one, "dc", "test", "one", Item.ANY); itemService.clearMetadata(context, two, "dc", "test", "one", Item.ANY); ic = new ItemComparator("test", "one", Item.ANY, false); - itemService.addMetadata(context, one, "dc", "test", "one", Item.ANY, "1"); - itemService.addMetadata(context, one, "dc", "test", "one", Item.ANY, "2"); - itemService.addMetadata(context, two, "dc", "test", "one", Item.ANY, "2"); - itemService.addMetadata(context, two, "dc", "test", "one", Item.ANY, "3"); + itemService.addMetadata(context, one, "dc", "test", "one", null, "1"); + itemService.addMetadata(context, one, "dc", "test", "one", null, "2"); + itemService.addMetadata(context, two, "dc", "test", "one", null, "2"); + itemService.addMetadata(context, two, "dc", "test", "one", null, "3"); result = ic.compare(one, two); assertTrue("testCompare 3", result <= -1); itemService.clearMetadata(context, one, "dc", "test", "one", Item.ANY); itemService.clearMetadata(context, two, "dc", "test", "one", Item.ANY); ic = new ItemComparator("test", "one", Item.ANY, false); - itemService.addMetadata(context, one, "dc", "test", "one", Item.ANY, "1"); - itemService.addMetadata(context, one, "dc", "test", "one", Item.ANY, "2"); - itemService.addMetadata(context, two, "dc", "test", "one", Item.ANY, "1"); - itemService.addMetadata(context, two, "dc", "test", "one", Item.ANY, "5"); + itemService.addMetadata(context, one, "dc", "test", "one", null, "1"); + itemService.addMetadata(context, one, "dc", "test", "one", null, "2"); + itemService.addMetadata(context, two, "dc", "test", "one", null, "1"); + itemService.addMetadata(context, two, "dc", "test", "one", null, "5"); result = ic.compare(one, two); assertTrue("testCompare 4", result == 0); itemService.clearMetadata(context, one, "dc", "test", "one", Item.ANY); itemService.clearMetadata(context, two, "dc", "test", "one", Item.ANY); ic = new ItemComparator("test", "one", Item.ANY, false); - itemService.addMetadata(context, one, "dc", "test", "one", Item.ANY, "2"); - itemService.addMetadata(context, one, "dc", "test", "one", Item.ANY, "3"); - itemService.addMetadata(context, two, "dc", "test", "one", Item.ANY, "1"); - itemService.addMetadata(context, two, "dc", "test", "one", Item.ANY, "4"); + itemService.addMetadata(context, one, "dc", "test", "one", null, "2"); + itemService.addMetadata(context, one, "dc", "test", "one", null, "3"); + itemService.addMetadata(context, two, "dc", "test", "one", null, "1"); + itemService.addMetadata(context, two, "dc", "test", "one", null, "4"); result = ic.compare(one, two); assertTrue("testCompare 5", result >= 1); itemService.clearMetadata(context, one, "dc", "test", "one", Item.ANY); diff --git a/dspace-api/src/test/java/org/dspace/content/ItemTest.java b/dspace-api/src/test/java/org/dspace/content/ItemTest.java index aaa28769dc..00dbf2994d 100644 --- a/dspace-api/src/test/java/org/dspace/content/ItemTest.java +++ b/dspace-api/src/test/java/org/dspace/content/ItemTest.java @@ -518,11 +518,11 @@ public class ItemTest extends AbstractDSpaceObjectTest { String schema = "dc"; String element = "contributor"; String qualifier = "author"; - String lang = Item.ANY; + String lang = null; String[] values = {"value0", "value1"}; itemService.addMetadata(context, it, schema, element, qualifier, lang, Arrays.asList(values)); - List dc = itemService.getMetadata(it, schema, element, qualifier, lang); + List dc = itemService.getMetadata(it, schema, element, qualifier, Item.ANY); assertThat("testAddMetadata_5args_1 0", dc, notNullValue()); assertTrue("testAddMetadata_5args_1 1", dc.size() == 2); assertThat("testAddMetadata_5args_1 2", dc.get(0).getMetadataField().getMetadataSchema().getName(), @@ -544,7 +544,7 @@ public class ItemTest extends AbstractDSpaceObjectTest { String schema = "dc"; String element = "contributor"; String qualifier = "author"; - String lang = Item.ANY; + String lang = null; String[] values = {}; itemService.addMetadata(context, it, schema, element, qualifier, lang, Arrays.asList(values)); fail("IllegalArgumentException expected"); @@ -563,13 +563,13 @@ public class ItemTest extends AbstractDSpaceObjectTest { String schema = "dc"; String element = "language"; String qualifier = "iso"; - String lang = Item.ANY; + String lang = null; List values = Arrays.asList("en_US", "en"); List authorities = Arrays.asList("accepted", "uncertain"); List confidences = Arrays.asList(0, 0); itemService.addMetadata(context, it, schema, element, qualifier, lang, values, authorities, confidences); - List dc = itemService.getMetadata(it, schema, element, qualifier, lang); + List dc = itemService.getMetadata(it, schema, element, qualifier, Item.ANY); assertThat("testAddMetadata_7args_1 0", dc, notNullValue()); assertTrue("testAddMetadata_7args_1 1", dc.size() == 2); assertThat("testAddMetadata_7args_1 2", dc.get(0).getMetadataField().getMetadataSchema().getName(), @@ -600,13 +600,13 @@ public class ItemTest extends AbstractDSpaceObjectTest { String schema = "dc"; String element = "contributor"; String qualifier = "author"; - String lang = Item.ANY; + String lang = null; List values = Arrays.asList("value0", "value1"); List authorities = Arrays.asList("auth0", "auth2"); List confidences = Arrays.asList(0, 0); itemService.addMetadata(context, it, schema, element, qualifier, lang, values, authorities, confidences); - List dc = itemService.getMetadata(it, schema, element, qualifier, lang); + List dc = itemService.getMetadata(it, schema, element, qualifier, Item.ANY); assertThat("testAddMetadata_7args_1 0", dc, notNullValue()); assertTrue("testAddMetadata_7args_1 1", dc.size() == 2); assertThat("testAddMetadata_7args_1 2", dc.get(0).getMetadataField().getMetadataSchema().getName(), @@ -632,7 +632,7 @@ public class ItemTest extends AbstractDSpaceObjectTest { String schema = "dc"; String element = "contributor"; String qualifier = "author"; - String lang = Item.ANY; + String lang = null; List values = new ArrayList(); List authorities = new ArrayList(); List confidences = new ArrayList(); @@ -645,7 +645,7 @@ public class ItemTest extends AbstractDSpaceObjectTest { String schema = "dc"; String element = "contributor"; String qualifier = "author"; - String lang = Item.ANY; + String lang = null; // Create two fake virtual metadata ("virtual::[relationship-id]") values List values = new ArrayList<>(Arrays.asList("uuid-1", "uuid-2")); List authorities = new ArrayList<>(Arrays.asList(Constants.VIRTUAL_AUTHORITY_PREFIX + "relationship-1", @@ -674,7 +674,7 @@ public class ItemTest extends AbstractDSpaceObjectTest { assertEquals(1, valuesAdded.size()); // Get metadata and ensure new value is the ONLY ONE for this metadata field - List dc = itemService.getMetadata(it, schema, element, qualifier, lang); + List dc = itemService.getMetadata(it, schema, element, qualifier, Item.ANY); assertNotNull(dc); assertEquals(1, dc.size()); assertEquals(schema, dc.get(0).getMetadataField().getMetadataSchema().getName()); @@ -693,11 +693,11 @@ public class ItemTest extends AbstractDSpaceObjectTest { String schema = "dc"; String element = "contributor"; String qualifier = "author"; - String lang = Item.ANY; + String lang = null; String value = "value0"; itemService.addMetadata(context, it, schema, element, qualifier, lang, value); - List dc = itemService.getMetadata(it, schema, element, qualifier, lang); + List dc = itemService.getMetadata(it, schema, element, qualifier, Item.ANY); assertThat("testAddMetadata_5args_2 0", dc, notNullValue()); assertTrue("testAddMetadata_5args_2 1", dc.size() == 1); assertThat("testAddMetadata_5args_2 2", dc.get(0).getMetadataField().getMetadataSchema().getName(), @@ -719,13 +719,13 @@ public class ItemTest extends AbstractDSpaceObjectTest { String schema = "dc"; String element = "language"; String qualifier = "iso"; - String lang = Item.ANY; + String lang = null; String values = "en"; String authorities = "accepted"; int confidences = 0; itemService.addMetadata(context, it, schema, element, qualifier, lang, values, authorities, confidences); - List dc = itemService.getMetadata(it, schema, element, qualifier, lang); + List dc = itemService.getMetadata(it, schema, element, qualifier, Item.ANY); assertThat("testAddMetadata_7args_2 0", dc, notNullValue()); assertTrue("testAddMetadata_7args_2 1", dc.size() == 1); assertThat("testAddMetadata_7args_2 2", dc.get(0).getMetadataField().getMetadataSchema().getName(), @@ -748,13 +748,13 @@ public class ItemTest extends AbstractDSpaceObjectTest { String schema = "dc"; String element = "contributor"; String qualifier = "editor"; - String lang = Item.ANY; + String lang = null; String values = "value0"; String authorities = "auth0"; int confidences = 0; itemService.addMetadata(context, it, schema, element, qualifier, lang, values, authorities, confidences); - List dc = itemService.getMetadata(it, schema, element, qualifier, lang); + List dc = itemService.getMetadata(it, schema, element, qualifier, Item.ANY); assertThat("testAddMetadata_7args_2 0", dc, notNullValue()); assertTrue("testAddMetadata_7args_2 1", dc.size() == 1); assertThat("testAddMetadata_7args_2 2", dc.get(0).getMetadataField().getMetadataSchema().getName(), @@ -772,7 +772,7 @@ public class ItemTest extends AbstractDSpaceObjectTest { String schema = "dc"; String element = "contributor"; String qualifier = "author"; - String lang = Item.ANY; + String lang = null; // Create a single fake virtual metadata ("virtual::[relationship-id]") value String value = "uuid-1"; String authority = Constants.VIRTUAL_AUTHORITY_PREFIX + "relationship-1"; @@ -786,7 +786,7 @@ public class ItemTest extends AbstractDSpaceObjectTest { assertNull(valuesAdded); // Verify this metadata field does NOT exist on the item - List mv = itemService.getMetadata(it, schema, element, qualifier, lang); + List mv = itemService.getMetadata(it, schema, element, qualifier, Item.ANY); assertNotNull(mv); assertTrue(mv.isEmpty()); @@ -797,7 +797,7 @@ public class ItemTest extends AbstractDSpaceObjectTest { assertNull(valuesAdded); // Verify this metadata field does NOT exist on the item - mv = itemService.getMetadata(it, schema, element, qualifier, lang); + mv = itemService.getMetadata(it, schema, element, qualifier, Item.ANY); assertNotNull(mv); assertTrue(mv.isEmpty()); } @@ -811,13 +811,13 @@ public class ItemTest extends AbstractDSpaceObjectTest { String schema = "dc"; String element = "contributor"; String qualifier = "author"; - String lang = Item.ANY; + String lang = null; String values = "value0"; itemService.addMetadata(context, it, schema, element, qualifier, lang, values); - itemService.clearMetadata(context, it, schema, element, qualifier, lang); + itemService.clearMetadata(context, it, schema, element, qualifier, Item.ANY); - List dc = itemService.getMetadata(it, schema, element, qualifier, lang); + List dc = itemService.getMetadata(it, schema, element, qualifier, Item.ANY); assertThat("testClearMetadata 0", dc, notNullValue()); assertTrue("testClearMetadata 1", dc.size() == 0); } @@ -859,11 +859,11 @@ public class ItemTest extends AbstractDSpaceObjectTest { context.turnOffAuthorisationSystem(); Collection collection = collectionService.create(context, owningCommunity); collectionService.setMetadataSingleValue(context, collection, MetadataSchemaEnum.DC.getName(), - "title", null, Item.ANY, "collection B"); + "title", null, null, "collection B"); it.addCollection(collection); collection = collectionService.create(context, owningCommunity); collectionService.setMetadataSingleValue(context, collection, MetadataSchemaEnum.DC.getName(), - "title", null, Item.ANY, "collection A"); + "title", null, null, "collection A"); it.addCollection(collection); context.restoreAuthSystemState(); assertThat("testGetCollections 0", it.getCollections(), notNullValue()); @@ -1772,7 +1772,7 @@ public class ItemTest extends AbstractDSpaceObjectTest { // add new metadata to item context.turnOffAuthorisationSystem(); - itemService.addMetadata(context, it, schema, element, qualifier, Item.ANY, value); + itemService.addMetadata(context, it, schema, element, qualifier, null, value); itemService.update(context, it); context.restoreAuthSystemState(); @@ -1837,7 +1837,7 @@ public class ItemTest extends AbstractDSpaceObjectTest { // add new metadata (with authority) to item context.turnOffAuthorisationSystem(); - itemService.addMetadata(context, it, schema, element, qualifier, Item.ANY, value, authority, confidence); + itemService.addMetadata(context, it, schema, element, qualifier, null, value, authority, confidence); itemService.update(context, it); context.restoreAuthSystemState(); diff --git a/dspace-api/src/test/java/org/dspace/content/RelationshipServiceImplPlaceTest.java b/dspace-api/src/test/java/org/dspace/content/RelationshipServiceImplPlaceTest.java index 3e36f77c68..b33063a1fa 100644 --- a/dspace-api/src/test/java/org/dspace/content/RelationshipServiceImplPlaceTest.java +++ b/dspace-api/src/test/java/org/dspace/content/RelationshipServiceImplPlaceTest.java @@ -429,7 +429,7 @@ public class RelationshipServiceImplPlaceTest extends AbstractUnitTest { context.turnOffAuthorisationSystem(); - // Create a relationship with this item with a spcific place + // Create a relationship with this item with a specific place Relationship relationship = relationshipService.create(context, item, authorItem, isAuthorOfPublication, 1, -1); context.restoreAuthSystemState(); diff --git a/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsIT.java b/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsIT.java index 44653300e0..3acc4ca146 100644 --- a/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsIT.java +++ b/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsIT.java @@ -49,6 +49,7 @@ import org.dspace.builder.EntityTypeBuilder; import org.dspace.builder.ItemBuilder; import org.dspace.builder.RelationshipBuilder; import org.dspace.builder.RelationshipTypeBuilder; +import org.dspace.builder.VersionBuilder; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.InstallItemService; import org.dspace.content.service.ItemService; @@ -62,8 +63,6 @@ import org.dspace.discovery.SolrSearchCore; import org.dspace.kernel.ServiceManager; import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.versioning.Version; -import org.dspace.versioning.factory.VersionServiceFactory; -import org.dspace.versioning.service.VersioningService; import org.hamcrest.Matcher; import org.junit.Assert; import org.junit.Before; @@ -74,8 +73,6 @@ public class VersioningWithRelationshipsIT extends AbstractIntegrationTestWithDa private final RelationshipService relationshipService = ContentServiceFactory.getInstance().getRelationshipService(); - private final VersioningService versioningService = - VersionServiceFactory.getInstance().getVersionService(); private final WorkspaceItemService workspaceItemService = ContentServiceFactory.getInstance().getWorkspaceItemService(); private final InstallItemService installItemService = @@ -84,7 +81,6 @@ public class VersioningWithRelationshipsIT extends AbstractIntegrationTestWithDa ContentServiceFactory.getInstance().getItemService(); private final SolrSearchCore solrSearchCore = DSpaceServicesFactory.getInstance().getServiceManager().getServicesByType(SolrSearchCore.class).get(0); - protected Community community; protected Collection collection; protected EntityType publicationEntityType; @@ -291,7 +287,7 @@ public class VersioningWithRelationshipsIT extends AbstractIntegrationTestWithDa // create a new version of the publication // ///////////////////////////////////////////// - Version newVersion = versioningService.createNewVersion(context, originalPublication); + Version newVersion = VersionBuilder.createVersion(context, originalPublication, "test").build(); Item newPublication = newVersion.getItem(); assertNotSame(originalPublication, newPublication); @@ -567,7 +563,7 @@ public class VersioningWithRelationshipsIT extends AbstractIntegrationTestWithDa // create a new version of the publication // ///////////////////////////////////////////// - Version newVersion = versioningService.createNewVersion(context, originalPublication); + Version newVersion = VersionBuilder.createVersion(context, originalPublication, "test").build(); Item newPublication = newVersion.getItem(); assertNotSame(originalPublication, newPublication); @@ -927,7 +923,7 @@ public class VersioningWithRelationshipsIT extends AbstractIntegrationTestWithDa // create a new version of the person // //////////////////////////////////////// - Version newVersion = versioningService.createNewVersion(context, originalPerson); + Version newVersion = VersionBuilder.createVersion(context, originalPerson, "test").build(); Item newPerson = newVersion.getItem(); assertNotSame(originalPerson, newPerson); @@ -1300,7 +1296,7 @@ public class VersioningWithRelationshipsIT extends AbstractIntegrationTestWithDa // create new version of publication // /////////////////////////////////////// - Version newVersion = versioningService.createNewVersion(context, originalPublication); + Version newVersion = VersionBuilder.createVersion(context, originalPublication, "test").build(); Item newPublication = newVersion.getItem(); assertNotSame(originalPublication, newPublication); @@ -1463,7 +1459,7 @@ public class VersioningWithRelationshipsIT extends AbstractIntegrationTestWithDa // create a new version of the publication // ///////////////////////////////////////////// - Version newVersion = versioningService.createNewVersion(context, originalPublication); + Version newVersion = VersionBuilder.createVersion(context, originalPublication, "test").build(); Item newPublication = newVersion.getItem(); assertNotSame(originalPublication, newPublication); @@ -1782,7 +1778,7 @@ public class VersioningWithRelationshipsIT extends AbstractIntegrationTestWithDa // create new version - volume 1.2 // ///////////////////////////////////// - Item v1_2 = versioningService.createNewVersion(context, v1_1).getItem(); + Item v1_2 = VersionBuilder.createVersion(context, v1_1, "test").build().getItem(); installItemService.installItem(context, workspaceItemService.findByItem(context, v1_2)); context.commit(); @@ -1790,7 +1786,7 @@ public class VersioningWithRelationshipsIT extends AbstractIntegrationTestWithDa // create new version - issue 3.2 // //////////////////////////////////// - Item i3_2 = versioningService.createNewVersion(context, i3_1).getItem(); + Item i3_2 = VersionBuilder.createVersion(context, i3_1, "test").build().getItem(); installItemService.installItem(context, workspaceItemService.findByItem(context, i3_2)); context.commit(); @@ -2316,7 +2312,7 @@ public class VersioningWithRelationshipsIT extends AbstractIntegrationTestWithDa // create new version - person 3.2 // ///////////////////////////////////// - Item pe3_2 = versioningService.createNewVersion(context, pe3_1).getItem(); + Item pe3_2 = VersionBuilder.createVersion(context, pe3_1, "test").build().getItem(); installItemService.installItem(context, workspaceItemService.findByItem(context, pe3_2)); context.commit(); @@ -2324,7 +2320,7 @@ public class VersioningWithRelationshipsIT extends AbstractIntegrationTestWithDa // create new version - project 3.2 // ////////////////////////////////////// - Item pr3_2 = versioningService.createNewVersion(context, pr3_1).getItem(); + Item pr3_2 = VersionBuilder.createVersion(context, pr3_1, "test").build().getItem(); installItemService.installItem(context, workspaceItemService.findByItem(context, pr3_2)); context.commit(); @@ -3056,7 +3052,7 @@ public class VersioningWithRelationshipsIT extends AbstractIntegrationTestWithDa // create new version - volume 1.2 // ///////////////////////////////////// - Item v1_2 = versioningService.createNewVersion(context, v1_1).getItem(); + Item v1_2 = VersionBuilder.createVersion(context, v1_1, "test").build().getItem(); installItemService.installItem(context, workspaceItemService.findByItem(context, v1_2)); context.commit(); @@ -3064,7 +3060,7 @@ public class VersioningWithRelationshipsIT extends AbstractIntegrationTestWithDa // create new version - issue 3.2 // //////////////////////////////////// - Item i3_2 = versioningService.createNewVersion(context, i3_1).getItem(); + Item i3_2 = VersionBuilder.createVersion(context, i3_1, "test").build().getItem(); installItemService.installItem(context, workspaceItemService.findByItem(context, i3_2)); context.commit(); @@ -3509,7 +3505,7 @@ public class VersioningWithRelationshipsIT extends AbstractIntegrationTestWithDa // create a new version of publication 1 and archive // /////////////////////////////////////////////////////// - Item publication1V2 = versioningService.createNewVersion(context, publication1V1).getItem(); + Item publication1V2 = VersionBuilder.createVersion(context, publication1V1, "test").build().getItem(); installItemService.installItem(context, workspaceItemService.findByItem(context, publication1V2)); context.dispatchEvents(); @@ -3517,7 +3513,7 @@ public class VersioningWithRelationshipsIT extends AbstractIntegrationTestWithDa // create new version of person 1 // //////////////////////////////////// - Item person1V2 = versioningService.createNewVersion(context, person1V1).getItem(); + Item person1V2 = VersionBuilder.createVersion(context, person1V1, "test").build().getItem(); // update "Smith, Donald" to "Smith, D." itemService.replaceMetadata( context, person1V2, "person", "givenName", null, null, "D.", @@ -3853,7 +3849,7 @@ public class VersioningWithRelationshipsIT extends AbstractIntegrationTestWithDa // create new version of person 2 // //////////////////////////////////// - Item person2V2 = versioningService.createNewVersion(context, person2V1).getItem(); + Item person2V2 = VersionBuilder.createVersion(context, person2V1, "test").build().getItem(); Relationship rel1 = getRelationship(publication1V2, isAuthorOfPublication, person2V2); assertNotNull(rel1); rel1.setRightwardValue("Doe, Jane Jr"); diff --git a/dspace-api/src/test/java/org/dspace/content/dao/RelationshipDAOImplIT.java b/dspace-api/src/test/java/org/dspace/content/dao/RelationshipDAOImplIT.java index 2d08223b2e..8e48c70884 100644 --- a/dspace-api/src/test/java/org/dspace/content/dao/RelationshipDAOImplIT.java +++ b/dspace-api/src/test/java/org/dspace/content/dao/RelationshipDAOImplIT.java @@ -72,7 +72,7 @@ public class RelationshipDAOImplIT extends AbstractIntegrationTest { protected EntityTypeService entityTypeService = ContentServiceFactory.getInstance().getEntityTypeService(); /** - * Initalize DSpace objects used for testing for each test + * Initialize DSpace objects used for testing for each test */ @Before @Override @@ -87,8 +87,8 @@ public class RelationshipDAOImplIT extends AbstractIntegrationTest { WorkspaceItem workspaceItemTwo = workspaceItemService.create(context, collection, false); itemOne = installItemService.installItem(context, workspaceItem); itemTwo = installItemService.installItem(context, workspaceItemTwo); - itemService.addMetadata(context, itemOne, "dspace", "entity", "type", Item.ANY, "Publication"); - itemService.addMetadata(context, itemTwo, "dspace", "entity", "type", Item.ANY, "Person"); + itemService.addMetadata(context, itemOne, "dspace", "entity", "type", null, "Publication"); + itemService.addMetadata(context, itemTwo, "dspace", "entity", "type", null, "Person"); itemService.update(context, itemOne); itemService.update(context, itemTwo); entityTypeOne = entityTypeService.create(context, "Person"); @@ -106,7 +106,7 @@ public class RelationshipDAOImplIT extends AbstractIntegrationTest { } /** - * Delete all initalized DSpace objects after each test + * Delete all initialized DSpace objects after each test */ @After @Override diff --git a/dspace-api/src/test/java/org/dspace/content/dao/RelationshipTypeDAOImplIT.java b/dspace-api/src/test/java/org/dspace/content/dao/RelationshipTypeDAOImplIT.java index ff7d03b49f..315fabba9e 100644 --- a/dspace-api/src/test/java/org/dspace/content/dao/RelationshipTypeDAOImplIT.java +++ b/dspace-api/src/test/java/org/dspace/content/dao/RelationshipTypeDAOImplIT.java @@ -68,7 +68,7 @@ public class RelationshipTypeDAOImplIT extends AbstractIntegrationTest { protected EntityTypeService entityTypeService = ContentServiceFactory.getInstance().getEntityTypeService(); /** - * Initalize DSpace objects used for testing for each test + * Initialize DSpace objects used for testing for each test */ @Before @Override @@ -82,8 +82,8 @@ public class RelationshipTypeDAOImplIT extends AbstractIntegrationTest { WorkspaceItem workspaceItemTwo = workspaceItemService.create(context, collection, false); itemOne = installItemService.installItem(context, workspaceItem); itemTwo = installItemService.installItem(context, workspaceItemTwo); - itemService.addMetadata(context, itemOne, "dspace", "entity", "type", Item.ANY, "Publication"); - itemService.addMetadata(context, itemTwo, "dspace", "entity", "type", Item.ANY, "Person"); + itemService.addMetadata(context, itemOne, "dspace", "entity", "type", null, "Publication"); + itemService.addMetadata(context, itemTwo, "dspace", "entity", "type", null, "Person"); itemService.update(context, itemOne); itemService.update(context, itemTwo); entityTypeOne = entityTypeService.create(context, "Person"); @@ -101,7 +101,7 @@ public class RelationshipTypeDAOImplIT extends AbstractIntegrationTest { } /** - * Delete all initalized DSpace objects after each test + * Delete all initialized DSpace objects after each test */ @After @Override diff --git a/dspace-api/src/test/java/org/dspace/content/packager/ITDSpaceAIP.java b/dspace-api/src/test/java/org/dspace/content/packager/ITDSpaceAIP.java index c5a73ed539..b9cbc36f71 100644 --- a/dspace-api/src/test/java/org/dspace/content/packager/ITDSpaceAIP.java +++ b/dspace-api/src/test/java/org/dspace/content/packager/ITDSpaceAIP.java @@ -146,7 +146,7 @@ public class ITDSpaceAIP extends AbstractIntegrationTest { InstallItemService installItemService = ContentServiceFactory.getInstance().getInstallItemService(); log.info("setUpClass() - CREATE TEST HIERARCHY"); - // Create a hierachy of sub-Communities and Collections and Items, + // Create a hierarchy of sub-Communities and Collections and Items, // which looks like this: // "Top Community" // - "Child Community" diff --git a/dspace-api/src/test/java/org/dspace/content/packager/PackageUtilsTest.java b/dspace-api/src/test/java/org/dspace/content/packager/PackageUtilsTest.java index ae68600124..06020be513 100644 --- a/dspace-api/src/test/java/org/dspace/content/packager/PackageUtilsTest.java +++ b/dspace-api/src/test/java/org/dspace/content/packager/PackageUtilsTest.java @@ -79,7 +79,7 @@ public class PackageUtilsTest extends AbstractUnitTest { CollectionService collectionService = ContentServiceFactory.getInstance().getCollectionService(); log.info("setUpClass() - CREATE TEST HIERARCHY"); - // Create a hierachy of sub-Communities and Collections + // Create a hierarchy of sub-Communities and Collections // which looks like this: // "Top Community" // - "Child Community" diff --git a/dspace-api/src/test/java/org/dspace/content/service/ItemServiceIT.java b/dspace-api/src/test/java/org/dspace/content/service/ItemServiceIT.java index 2ff7dd21da..eee445b333 100644 --- a/dspace-api/src/test/java/org/dspace/content/service/ItemServiceIT.java +++ b/dspace-api/src/test/java/org/dspace/content/service/ItemServiceIT.java @@ -11,7 +11,9 @@ import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.hasSize; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.ByteArrayInputStream; @@ -19,6 +21,9 @@ import java.io.InputStream; import java.sql.SQLException; import java.util.Comparator; import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; import java.util.stream.Collectors; import org.apache.logging.log4j.Logger; @@ -44,11 +49,15 @@ import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.EntityType; import org.dspace.content.Item; +import org.dspace.content.MetadataField; +import org.dspace.content.MetadataSchema; import org.dspace.content.MetadataValue; import org.dspace.content.Relationship; import org.dspace.content.RelationshipType; import org.dspace.content.WorkspaceItem; import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.contentreport.QueryOperator; +import org.dspace.contentreport.QueryPredicate; import org.dspace.core.Constants; import org.dspace.eperson.Group; import org.dspace.eperson.factory.EPersonServiceFactory; @@ -71,6 +80,9 @@ public class ItemServiceIT extends AbstractIntegrationTestWithDatabase { protected ItemService itemService = ContentServiceFactory.getInstance().getItemService(); protected InstallItemService installItemService = ContentServiceFactory.getInstance().getInstallItemService(); protected WorkspaceItemService workspaceItemService = ContentServiceFactory.getInstance().getWorkspaceItemService(); + protected MetadataSchemaService metadataSchemaService = + ContentServiceFactory.getInstance().getMetadataSchemaService(); + protected MetadataFieldService metadataFieldService = ContentServiceFactory.getInstance().getMetadataFieldService(); protected MetadataValueService metadataValueService = ContentServiceFactory.getInstance().getMetadataValueService(); protected VersioningService versioningService = VersionServiceFactory.getInstance().getVersionService(); protected AuthorizeService authorizeService = AuthorizeServiceFactory.getInstance().getAuthorizeService(); @@ -78,6 +90,8 @@ public class ItemServiceIT extends AbstractIntegrationTestWithDatabase { Community community; Collection collection1; + MetadataSchema schemaDC; + MetadataField fieldAuthor; Item item; @@ -99,6 +113,9 @@ public class ItemServiceIT extends AbstractIntegrationTestWithDatabase { try { context.turnOffAuthorisationSystem(); + schemaDC = metadataSchemaService.find(context, "dc"); + fieldAuthor = metadataFieldService.findByElement(context, schemaDC, "contributor", "author"); + community = CommunityBuilder.createCommunity(context) .build(); @@ -142,7 +159,7 @@ public class ItemServiceIT extends AbstractIntegrationTestWithDatabase { // check the correct order using default method `getMetadata` List defaultMetadata = - this.itemService.getMetadata(item, dcSchema, contributorElement, authorQualifier, Item.ANY); + itemService.getMetadata(item, dcSchema, contributorElement, authorQualifier, Item.ANY); assertThat(defaultMetadata,hasSize(3)); @@ -158,7 +175,7 @@ public class ItemServiceIT extends AbstractIntegrationTestWithDatabase { // check the correct order using the method `getMetadata` without virtual fields List nonVirtualMetadatas = - this.itemService.getMetadata(item, dcSchema, contributorElement, authorQualifier, Item.ANY, false); + itemService.getMetadata(item, dcSchema, contributorElement, authorQualifier, Item.ANY, false); // if we don't reload the item the place order is not applied correctly // item = context.reloadEntity(item); @@ -180,19 +197,19 @@ public class ItemServiceIT extends AbstractIntegrationTestWithDatabase { item = context.reloadEntity(item); // now just add one metadata to be the last - this.itemService.addMetadata( - context, item, dcSchema, contributorElement, authorQualifier, Item.ANY, "test, latest", null, 0 + itemService.addMetadata( + context, item, dcSchema, contributorElement, authorQualifier, null, "test, latest", null, 0 ); // now just remove first metadata - this.itemService.removeMetadataValues(context, item, List.of(placeZero)); + itemService.removeMetadataValues(context, item, List.of(placeZero)); // now just add one metadata to place 0 - this.itemService.addAndShiftRightMetadata( + itemService.addAndShiftRightMetadata( context, item, dcSchema, contributorElement, authorQualifier, Item.ANY, "test, new", null, 0, 0 ); // check the metadata using method `getMetadata` defaultMetadata = - this.itemService.getMetadata(item, dcSchema, contributorElement, authorQualifier, Item.ANY); + itemService.getMetadata(item, dcSchema, contributorElement, authorQualifier, Item.ANY); // check correct places assertThat(defaultMetadata,hasSize(4)); @@ -212,7 +229,7 @@ public class ItemServiceIT extends AbstractIntegrationTestWithDatabase { // check metadata using nonVirtualMethod nonVirtualMetadatas = - this.itemService.getMetadata(item, dcSchema, contributorElement, authorQualifier, Item.ANY, false); + itemService.getMetadata(item, dcSchema, contributorElement, authorQualifier, Item.ANY, false); // check correct places assertThat(nonVirtualMetadatas,hasSize(4)); @@ -244,7 +261,7 @@ public class ItemServiceIT extends AbstractIntegrationTestWithDatabase { // check after commit defaultMetadata = - this.itemService.getMetadata(item, dcSchema, contributorElement, authorQualifier, Item.ANY); + itemService.getMetadata(item, dcSchema, contributorElement, authorQualifier, Item.ANY); // check correct places assertThat(defaultMetadata,hasSize(4)); @@ -264,7 +281,7 @@ public class ItemServiceIT extends AbstractIntegrationTestWithDatabase { // check metadata using nonVirtualMethod nonVirtualMetadatas = - this.itemService.getMetadata(item, dcSchema, contributorElement, authorQualifier, Item.ANY, false); + itemService.getMetadata(item, dcSchema, contributorElement, authorQualifier, Item.ANY, false); // check correct places assertThat(nonVirtualMetadatas,hasSize(4)); @@ -912,4 +929,65 @@ public class ItemServiceIT extends AbstractIntegrationTestWithDatabase { assertThat(metadataValue.getAuthority(), equalTo(authority)); assertThat(metadataValue.getPlace(), equalTo(place)); } + + @Test + public void testFindByMetadataQuery() throws Exception { + context.turnOffAuthorisationSystem(); + + // Here we add an author to the item + MetadataValue mv = itemService.addMetadata(context, item, dcSchema, contributorElement, + authorQualifier, null, "test, one"); + context.commit(); + + item = context.reloadEntity(item); + + assertNotNull(mv); + MetadataField mf = mv.getMetadataField(); + assertEquals(fieldAuthor, mf); + MetadataSchema ms = mf.getMetadataSchema(); + assertNotNull(ms); + assertEquals(dcSchema, ms.getName()); + + // We check whether the author metadata was properly added. + List mvs = item.getMetadata(); + MetadataValue mvAuthor1 = mvs.stream() + .filter(mv1 -> Objects.equals(mv1.getMetadataField().getElement(), "contributor")) + .filter(mv1 -> Objects.equals(mv1.getMetadataField().getQualifier(), "author")) + .findFirst() + .orElse(null); + assertNotNull(mvAuthor1); + assertEquals("test, one", mvAuthor1.getValue()); + + assertMetadataValue( + authorQualifier, contributorElement, dcSchema, "test, one", null, 0, mvAuthor1 + ); + + assertEquals(collection1, item.getOwningCollection()); + + List collectionUuids = List.of(collection1.getID()); + + // First test: we should not find anything. + QueryPredicate predicate = QueryPredicate.of(fieldAuthor, QueryOperator.MATCHES, ".*whatever.*"); + List items = itemService.findByMetadataQuery(context, List.of(predicate), collectionUuids, 0, -1); + assertTrue(items.isEmpty()); + + // Second test: we search against the metadata value specified above. + predicate = QueryPredicate.of(fieldAuthor, QueryOperator.EQUALS, "test, one"); + items = itemService.findByMetadataQuery(context, List.of(predicate), collectionUuids, 0, -1); + assertEquals(1, items.size()); + + Item item = items.get(0); + assertNotNull(item); + List allMetadata = item.getMetadata(); + Optional mvAuthor = allMetadata.stream() + .filter(md -> Objects.equals(dcSchema, md.getMetadataField().getMetadataSchema().getName())) + .filter(md -> Objects.equals(contributorElement, md.getMetadataField().getElement())) + .filter(md -> Objects.equals(authorQualifier, md.getMetadataField().getQualifier())) + .findFirst(); + assertTrue(mvAuthor.isPresent()); + assertEquals("test, one", mvAuthor.get().getValue()); + + context.restoreAuthSystemState(); + } + } diff --git a/dspace-api/src/test/java/org/dspace/content/virtual/ConcatenateTest.java b/dspace-api/src/test/java/org/dspace/content/virtual/ConcatenateTest.java index 52457a23d7..f39f2d8fd9 100644 --- a/dspace-api/src/test/java/org/dspace/content/virtual/ConcatenateTest.java +++ b/dspace-api/src/test/java/org/dspace/content/virtual/ConcatenateTest.java @@ -61,11 +61,11 @@ public class ConcatenateTest { @Test public void testGetSeperator() { // Setup objects utilized in unit test - String seperator = ","; + String separator = ","; concatenate.setSeparator(","); - // The reported seperator should match our defined seperator - assertEquals("TestGetSeperator 0", seperator, concatenate.getSeparator()); + // The reported separator should match our defined separator + assertEquals("TestGetSeperator 0", separator, concatenate.getSeparator()); } @Test @@ -73,7 +73,7 @@ public class ConcatenateTest { // Setup objects utilized in unit test concatenate.setSeparator(","); - // The reported seperator should match our defined seperator + // The reported separator should match our defined separator assertEquals("TestSetSeperator 0", ",", concatenate.getSeparator()); } @@ -82,7 +82,7 @@ public class ConcatenateTest { // Setup objects utilized in unit test concatenate.setUseForPlace(true); - // The reported seperator should match our defined seperator + // The reported separator should match our defined separator assertEquals("TestSetUseForPlace 0", true, concatenate.getUseForPlace()); } diff --git a/dspace-api/src/test/java/org/dspace/ctask/general/CreateMissingIdentifiersIT.java b/dspace-api/src/test/java/org/dspace/ctask/general/CreateMissingIdentifiersIT.java index 2a07799dee..8038a71533 100644 --- a/dspace-api/src/test/java/org/dspace/ctask/general/CreateMissingIdentifiersIT.java +++ b/dspace-api/src/test/java/org/dspace/ctask/general/CreateMissingIdentifiersIT.java @@ -10,6 +10,8 @@ package org.dspace.ctask.general; import static org.junit.Assert.assertEquals; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; import org.dspace.AbstractIntegrationTestWithDatabase; import org.dspace.builder.CollectionBuilder; @@ -19,7 +21,10 @@ import org.dspace.content.Collection; import org.dspace.content.Item; import org.dspace.core.factory.CoreServiceFactory; import org.dspace.curate.Curator; +import org.dspace.identifier.IdentifierProvider; +import org.dspace.identifier.IdentifierServiceImpl; import org.dspace.identifier.VersionedHandleIdentifierProviderWithCanonicalHandles; +import org.dspace.kernel.ServiceManager; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; import org.junit.After; @@ -32,10 +37,23 @@ import org.junit.Test; */ public class CreateMissingIdentifiersIT extends AbstractIntegrationTestWithDatabase { + private ServiceManager serviceManager; + private IdentifierServiceImpl identifierService; private static final String P_TASK_DEF = "plugin.named.org.dspace.curate.CurationTask"; private static final String TASK_NAME = "test"; + @Override + public void setUp() throws Exception { + super.setUp(); + context.turnOffAuthorisationSystem(); + + serviceManager = DSpaceServicesFactory.getInstance().getServiceManager(); + identifierService = serviceManager.getServicesByType(IdentifierServiceImpl.class).get(0); + // Clean out providers to avoid any being used for creation of community and collection + identifierService.setProviders(new ArrayList<>()); + } + @Test public void testPerform() throws IOException { @@ -67,11 +85,7 @@ public class CreateMissingIdentifiersIT /* * Now install an incompatible provider to make the task fail. */ - DSpaceServicesFactory.getInstance() - .getServiceManager() - .registerServiceClass( - VersionedHandleIdentifierProviderWithCanonicalHandles.class.getCanonicalName(), - VersionedHandleIdentifierProviderWithCanonicalHandles.class); + registerProvider(VersionedHandleIdentifierProviderWithCanonicalHandles.class); curator.curate(context, item); System.out.format("With incompatible provider, result is '%s'.\n", @@ -86,4 +100,14 @@ public class CreateMissingIdentifiersIT super.destroy(); DSpaceServicesFactory.getInstance().getServiceManager().getApplicationContext().refresh(); } + + private void registerProvider(Class type) { + // Register our new provider + serviceManager.registerServiceClass(type.getName(), type); + IdentifierProvider identifierProvider = + (IdentifierProvider) serviceManager.getServiceByName(type.getName(), type); + + // Overwrite the identifier-service's providers with the new one to ensure only this provider is used + identifierService.setProviders(List.of(identifierProvider)); + } } diff --git a/dspace-api/src/test/java/org/dspace/curate/CuratorReportTest.java b/dspace-api/src/test/java/org/dspace/curate/CuratorReportTest.java index 489161bf8d..0f57e18731 100644 --- a/dspace-api/src/test/java/org/dspace/curate/CuratorReportTest.java +++ b/dspace-api/src/test/java/org/dspace/curate/CuratorReportTest.java @@ -14,6 +14,8 @@ import java.util.ArrayList; import java.util.List; import java.util.regex.Pattern; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.AbstractUnitTest; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Community; @@ -27,8 +29,6 @@ import org.junit.Assert; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * Drive the Curator and check results. @@ -37,7 +37,7 @@ import org.slf4j.LoggerFactory; */ public class CuratorReportTest extends AbstractUnitTest { - Logger LOG = LoggerFactory.getLogger(CuratorReportTest.class); + Logger LOG = LogManager.getLogger(); public CuratorReportTest() { } diff --git a/dspace-api/src/test/java/org/dspace/discovery/DiscoveryIT.java b/dspace-api/src/test/java/org/dspace/discovery/DiscoveryIT.java index 55be531418..6bc79cad55 100644 --- a/dspace-api/src/test/java/org/dspace/discovery/DiscoveryIT.java +++ b/dspace-api/src/test/java/org/dspace/discovery/DiscoveryIT.java @@ -19,8 +19,8 @@ import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.stream.Collectors; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.AbstractIntegrationTestWithDatabase; import org.dspace.app.launcher.ScriptLauncher; import org.dspace.app.scripts.handler.impl.TestDSpaceRunnableHandler; diff --git a/dspace-api/src/test/java/org/dspace/discovery/FullTextContentStreamsTest.java b/dspace-api/src/test/java/org/dspace/discovery/FullTextContentStreamsTest.java index f2a759fa09..ff4174d048 100644 --- a/dspace-api/src/test/java/org/dspace/discovery/FullTextContentStreamsTest.java +++ b/dspace-api/src/test/java/org/dspace/discovery/FullTextContentStreamsTest.java @@ -193,7 +193,7 @@ public class FullTextContentStreamsTest { content.contains("This is text 1")); assertFalse("The data should NOT contain data of the second bitstream that is corrupt", content.contains("This is text 2")); - assertTrue("The data should contain data of the third bistream that is not corrupt", + assertTrue("The data should contain data of the third bitstream that is not corrupt", content.contains("This is text 3")); assertTrue("The data should contain data on the exception that occurred", content.contains("java.io.IOException")); diff --git a/dspace-api/src/test/java/org/dspace/eperson/EPersonInWorkflowIT.java b/dspace-api/src/test/java/org/dspace/eperson/EPersonInWorkflowIT.java index 0aa549e8c8..a9cc213ad9 100644 --- a/dspace-api/src/test/java/org/dspace/eperson/EPersonInWorkflowIT.java +++ b/dspace-api/src/test/java/org/dspace/eperson/EPersonInWorkflowIT.java @@ -15,8 +15,8 @@ import static org.junit.Assert.fail; import java.sql.SQLException; import java.util.List; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.dspace.AbstractIntegrationTestWithDatabase; @@ -347,7 +347,7 @@ public class EPersonInWorkflowIT extends AbstractIntegrationTestWithDatabase { * being no other members in step 3 * - approve it by user B * - delete user B - * - verify the delete suceeds + * - verify the delete succeeds * - verify that the item is archived */ context.turnOffAuthorisationSystem(); @@ -543,7 +543,7 @@ public class EPersonInWorkflowIT extends AbstractIntegrationTestWithDatabase { * task if they are the only member. This test also verifies the user can be removed from a step with no tasks * even if they are the only member. This test also verifies that after the task has been passed and the user has * been removed from the workflow, the EPerson can be removed. This test also verifies that an item is correctly - * arhived if the last step has no members left. + * archived if the last step has no members left. * * @throws Exception */ diff --git a/dspace-api/src/test/java/org/dspace/eperson/EPersonTest.java b/dspace-api/src/test/java/org/dspace/eperson/EPersonTest.java index 3780afcf63..4439e39e70 100644 --- a/dspace-api/src/test/java/org/dspace/eperson/EPersonTest.java +++ b/dspace-api/src/test/java/org/dspace/eperson/EPersonTest.java @@ -21,8 +21,8 @@ import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.Set; -import javax.mail.MessagingException; +import jakarta.mail.MessagingException; import org.apache.commons.codec.DecoderException; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; diff --git a/dspace-api/src/test/java/org/dspace/external/MockOpenAIRERestConnector.java b/dspace-api/src/test/java/org/dspace/external/MockOpenaireRestConnector.java similarity index 82% rename from dspace-api/src/test/java/org/dspace/external/MockOpenAIRERestConnector.java rename to dspace-api/src/test/java/org/dspace/external/MockOpenaireRestConnector.java index bac80e196c..9e1362e231 100644 --- a/dspace-api/src/test/java/org/dspace/external/MockOpenAIRERestConnector.java +++ b/dspace-api/src/test/java/org/dspace/external/MockOpenaireRestConnector.java @@ -8,21 +8,20 @@ package org.dspace.external; import java.io.InputStream; -import javax.xml.bind.JAXBException; import eu.openaire.jaxb.helper.OpenAIREHandler; import eu.openaire.jaxb.model.Response; /** - * Mock the OpenAIRE rest connector for unit testing
    + * Mock the Openaire rest connector for unit testing
    * will be resolved against static test xml files * * @author pgraca * */ -public class MockOpenAIRERestConnector extends OpenAIRERestConnector { +public class MockOpenaireRestConnector extends OpenaireRestConnector { - public MockOpenAIRERestConnector(String url) { + public MockOpenaireRestConnector(String url) { super(url); } @@ -30,7 +29,7 @@ public class MockOpenAIRERestConnector extends OpenAIRERestConnector { public Response searchProjectByKeywords(int page, int size, String... keywords) { try { return OpenAIREHandler.unmarshal(this.getClass().getResourceAsStream("openaire-projects.xml")); - } catch (JAXBException e) { + } catch (Exception e) { e.printStackTrace(); } return null; @@ -40,7 +39,7 @@ public class MockOpenAIRERestConnector extends OpenAIRERestConnector { public Response searchProjectByIDAndFunder(String projectID, String projectFunder, int page, int size) { try { return OpenAIREHandler.unmarshal(this.getClass().getResourceAsStream("openaire-project.xml")); - } catch (JAXBException e) { + } catch (Exception e) { e.printStackTrace(); } return null; @@ -50,7 +49,7 @@ public class MockOpenAIRERestConnector extends OpenAIRERestConnector { public Response search(String path, int page, int size) { try { return OpenAIREHandler.unmarshal(this.getClass().getResourceAsStream("openaire-no-projects.xml")); - } catch (JAXBException e) { + } catch (Exception e) { e.printStackTrace(); } return null; diff --git a/dspace-api/src/test/java/org/dspace/external/provider/impl/OpenAIREFundingDataProviderTest.java b/dspace-api/src/test/java/org/dspace/external/provider/impl/OpenaireFundingDataProviderTest.java similarity index 59% rename from dspace-api/src/test/java/org/dspace/external/provider/impl/OpenAIREFundingDataProviderTest.java rename to dspace-api/src/test/java/org/dspace/external/provider/impl/OpenaireFundingDataProviderTest.java index 5e96f06ac8..d14dc99035 100644 --- a/dspace-api/src/test/java/org/dspace/external/provider/impl/OpenAIREFundingDataProviderTest.java +++ b/dspace-api/src/test/java/org/dspace/external/provider/impl/OpenaireFundingDataProviderTest.java @@ -23,15 +23,15 @@ import org.junit.Before; import org.junit.Test; /** - * Unit tests for OpenAIREFundingDataProvider + * Unit tests for OpenaireFundingDataProvider * * @author pgraca * */ -public class OpenAIREFundingDataProviderTest extends AbstractDSpaceTest { +public class OpenaireFundingDataProviderTest extends AbstractDSpaceTest { ExternalDataService externalDataService; - ExternalDataProvider openAIREFundingDataProvider; + ExternalDataProvider openaireFundingDataProvider; /** * This method will be run before every test as per @Before. It will initialize @@ -44,38 +44,38 @@ public class OpenAIREFundingDataProviderTest extends AbstractDSpaceTest { public void init() { // Set up External Service Factory and set data providers externalDataService = ExternalServiceFactory.getInstance().getExternalDataService(); - openAIREFundingDataProvider = externalDataService.getExternalDataProvider("openAIREFunding"); + openaireFundingDataProvider = externalDataService.getExternalDataProvider("openaireFunding"); } @Test public void testNumberOfResultsWSingleKeyword() { - assertNotNull("openAIREFundingDataProvider is not null", openAIREFundingDataProvider); - assertEquals("openAIREFunding.numberOfResults.query:mock", 77, - openAIREFundingDataProvider.getNumberOfResults("mock")); + assertNotNull("openaireFundingDataProvider is not null", openaireFundingDataProvider); + assertEquals("openaireFunding.numberOfResults.query:mock", 77, + openaireFundingDataProvider.getNumberOfResults("mock")); } @Test public void testNumberOfResultsWKeywords() { - assertNotNull("openAIREFundingDataProvider is not null", openAIREFundingDataProvider); - assertEquals("openAIREFunding.numberOfResults.query:mock+test", 77, - openAIREFundingDataProvider.getNumberOfResults("mock+test")); + assertNotNull("openaireFundingDataProvider is not null", openaireFundingDataProvider); + assertEquals("openaireFunding.numberOfResults.query:mock+test", 77, + openaireFundingDataProvider.getNumberOfResults("mock+test")); } @Test public void testQueryResultsWSingleKeyword() { - assertNotNull("openAIREFundingDataProvider is not null", openAIREFundingDataProvider); - List results = openAIREFundingDataProvider.searchExternalDataObjects("mock", 0, 10); - assertEquals("openAIREFunding.searchExternalDataObjects.size", 10, results.size()); + assertNotNull("openaireFundingDataProvider is not null", openaireFundingDataProvider); + List results = openaireFundingDataProvider.searchExternalDataObjects("mock", 0, 10); + assertEquals("openaireFunding.searchExternalDataObjects.size", 10, results.size()); } @Test public void testQueryResultsWKeywords() { String value = "Mushroom Robo-Pic - Development of an autonomous robotic mushroom picking system"; - assertNotNull("openAIREFundingDataProvider is not null", openAIREFundingDataProvider); - List results = openAIREFundingDataProvider.searchExternalDataObjects("mock+test", 0, 10); - assertEquals("openAIREFunding.searchExternalDataObjects.size", 10, results.size()); - assertTrue("openAIREFunding.searchExternalDataObjects.first.value", value.equals(results.get(0).getValue())); + assertNotNull("openaireFundingDataProvider is not null", openaireFundingDataProvider); + List results = openaireFundingDataProvider.searchExternalDataObjects("mock+test", 0, 10); + assertEquals("openaireFunding.searchExternalDataObjects.size", 10, results.size()); + assertTrue("openaireFunding.searchExternalDataObjects.first.value", value.equals(results.get(0).getValue())); } @Test @@ -84,22 +84,22 @@ public class OpenAIREFundingDataProviderTest extends AbstractDSpaceTest { String value = "Portuguese Wild Mushrooms: Chemical characterization and functional study" + " of antiproliferative and proapoptotic properties in cancer cell lines"; - assertNotNull("openAIREFundingDataProvider is not null", openAIREFundingDataProvider); + assertNotNull("openaireFundingDataProvider is not null", openaireFundingDataProvider); - Optional result = openAIREFundingDataProvider.getExternalDataObject(id); + Optional result = openaireFundingDataProvider.getExternalDataObject(id); - assertTrue("openAIREFunding.getExternalDataObject.exists", result.isPresent()); - assertTrue("openAIREFunding.getExternalDataObject.value", value.equals(result.get().getValue())); + assertTrue("openaireFunding.getExternalDataObject.exists", result.isPresent()); + assertTrue("openaireFunding.getExternalDataObject.value", value.equals(result.get().getValue())); } @Test public void testGetDataObjectWInvalidId() { String id = "WRONGID"; - assertNotNull("openAIREFundingDataProvider is not null", openAIREFundingDataProvider); + assertNotNull("openaireFundingDataProvider is not null", openaireFundingDataProvider); - Optional result = openAIREFundingDataProvider.getExternalDataObject(id); + Optional result = openaireFundingDataProvider.getExternalDataObject(id); - assertTrue("openAIREFunding.getExternalDataObject.notExists:WRONGID", result.isEmpty()); + assertTrue("openaireFunding.getExternalDataObject.notExists:WRONGID", result.isEmpty()); } } diff --git a/dspace-api/src/test/java/org/dspace/external/provider/impl/OrcidPublicationDataProviderIT.java b/dspace-api/src/test/java/org/dspace/external/provider/impl/OrcidPublicationDataProviderIT.java index dae14115b8..671fe94679 100644 --- a/dspace-api/src/test/java/org/dspace/external/provider/impl/OrcidPublicationDataProviderIT.java +++ b/dspace-api/src/test/java/org/dspace/external/provider/impl/OrcidPublicationDataProviderIT.java @@ -27,9 +27,9 @@ import java.net.URL; import java.util.List; import java.util.Optional; import java.util.function.Predicate; -import javax.xml.bind.JAXBContext; -import javax.xml.bind.Unmarshaller; +import jakarta.xml.bind.JAXBContext; +import jakarta.xml.bind.Unmarshaller; import org.apache.commons.codec.binary.StringUtils; import org.dspace.AbstractIntegrationTestWithDatabase; import org.dspace.builder.CollectionBuilder; diff --git a/dspace-api/src/test/java/org/dspace/identifier/DOIIdentifierProviderTest.java b/dspace-api/src/test/java/org/dspace/identifier/DOIIdentifierProviderTest.java index 09387acd3e..734713b92c 100644 --- a/dspace-api/src/test/java/org/dspace/identifier/DOIIdentifierProviderTest.java +++ b/dspace-api/src/test/java/org/dspace/identifier/DOIIdentifierProviderTest.java @@ -566,7 +566,7 @@ public class DOIIdentifierProviderTest /** * Test minting a DOI with a filter that always returns true and therefore allows the DOI to be minted - * (this should have hte same results as base testMint_DOI, but here we use an explicit filter rather than null) + * (this should have the same results as base testMint_DOI, but here we use an explicit filter rather than null) */ @Test public void testMint_DOI_withMatchingFilter() @@ -617,7 +617,7 @@ public class DOIIdentifierProviderTest DOI doiRow = doiService.findByDoi(context, doi.substring(DOI.SCHEME.length())); assumeNotNull(doiRow); - assertTrue("Reservation of DOI did not set the corret DOI status.", + assertTrue("Reservation of DOI did not set the correct DOI status.", DOIIdentifierProvider.TO_BE_RESERVED.equals(doiRow.getStatus())); } @@ -633,7 +633,7 @@ public class DOIIdentifierProviderTest DOI doiRow = doiService.findByDoi(context, doi.substring(DOI.SCHEME.length())); assumeNotNull(doiRow); - assertTrue("Registration of DOI did not set the corret DOI status.", + assertTrue("Registration of DOI did not set the correct DOI status.", DOIIdentifierProvider.TO_BE_REGISTERED.equals(doiRow.getStatus())); } @@ -649,7 +649,7 @@ public class DOIIdentifierProviderTest DOI doiRow = doiService.findByDoi(context, doi.substring(DOI.SCHEME.length())); assumeNotNull(doiRow); - assertTrue("Registration of DOI did not set the corret DOI status.", + assertTrue("Registration of DOI did not set the correct DOI status.", DOIIdentifierProvider.TO_BE_REGISTERED.equals(doiRow.getStatus())); } @@ -672,7 +672,7 @@ public class DOIIdentifierProviderTest DOI doiRow = doiService.findByDoi(context, doi.substring(DOI.SCHEME.length())); assertNotNull("Created DOI was not stored in database.", doiRow); - assertTrue("Registration of DOI did not set the corret DOI status.", + assertTrue("Registration of DOI did not set the correct DOI status.", DOIIdentifierProvider.TO_BE_REGISTERED.equals(doiRow.getStatus())); } diff --git a/dspace-api/src/test/java/org/dspace/identifier/VersionedHandleIdentifierProviderIT.java b/dspace-api/src/test/java/org/dspace/identifier/VersionedHandleIdentifierProviderIT.java index 7e549f6cae..57acf1f1c4 100644 --- a/dspace-api/src/test/java/org/dspace/identifier/VersionedHandleIdentifierProviderIT.java +++ b/dspace-api/src/test/java/org/dspace/identifier/VersionedHandleIdentifierProviderIT.java @@ -24,6 +24,7 @@ import org.dspace.content.Collection; import org.dspace.content.Item; import org.dspace.kernel.ServiceManager; import org.dspace.services.factory.DSpaceServicesFactory; +import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -57,13 +58,30 @@ public class VersionedHandleIdentifierProviderIT extends AbstractIntegrationTest .build(); } + @After + @Override + public void destroy() throws Exception { + super.destroy(); + // After this test has finished running, refresh application context and + // set the expected 'default' versioned handle provider back to ensure other tests don't fail + DSpaceServicesFactory.getInstance().getServiceManager().getApplicationContext().refresh(); + } + private void registerProvider(Class type) { // Register our new provider - serviceManager.registerServiceClass(type.getName(), type); IdentifierProvider identifierProvider = - (IdentifierProvider) serviceManager.getServiceByName(type.getName(), type); + (IdentifierProvider) DSpaceServicesFactory.getInstance().getServiceManager() + .getServiceByName(type.getName(), type); + if (identifierProvider == null) { + DSpaceServicesFactory.getInstance().getServiceManager().registerServiceClass(type.getName(), type); + identifierProvider = (IdentifierProvider) DSpaceServicesFactory.getInstance().getServiceManager() + .getServiceByName(type.getName(), type); + } // Overwrite the identifier-service's providers with the new one to ensure only this provider is used + identifierService = DSpaceServicesFactory.getInstance().getServiceManager() + .getServicesByType(IdentifierServiceImpl.class).get(0); + identifierService.setProviders(new ArrayList<>()); identifierService.setProviders(List.of(identifierProvider)); } diff --git a/dspace-api/src/test/java/org/dspace/importer/external/crossref/CrossRefDateMetadataProcessorTest.java b/dspace-api/src/test/java/org/dspace/importer/external/crossref/CrossRefDateMetadataProcessorTest.java new file mode 100644 index 0000000000..323856cd0a --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/importer/external/crossref/CrossRefDateMetadataProcessorTest.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.importer.external.crossref; + +import static org.junit.Assert.assertEquals; + +import java.util.Collection; + +import org.junit.Test; + +/** + * + * @author mwood + */ +public class CrossRefDateMetadataProcessorTest { + /** + * Test of processMetadata method, of class CrossRefDateMetadataProcessor. + */ + @Test + public void testProcessMetadata() { + CrossRefDateMetadataProcessor unit = new CrossRefDateMetadataProcessor(); + unit.setPathToArray("/dates"); + Collection metadata = unit.processMetadata("{\"dates\": [" + + "[1957, 1, 27]," + + "[1957, 1]," + + "[1957]" + + "]}"); + String[] metadataValues = (String[]) metadata.toArray(new String[3]); + assertEquals("[yyyy, MM, dd] should parse", "1957-01-27", metadataValues[0]); + assertEquals("[yyyy, MM] should parse", "1957-01", metadataValues[1]); + assertEquals("[yyyy] should parse", "1957", metadataValues[2]); + } +} diff --git a/dspace-api/src/test/java/org/dspace/matcher/NotifyServiceEntityMatcher.java b/dspace-api/src/test/java/org/dspace/matcher/NotifyServiceEntityMatcher.java new file mode 100644 index 0000000000..7b20a1c8be --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/matcher/NotifyServiceEntityMatcher.java @@ -0,0 +1,57 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.matcher; + +import org.dspace.app.ldn.NotifyServiceEntity; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; + +/** + * Implementation of {@link Matcher} to match a NotifyServiceEntity by all its + * attributes. + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + * + */ +public class NotifyServiceEntityMatcher extends TypeSafeMatcher { + + private final NotifyServiceEntity expectedEntity; + + private NotifyServiceEntityMatcher(NotifyServiceEntity expectedEntity) { + this.expectedEntity = expectedEntity; + } + + public static NotifyServiceEntityMatcher matchesNotifyServiceEntity(NotifyServiceEntity expectedEntity) { + return new NotifyServiceEntityMatcher(expectedEntity); + } + + @Override + protected boolean matchesSafely(NotifyServiceEntity actualEntity) { + return actualEntity.getName().equals(expectedEntity.getName()) && + actualEntity.getDescription().equals(expectedEntity.getDescription()) && + actualEntity.getUrl().equals(expectedEntity.getUrl()) && + actualEntity.getLdnUrl().equals(expectedEntity.getLdnUrl()) && + actualEntity.getInboundPatterns() == expectedEntity.getInboundPatterns() && + actualEntity.isEnabled() == expectedEntity.isEnabled() && + actualEntity.getScore() == expectedEntity.getScore(); + } + + @Override + public void describeTo(Description description) { + description.appendText("a Notify Service Entity with the following attributes:") + .appendText(", name ").appendValue(expectedEntity.getName()) + .appendText(", description ").appendValue(expectedEntity.getDescription()) + .appendText(", URL ").appendValue(expectedEntity.getUrl()) + .appendText(", LDN URL ").appendValue(expectedEntity.getLdnUrl()) + .appendText(", inbound patterns ").appendValue(expectedEntity.getInboundPatterns()) + .appendText(", enabled ").appendValue(expectedEntity.isEnabled()) + .appendText(", score ").appendValue(expectedEntity.getScore()); + } + +} diff --git a/dspace-api/src/test/java/org/dspace/matcher/QAEventMatcher.java b/dspace-api/src/test/java/org/dspace/matcher/QAEventMatcher.java new file mode 100644 index 0000000000..61affd0ec8 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/matcher/QAEventMatcher.java @@ -0,0 +1,117 @@ +/** + * 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.matcher; + +import static org.dspace.content.QAEvent.OPENAIRE_SOURCE; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; + +import org.dspace.content.Item; +import org.dspace.content.QAEvent; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; + +/** + * Implementation of {@link org.hamcrest.Matcher} to match a QAEvent by all its + * attributes. + * + * @author Luca Giamminonni (luca.giamminonni at 4science.it) + * + */ +public class QAEventMatcher extends TypeSafeMatcher { + + private Matcher eventIdMatcher; + + private Matcher originalIdMatcher; + + private Matcher relatedMatcher; + + private Matcher sourceMatcher; + + private Matcher statusMatcher; + + private Matcher targetMatcher; + + private Matcher titleMatcher; + + private Matcher messageMatcher; + + private Matcher topicMatcher; + + private Matcher trustMatcher; + + private QAEventMatcher(Matcher eventIdMatcher, Matcher originalIdMatcher, + Matcher relatedMatcher, Matcher sourceMatcher, Matcher statusMatcher, + Matcher targetMatcher, Matcher titleMatcher, Matcher messageMatcher, + Matcher topicMatcher, Matcher trustMatcher) { + this.eventIdMatcher = eventIdMatcher; + this.originalIdMatcher = originalIdMatcher; + this.relatedMatcher = relatedMatcher; + this.sourceMatcher = sourceMatcher; + this.statusMatcher = statusMatcher; + this.targetMatcher = targetMatcher; + this.titleMatcher = titleMatcher; + this.messageMatcher = messageMatcher; + this.topicMatcher = topicMatcher; + this.trustMatcher = trustMatcher; + } + + /** + * Creates an instance of {@link QAEventMatcher} that matches an OPENAIRE + * QAEvent with PENDING status, with an event id, without a related item and + * with the given attributes. + * + * @param originalId the original id to match + * @param target the target to match + * @param title the title to match + * @param message the message to match + * @param topic the topic to match + * @param trust the trust to match + * @return the matcher instance + */ + public static QAEventMatcher pendingOpenaireEventWith(String originalId, Item target, + String title, String message, String topic, Double trust) { + + return new QAEventMatcher(notNullValue(String.class), is(originalId), nullValue(String.class), + is(OPENAIRE_SOURCE), is("PENDING"), is(target.getID().toString()), is(title), is(message), is(topic), + is(trust)); + + } + + @Override + public boolean matchesSafely(QAEvent event) { + return eventIdMatcher.matches(event.getEventId()) + && originalIdMatcher.matches(event.getOriginalId()) + && relatedMatcher.matches(event.getRelated()) + && sourceMatcher.matches(event.getSource()) + && statusMatcher.matches(event.getStatus()) + && targetMatcher.matches(event.getTarget()) + && titleMatcher.matches(event.getTitle()) + && messageMatcher.matches(event.getMessage()) + && topicMatcher.matches(event.getTopic()) + && trustMatcher.matches(event.getTrust()); + } + + @Override + public void describeTo(Description description) { + description.appendText("a QA event with the following attributes:") + .appendText(" event id ").appendDescriptionOf(eventIdMatcher) + .appendText(", original id ").appendDescriptionOf(originalIdMatcher) + .appendText(", related ").appendDescriptionOf(relatedMatcher) + .appendText(", source ").appendDescriptionOf(sourceMatcher) + .appendText(", status ").appendDescriptionOf(statusMatcher) + .appendText(", target ").appendDescriptionOf(targetMatcher) + .appendText(", title ").appendDescriptionOf(titleMatcher) + .appendText(", message ").appendDescriptionOf(messageMatcher) + .appendText(", topic ").appendDescriptionOf(topicMatcher) + .appendText(" and trust ").appendDescriptionOf(trustMatcher); + } + +} diff --git a/dspace-api/src/test/java/org/dspace/matcher/QASourceMatcher.java b/dspace-api/src/test/java/org/dspace/matcher/QASourceMatcher.java new file mode 100644 index 0000000000..fe3b7130b5 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/matcher/QASourceMatcher.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.matcher; + +import static org.hamcrest.Matchers.is; + +import org.dspace.qaevent.QASource; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; + +/** + * Implementation of {@link org.hamcrest.Matcher} to match a QASource by all its + * attributes. + * + * @author Luca Giamminonni (luca.giamminonni at 4science.it) + * + */ +public class QASourceMatcher extends TypeSafeMatcher { + + private Matcher nameMatcher; + + private Matcher totalEventsMatcher; + + private QASourceMatcher(Matcher nameMatcher, Matcher totalEventsMatcher) { + this.nameMatcher = nameMatcher; + this.totalEventsMatcher = totalEventsMatcher; + } + + /** + * Creates an instance of {@link QASourceMatcher} that matches a QATopic with + * the given name and total events count. + * @param name the name to match + * @param totalEvents the total events count to match + * @return the matcher instance + */ + public static QASourceMatcher with(String name, long totalEvents) { + return new QASourceMatcher(is(name), is(totalEvents)); + } + + @Override + public boolean matchesSafely(QASource event) { + return nameMatcher.matches(event.getName()) && totalEventsMatcher.matches(event.getTotalEvents()); + } + + @Override + public void describeTo(Description description) { + description.appendText("a QA source with the following attributes:") + .appendText(" name ").appendDescriptionOf(nameMatcher) + .appendText(" and total events ").appendDescriptionOf(totalEventsMatcher); + } + +} diff --git a/dspace-api/src/test/java/org/dspace/matcher/QATopicMatcher.java b/dspace-api/src/test/java/org/dspace/matcher/QATopicMatcher.java new file mode 100644 index 0000000000..dd93972814 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/matcher/QATopicMatcher.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.matcher; + +import static org.hamcrest.Matchers.is; + +import org.dspace.qaevent.QATopic; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; + +/** + * Implementation of {@link org.hamcrest.Matcher} to match a QATopic by all its + * attributes. + * + * @author Luca Giamminonni (luca.giamminonni at 4science.it) + * + */ +public class QATopicMatcher extends TypeSafeMatcher { + + private Matcher keyMatcher; + + private Matcher totalEventsMatcher; + + private QATopicMatcher(Matcher keyMatcher, Matcher totalEventsMatcher) { + this.keyMatcher = keyMatcher; + this.totalEventsMatcher = totalEventsMatcher; + } + + /** + * Creates an instance of {@link QATopicMatcher} that matches a QATopic with the + * given key and total events count. + * @param key the key to match + * @param totalEvents the total events count to match + * @return the matcher instance + */ + public static QATopicMatcher with(String key, long totalEvents) { + return new QATopicMatcher(is(key), is(totalEvents)); + } + + @Override + public boolean matchesSafely(QATopic event) { + return keyMatcher.matches(event.getKey()) && totalEventsMatcher.matches(event.getTotalEvents()); + } + + @Override + public void describeTo(Description description) { + description.appendText("a QA topic with the following attributes:") + .appendText(" key ").appendDescriptionOf(keyMatcher) + .appendText(" and total events ").appendDescriptionOf(totalEventsMatcher); + } + +} diff --git a/dspace-api/src/test/java/org/dspace/orcid/service/OrcidEntityFactoryServiceIT.java b/dspace-api/src/test/java/org/dspace/orcid/service/OrcidEntityFactoryServiceIT.java index 17bc6ee531..912efcfcf3 100644 --- a/dspace-api/src/test/java/org/dspace/orcid/service/OrcidEntityFactoryServiceIT.java +++ b/dspace-api/src/test/java/org/dspace/orcid/service/OrcidEntityFactoryServiceIT.java @@ -274,13 +274,13 @@ public class OrcidEntityFactoryServiceIT extends AbstractIntegrationTestWithData private Predicate contributor(String name, ContributorRole role, SequenceType sequence) { return contributor -> contributor.getCreditName().getContent().equals(name) - && role.equals(contributor.getContributorAttributes().getContributorRole()) + && role.value().equals(contributor.getContributorAttributes().getContributorRole()) && contributor.getContributorAttributes().getContributorSequence() == sequence; } private Predicate fundingContributor(String name, FundingContributorRole role) { return contributor -> contributor.getCreditName().getContent().equals(name) - && role.equals(contributor.getContributorAttributes().getContributorRole()); + && role.value().equals(contributor.getContributorAttributes().getContributorRole()); } private Predicate date(String year, String month, String days) { diff --git a/dspace-api/src/test/java/org/dspace/qaevent/MockQAEventService.java b/dspace-api/src/test/java/org/dspace/qaevent/MockQAEventService.java new file mode 100644 index 0000000000..3d460015f7 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/qaevent/MockQAEventService.java @@ -0,0 +1,46 @@ +/** + * 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.qaevent; + +import java.io.IOException; + +import org.apache.solr.client.solrj.SolrServerException; +import org.dspace.qaevent.service.impl.QAEventServiceImpl; +import org.dspace.solr.MockSolrServer; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.stereotype.Service; + +/** + * Mock SOLR service for the qaevents Core. + */ +@Service +public class MockQAEventService extends QAEventServiceImpl implements InitializingBean, DisposableBean { + private MockSolrServer mockSolrServer; + + @Override + public void afterPropertiesSet() throws Exception { + mockSolrServer = new MockSolrServer("qaevent"); + solr = mockSolrServer.getSolrServer(); + } + + /** Clear all records from the search core. */ + public void reset() { + mockSolrServer.reset(); + try { + mockSolrServer.getSolrServer().commit(); + } catch (SolrServerException | IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public void destroy() throws Exception { + mockSolrServer.destroy(); + } +} \ No newline at end of file diff --git a/dspace-api/src/test/java/org/dspace/qaevent/script/OpenaireEventsImportIT.java b/dspace-api/src/test/java/org/dspace/qaevent/script/OpenaireEventsImportIT.java new file mode 100644 index 0000000000..b63c85eeb6 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/qaevent/script/OpenaireEventsImportIT.java @@ -0,0 +1,545 @@ +/** + * 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.qaevent.script; + +import static java.util.List.of; +import static org.dspace.content.QAEvent.COAR_NOTIFY_SOURCE; +import static org.dspace.content.QAEvent.OPENAIRE_SOURCE; +import static org.dspace.matcher.QAEventMatcher.pendingOpenaireEventWith; +import static org.dspace.qaevent.service.impl.QAEventServiceImpl.QAEVENTS_SOURCES; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import java.io.File; +import java.io.FileInputStream; +import java.io.OutputStream; +import java.net.URL; +import java.util.List; + +import eu.dnetlib.broker.BrokerClient; +import org.apache.commons.io.IOUtils; +import org.dspace.AbstractIntegrationTestWithDatabase; +import org.dspace.app.launcher.ScriptLauncher; +import org.dspace.app.scripts.handler.impl.TestDSpaceRunnableHandler; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.content.Collection; +import org.dspace.content.Item; +import org.dspace.content.QAEvent; +import org.dspace.matcher.QASourceMatcher; +import org.dspace.matcher.QATopicMatcher; +import org.dspace.qaevent.QANotifyPatterns; +import org.dspace.qaevent.QATopic; +import org.dspace.qaevent.service.OpenaireClientFactory; +import org.dspace.qaevent.service.QAEventService; +import org.dspace.qaevent.service.impl.OpenaireClientFactoryImpl; +import org.dspace.services.ConfigurationService; +import org.dspace.utils.DSpace; +import org.junit.After; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; + +/** + * Integration tests for {@link OpenaireEventsImport}. + * + * @author Luca Giamminonni (luca.giamminonni at 4science.it) + * + */ +public class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase { + + private static final String ORDER_FIELD = "topic"; + private static final String BASE_JSON_DIR_PATH = "org/dspace/app/openaire-events/"; + + private QAEventService qaEventService = new DSpace().getSingletonService(QAEventService.class); + + private Collection collection; + + private BrokerClient brokerClient = OpenaireClientFactory.getInstance().getBrokerClient(); + + private BrokerClient mockBrokerClient = mock(BrokerClient.class); + + private ConfigurationService configurationService = new DSpace().getConfigurationService(); + + @Before + public void setup() { + + context.turnOffAuthorisationSystem(); + // we want all the test in this class to be run using the administrator user as OpenAIRE events are only visible + // to administrators. + // Please note that test related to forbidden and unauthorized access to qaevent are included in + // the QAEventRestRepositoryIT here we are only testing that the import script is creating the expected events + context.setCurrentUser(admin); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection") + .build(); + + context.restoreAuthSystemState(); + configurationService.setProperty(QAEVENTS_SOURCES, new String[] { QAEvent.OPENAIRE_SOURCE }); + ((OpenaireClientFactoryImpl) OpenaireClientFactory.getInstance()).setBrokerClient(mockBrokerClient); + } + + @After + public void after() { + ((OpenaireClientFactoryImpl) OpenaireClientFactory.getInstance()).setBrokerClient(brokerClient); + } + + @Test + public void testWithoutParameters() throws Exception { + TestDSpaceRunnableHandler handler = new TestDSpaceRunnableHandler(); + + String[] args = new String[] { "import-openaire-events" }; + ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), handler, kernelImpl); + + assertThat(handler.getErrorMessages(), empty()); + assertThat(handler.getWarningMessages(), empty()); + assertThat(handler.getInfoMessages(), empty()); + + Exception exception = handler.getException(); + assertThat(exception, instanceOf(IllegalArgumentException.class)); + assertThat(exception.getMessage(), is("One parameter between the location of the file and the email " + + "must be entered to proceed with the import.")); + + verifyNoInteractions(mockBrokerClient); + } + + @Test + public void testWithBothFileAndEmailParameters() throws Exception { + TestDSpaceRunnableHandler handler = new TestDSpaceRunnableHandler(); + + String[] args = new String[] { "import-openaire-events", "-f", getFileLocation("events.json"), + "-e", "test@user.com" }; + ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), handler, kernelImpl); + + assertThat(handler.getErrorMessages(), empty()); + assertThat(handler.getWarningMessages(), empty()); + assertThat(handler.getInfoMessages(), empty()); + + Exception exception = handler.getException(); + assertThat(exception, instanceOf(IllegalArgumentException.class)); + assertThat(exception.getMessage(), is("Only one parameter between the location of the file and the email " + + "must be entered to proceed with the import.")); + + verifyNoInteractions(mockBrokerClient); + } + + @Test + public void testManyEventsImportFromFile() throws Exception { + + context.turnOffAuthorisationSystem(); + + Item firstItem = createItem("Test item", "123456789/99998"); + Item secondItem = createItem("Test item 2", "123456789/99999"); + + context.restoreAuthSystemState(); + + TestDSpaceRunnableHandler handler = new TestDSpaceRunnableHandler(); + + String[] args = new String[] { "import-openaire-events", "-f", getFileLocation("events.json") }; + ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), handler, kernelImpl); + + assertThat(handler.getErrorMessages(), empty()); + assertThat(handler.getWarningMessages(), empty()); + assertThat(handler.getInfoMessages(), contains( + "Trying to read the QA events from the provided file", + "Found 5 events in the given file")); + + assertThat(qaEventService.findAllSources(context, 0, 20), + hasItem(QASourceMatcher.with(OPENAIRE_SOURCE, 5L)) + ); + + List topicList = qaEventService.findAllTopicsBySource(context, OPENAIRE_SOURCE, 0, 20, + ORDER_FIELD, false); + assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MORE_PROJECT, 1L))); + assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MORE_PID, 1L))); + assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MISSING_PID, 1L))); + assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MISSING_PROJECT, 1L))); + assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 1L))); + + String projectMessage = "{\"projects[0].acronym\":\"PAThs\",\"projects[0].code\":\"687567\"," + + "\"projects[0].funder\":\"EC\",\"projects[0].fundingProgram\":\"H2020\"," + + "\"projects[0].jurisdiction\":\"EU\"," + + "\"projects[0].openaireId\":\"40|corda__h2020::6e32f5eb912688f2424c68b851483ea4\"," + + "\"projects[0].title\":\"Tracking Papyrus and Parchment Paths\"}"; + + assertThat(qaEventService.findEventsByTopic(context, OPENAIRE_SOURCE, + QANotifyPatterns.TOPIC_ENRICH_MORE_PROJECT, 0, 20, ORDER_FIELD, true), + contains( + pendingOpenaireEventWith("oai:www.openstarts.units.it:123456789/99998", firstItem, + "Egypt, crossroad of translations and literary interweavings", projectMessage, + QANotifyPatterns.TOPIC_ENRICH_MORE_PROJECT, 1.00d))); + + String abstractMessage = "{\"abstracts[0]\":\"Missing Abstract\"}"; + + assertThat(qaEventService.findEventsByTopic(context, OPENAIRE_SOURCE, + QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 0, 20, ORDER_FIELD, true), + contains( + pendingOpenaireEventWith("oai:www.openstarts.units.it:123456789/99999", + secondItem, "Test Publication", + abstractMessage, QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 1.00d))); + + verifyNoInteractions(mockBrokerClient); + } + + @Test + public void testManyEventsImportFromFileWithUnknownHandle() throws Exception { + + context.turnOffAuthorisationSystem(); + + Item item = createItem("Test item", "123456789/99999"); + + context.restoreAuthSystemState(); + + TestDSpaceRunnableHandler handler = new TestDSpaceRunnableHandler(); + + String[] args = new String[] { "import-openaire-events", "-f", getFileLocation("events.json") }; + ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), handler, kernelImpl); + + assertThat(handler.getErrorMessages(), empty()); + assertThat(handler.getWarningMessages(), + contains("An error occurs storing the event with id 406fb9c5656c7f11cac8995abb746887: " + + "Skipped event 406fb9c5656c7f11cac8995abb746887 related to the oai record " + + "oai:www.openstarts.units.it:123456789/99998 as the record was not found", + "An error occurs storing the event with id eafd747feee49cca7603d30ba4e768dc: " + + "Skipped event eafd747feee49cca7603d30ba4e768dc related to the oai record " + + "oai:www.openstarts.units.it:123456789/99998 as the record was not found")); + assertThat(handler.getInfoMessages(), contains( + "Trying to read the QA events from the provided file", + "Found 5 events in the given file")); + + assertThat(qaEventService.findAllSources(context, 0, 20), hasItem(QASourceMatcher.with(OPENAIRE_SOURCE, 3L))); + + List topicList = qaEventService.findAllTopicsBySource(context, OPENAIRE_SOURCE, 0, 20, + ORDER_FIELD, false); + assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 1L))); + assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MISSING_PROJECT, 1L))); + assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MORE_PID, 1L))); + + String abstractMessage = "{\"abstracts[0]\":\"Missing Abstract\"}"; + + assertThat(qaEventService.findEventsByTopic(context, OPENAIRE_SOURCE, + QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 0, 20, + ORDER_FIELD, false), contains( + pendingOpenaireEventWith("oai:www.openstarts.units.it:123456789/99999", item, "Test Publication", + abstractMessage, QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 1.00d))); + + verifyNoInteractions(mockBrokerClient); + } + + @Test + public void testManyEventsImportFromFileWithUnknownTopic() throws Exception { + + context.turnOffAuthorisationSystem(); + + createItem("Test item", "123456789/99999"); + Item secondItem = createItem("Test item 2", "123456789/999991"); + + context.restoreAuthSystemState(); + + TestDSpaceRunnableHandler handler = new TestDSpaceRunnableHandler(); + + String[] args = new String[] { "import-openaire-events", "-f", getFileLocation("unknown-topic-events.json") }; + ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), handler, kernelImpl); + + assertThat(handler.getErrorMessages(), empty()); + assertThat(handler.getWarningMessages(), + contains("An error occurs storing the event with id 8307aa56769deba961faed7162d91aab:" + + " Skipped event 8307aa56769deba961faed7162d91aab related to the oai record" + + " oai:www.openstarts.units.it:123456789/99998 as the record was not found")); + assertThat(handler.getInfoMessages(), contains( + "Trying to read the QA events from the provided file", + "Found 2 events in the given file")); + + assertThat(qaEventService.findAllSources(context, 0, 20), hasItem(QASourceMatcher.with(OPENAIRE_SOURCE, 1L))); + + assertThat(qaEventService.findAllTopicsBySource(context, OPENAIRE_SOURCE, 0, 20, ORDER_FIELD, false), + contains(QATopicMatcher.with(org.dspace.qaevent.QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 1L))); + + String abstractMessage = "{\"abstracts[0]\":\"Missing Abstract\"}"; + + assertThat(qaEventService.findEventsByTopic(context, OPENAIRE_SOURCE, + QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 0, 20, ORDER_FIELD, false), contains( + pendingOpenaireEventWith("oai:www.openstarts.units.it:123456789/999991", secondItem, "Test Publication 2", + abstractMessage, org.dspace.qaevent.QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 1.00d))); + + verifyNoInteractions(mockBrokerClient); + } + + @Test + public void testImportFromFileWithoutEvents() throws Exception { + + TestDSpaceRunnableHandler handler = new TestDSpaceRunnableHandler(); + + String[] args = new String[] { "import-openaire-events", "-f", getFileLocation("empty-file.json") }; + ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), handler, kernelImpl); + + assertThat(handler.getErrorMessages(), + contains(containsString("A not recoverable error occurs during OPENAIRE events import"))); + assertThat(handler.getWarningMessages(),empty()); + assertThat(handler.getInfoMessages(), contains("Trying to read the QA events from the provided file")); + + assertThat(qaEventService.findAllSources(context, 0, 20), hasItem(QASourceMatcher.with(OPENAIRE_SOURCE, 0L))); + + assertThat(qaEventService.findAllTopics(context, 0, 20, ORDER_FIELD, false), empty()); + + verifyNoInteractions(mockBrokerClient); + } + + @Test + public void testImportFromOpenaireBroker() throws Exception { + + context.turnOffAuthorisationSystem(); + + Item firstItem = createItem("Test item", "123456789/99998"); + Item secondItem = createItem("Test item 2", "123456789/99999"); + Item thirdItem = createItem("Test item 3", "123456789/999991"); + + context.restoreAuthSystemState(); + + URL openaireURL = new URL("http://api.openaire.eu/broker"); + + when(mockBrokerClient.listSubscriptions(openaireURL, "user@test.com")).thenReturn(of("sub1", "sub2", "sub3")); + + doAnswer(i -> writeToOutputStream(i.getArgument(2, OutputStream.class), "events.json")) + .when(mockBrokerClient).downloadEvents(eq(openaireURL), eq("sub1"), any()); + + doAnswer(i -> writeToOutputStream(i.getArgument(2, OutputStream.class), "empty-events-list.json")) + .when(mockBrokerClient).downloadEvents(eq(openaireURL), eq("sub2"), any()); + + doAnswer(i -> writeToOutputStream(i.getArgument(2, OutputStream.class), "unknown-topic-events.json")) + .when(mockBrokerClient).downloadEvents(eq(openaireURL), eq("sub3"), any()); + + TestDSpaceRunnableHandler handler = new TestDSpaceRunnableHandler(); + + String[] args = new String[] { "import-openaire-events", "-e", "user@test.com" }; + ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), handler, kernelImpl); + + assertThat(handler.getErrorMessages(), empty()); + assertThat(handler.getWarningMessages(), + contains("Event for topic ENRICH/MORE/UNKNOWN is not allowed in the qaevents.cfg")); + assertThat(handler.getInfoMessages(), contains( + "Trying to read the QA events from the OPENAIRE broker", + "Found 3 subscriptions related to the given email", + "Found 5 events from the subscription sub1", + "Found 0 events from the subscription sub2", + "Found 2 events from the subscription sub3")); + + assertThat(qaEventService.findAllSources(context, 0, 20), hasItem(QASourceMatcher.with(OPENAIRE_SOURCE, 6L))); + + List topicList = qaEventService.findAllTopicsBySource(context, OPENAIRE_SOURCE,0,20,ORDER_FIELD,false); + assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MORE_PROJECT, 1L))); + assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MORE_PID, 1L))); + assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MISSING_PID, 1L))); + assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MISSING_PROJECT, 1L))); + assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 2L))); + + String projectMessage = "{\"projects[0].acronym\":\"PAThs\",\"projects[0].code\":\"687567\"," + + "\"projects[0].funder\":\"EC\",\"projects[0].fundingProgram\":\"H2020\"," + + "\"projects[0].jurisdiction\":\"EU\"," + + "\"projects[0].openaireId\":\"40|corda__h2020::6e32f5eb912688f2424c68b851483ea4\"," + + "\"projects[0].title\":\"Tracking Papyrus and Parchment Paths\"}"; + + assertThat(qaEventService.findEventsByTopic(context, OPENAIRE_SOURCE, + QANotifyPatterns.TOPIC_ENRICH_MORE_PROJECT, 0 , 20, + ORDER_FIELD, false), contains( + pendingOpenaireEventWith("oai:www.openstarts.units.it:123456789/99998", firstItem, + "Egypt, crossroad of translations and literary interweavings", projectMessage, + QANotifyPatterns.TOPIC_ENRICH_MORE_PROJECT, 1.00d))); + + String abstractMessage = "{\"abstracts[0]\":\"Missing Abstract\"}"; + + assertThat(qaEventService.findEventsByTopic(context, OPENAIRE_SOURCE, + QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 0, 20, + ORDER_FIELD, false), containsInAnyOrder( + pendingOpenaireEventWith("oai:www.openstarts.units.it:123456789/99999", secondItem, "Test Publication", + abstractMessage, QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 1.00d), + pendingOpenaireEventWith("oai:www.openstarts.units.it:123456789/999991", thirdItem, "Test Publication 2", + abstractMessage, QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 1.00d))); + + verify(mockBrokerClient).listSubscriptions(openaireURL, "user@test.com"); + verify(mockBrokerClient).downloadEvents(eq(openaireURL), eq("sub1"), any()); + verify(mockBrokerClient).downloadEvents(eq(openaireURL), eq("sub2"), any()); + verify(mockBrokerClient).downloadEvents(eq(openaireURL), eq("sub3"), any()); + + verifyNoMoreInteractions(mockBrokerClient); + } + + @Test + public void testImportFromOpenaireBrokerWithErrorDuringListSubscription() throws Exception { + + URL openaireURL = new URL("http://api.openaire.eu/broker"); + + when(mockBrokerClient.listSubscriptions(openaireURL, "user@test.com")) + .thenThrow(new RuntimeException("Connection refused")); + + TestDSpaceRunnableHandler handler = new TestDSpaceRunnableHandler(); + + String[] args = new String[] { "import-openaire-events", "-e", "user@test.com" }; + ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), handler, kernelImpl); + + assertThat(handler.getErrorMessages(), + contains("A not recoverable error occurs during OPENAIRE events import: Connection refused")); + assertThat(handler.getWarningMessages(), empty()); + assertThat(handler.getInfoMessages(), contains("Trying to read the QA events from the OPENAIRE broker")); + + assertThat(qaEventService.findAllSources(context, 0, 20), hasItem(QASourceMatcher.with(OPENAIRE_SOURCE, 0L))); + + assertThat(qaEventService.findAllTopics(context, 0, 20, ORDER_FIELD, false), empty()); + + verify(mockBrokerClient).listSubscriptions(openaireURL, "user@test.com"); + + verifyNoMoreInteractions(mockBrokerClient); + + } + + @Test + public void testImportFromOpenaireBrokerWithErrorDuringEventsDownload() throws Exception { + + context.turnOffAuthorisationSystem(); + + createItem("Test item", "123456789/99998"); + createItem("Test item 2", "123456789/99999"); + createItem("Test item 3", "123456789/999991"); + + context.restoreAuthSystemState(); + + URL openaireURL = new URL("http://api.openaire.eu/broker"); + + when(mockBrokerClient.listSubscriptions(openaireURL, "user@test.com")).thenReturn(of("sub1", "sub2", "sub3")); + + doAnswer(i -> writeToOutputStream(i.getArgument(2, OutputStream.class), "events.json")) + .when(mockBrokerClient).downloadEvents(eq(openaireURL), eq("sub1"), any()); + + doThrow(new RuntimeException("Invalid subscription id")) + .when(mockBrokerClient).downloadEvents(eq(openaireURL), eq("sub2"), any()); + + doAnswer(i -> writeToOutputStream(i.getArgument(2, OutputStream.class), "unknown-topic-events.json")) + .when(mockBrokerClient).downloadEvents(eq(openaireURL), eq("sub3"), any()); + + TestDSpaceRunnableHandler handler = new TestDSpaceRunnableHandler(); + + String[] args = new String[] { "import-openaire-events", "-e", "user@test.com" }; + ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), handler, kernelImpl); + + assertThat(handler.getErrorMessages(), contains("An error occurs downloading the events " + + "related to the subscription sub2: Invalid subscription id")); + assertThat(handler.getWarningMessages(), + contains("Event for topic ENRICH/MORE/UNKNOWN is not allowed in the qaevents.cfg")); + assertThat(handler.getInfoMessages(), contains( + "Trying to read the QA events from the OPENAIRE broker", + "Found 3 subscriptions related to the given email", + "Found 5 events from the subscription sub1", + "Found 0 events from the subscription sub2", + "Found 2 events from the subscription sub3")); + + assertThat(qaEventService.findAllSources(context, 0, 20), hasItem(QASourceMatcher.with(OPENAIRE_SOURCE, 6L))); + + List topicList = qaEventService.findAllTopicsBySource(context, OPENAIRE_SOURCE, 0, 20, + ORDER_FIELD, false); + assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MORE_PROJECT, 1L))); + assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MISSING_PID, 1L))); + assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MORE_PID, 1L))); + assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MISSING_PROJECT, 1L))); + assertThat(topicList, hasItem(QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 2L))); + + assertThat(qaEventService.findEventsByTopic(context, OPENAIRE_SOURCE, + QANotifyPatterns.TOPIC_ENRICH_MORE_PROJECT, 0, 20, ORDER_FIELD, false), hasSize(1)); + assertThat(qaEventService.findEventsByTopic(context, OPENAIRE_SOURCE, + QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 0, 20, ORDER_FIELD, false), hasSize(2)); + + verify(mockBrokerClient).listSubscriptions(openaireURL, "user@test.com"); + verify(mockBrokerClient).downloadEvents(eq(openaireURL), eq("sub1"), any()); + verify(mockBrokerClient).downloadEvents(eq(openaireURL), eq("sub2"), any()); + verify(mockBrokerClient).downloadEvents(eq(openaireURL), eq("sub3"), any()); + + verifyNoMoreInteractions(mockBrokerClient); + } + + /** + * Improper test for ENRICH/MORE/REVIEW qa. It has the COAR_NOTIFY source + * which must be tested via LDNMessage at DNInboxControllerIT + */ + @Test + @Ignore + public void testImportFromFileEventMoreReview() throws Exception { + + context.turnOffAuthorisationSystem(); + Item firstItem = createItem("Test item", "123456789/99998"); + Item secondItem = createItem("Test item 2", "123456789/99999"); + + context.restoreAuthSystemState(); + + TestDSpaceRunnableHandler handler = new TestDSpaceRunnableHandler(); + + String[] args = new String[] { "import-openaire-events", "-f", getFileLocation("event-more-review.json") }; + ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), handler, kernelImpl); + + assertThat(qaEventService.findAllTopicsBySource(context, COAR_NOTIFY_SOURCE, 0, 20, + ORDER_FIELD, false), contains( + QATopicMatcher.with(QANotifyPatterns.TOPIC_ENRICH_MORE_REVIEW, 1L))); + + assertThat(qaEventService.findAllSources(context, 0, 20), + hasItem(QASourceMatcher.with(COAR_NOTIFY_SOURCE, 1L))); + + verifyNoInteractions(mockBrokerClient); + } + + private Item createItem(String title, String handle) { + return ItemBuilder.createItem(context, collection) + .withTitle(title) + .withHandle(handle) + .build(); + } + + private Void writeToOutputStream(OutputStream outputStream, String fileName) { + try { + byte[] fileContent = getFileContent(fileName); + IOUtils.write(fileContent, outputStream); + return null; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private byte[] getFileContent(String fileName) throws Exception { + String fileLocation = getFileLocation(fileName); + try (FileInputStream fis = new FileInputStream(new File(fileLocation))) { + return IOUtils.toByteArray(fis); + } + } + + private String getFileLocation(String fileName) throws Exception { + URL resource = getClass().getClassLoader().getResource(BASE_JSON_DIR_PATH + fileName); + if (resource == null) { + throw new IllegalStateException("No resource found named " + BASE_JSON_DIR_PATH + fileName); + } + return new File(resource.getFile()).getAbsolutePath(); + } +} + diff --git a/dspace-api/src/test/java/org/dspace/solr/MockSolrServer.java b/dspace-api/src/test/java/org/dspace/solr/MockSolrServer.java index aed0c088c3..094275743b 100644 --- a/dspace-api/src/test/java/org/dspace/solr/MockSolrServer.java +++ b/dspace-api/src/test/java/org/dspace/solr/MockSolrServer.java @@ -47,10 +47,12 @@ public class MockSolrServer { private static final Logger log = LogManager.getLogger(); /** Shared embedded Solr connections, by name. */ - private static final ConcurrentMap loadedCores = new ConcurrentHashMap<>(); + private static final ConcurrentMap loadedCores + = new ConcurrentHashMap<>(); /** Reference counts for each core. */ - private static final ConcurrentMap usersPerCore = new ConcurrentHashMap<>(); + private static final ConcurrentMap usersPerCore + = new ConcurrentHashMap<>(); /** Container for embedded Solr cores. */ private static CoreContainer container = null; @@ -81,7 +83,7 @@ public class MockSolrServer { /** * Ensure that this instance's core is loaded. Create it if necessary. */ - protected void initSolrServer() { + private void initSolrServer() { solrServer = loadedCores.get(coreName); if (solrServer == null) { solrServer = initSolrServerForCore(coreName); @@ -103,7 +105,13 @@ public class MockSolrServer { if (server == null) { initSolrContainer(); - server = new EmbeddedSolrServer(container, coreName); + server = new EmbeddedSolrServer(container, coreName) { + // This ugliness should be fixed in Solr 8.9. + // https://issues.apache.org/jira/browse/SOLR-15085 + @Override public void close() { // Copied from Solr's own tests + // Do not close shared core container! + } + }; //Start with an empty index try { @@ -123,6 +131,11 @@ public class MockSolrServer { * Remove all records. */ public void reset() { + if (null == solrServer) { + log.warn("reset called with no server connection"); + return; + } + try { solrServer.deleteByQuery("*:*"); } catch (SolrServerException | IOException ex) { @@ -160,7 +173,8 @@ public class MockSolrServer { private static synchronized void initSolrContainer() { if (container == null) { Path solrDir = Paths.get(AbstractDSpaceIntegrationTest.getDspaceDir(), "solr"); - log.info("Initializing SOLR CoreContainer with directory {}", solrDir.toAbsolutePath().toString()); + log.info("Initializing SOLR CoreContainer with directory {}", + solrDir.toAbsolutePath().toString()); container = new CoreContainer(solrDir, new Properties()); container.load(); log.info("SOLR CoreContainer initialized"); diff --git a/dspace-api/src/test/java/org/dspace/statistics/EmbeddedSolrClientFactory.java b/dspace-api/src/test/java/org/dspace/statistics/EmbeddedSolrClientFactory.java new file mode 100644 index 0000000000..073734e875 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/statistics/EmbeddedSolrClientFactory.java @@ -0,0 +1,85 @@ +/** + * 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.statistics; + +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.file.Path; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.solr.client.solrj.SolrClient; +import org.dspace.solr.MockSolrServer; + +/** + * Factory of EmbeddedSolrClient instances. + * Wrapper for {@link org.dspace.solr.MockSolrServer}. Possibly useful for + * testing. + * + *

    + * To use this: + *

      + *
    1. {@code SolrClientFactory scf = new EmbeddedSolrClientFactory();}
    2. + *
    3. {@code SolrClient mycore = scf.getClient("mycore");}
    4. + *
    5. {@code mycore.this(); mycore.that();}
    6. + *
    7. {@code mycore.destroy();}
    8. + *
    + * + * @author mwood + */ +public class EmbeddedSolrClientFactory + implements SolrClientFactory { + private static final Logger log = LogManager.getLogger(); + + /** Name of this connection's core. */ + private String coreName; + + /** This instance's connection. */ + private SolrClient solrClient = null; + + private MockSolrServer mockSolrServer; + + @Override + public SolrClient getClient(String coreUrl) { + try { + coreName = Path.of(new URL(coreUrl).getPath()) + .getFileName() + .toString(); + } catch (MalformedURLException ex) { + log.warn("Unable to extract core name from URI '{}': {}", + coreUrl, ex.getMessage()); + } + + try { + mockSolrServer = new MockSolrServer(coreName); + solrClient = mockSolrServer.getSolrServer(); + } catch (Exception ex) { + log.warn("Failed to instantiate a MockSolrServer", ex); + solrClient = null; + } + return solrClient; + } + + /** + * Remove all records. + */ + public void reset() { + mockSolrServer.reset(); + } + + /** + * Decrease the reference count for connection to the current core. + * If now zero, shut down the connection and discard it. If no connections + * remain, destroy the container. + * + * @throws Exception passed through. + */ + public void destroy() throws Exception { + mockSolrServer.destroy(); + } +} diff --git a/dspace-api/src/test/java/org/dspace/statistics/SolrLoggerServiceImplIT.java b/dspace-api/src/test/java/org/dspace/statistics/SolrLoggerServiceImplIT.java new file mode 100644 index 0000000000..82f8680aea --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/statistics/SolrLoggerServiceImplIT.java @@ -0,0 +1,308 @@ +/** + * 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.statistics; + +import static org.dspace.statistics.SolrLoggerServiceImpl.DATE_FORMAT_8601; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.io.Writer; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.Date; + +import org.apache.commons.lang3.time.DateFormatUtils; +import org.apache.solr.client.solrj.SolrClient; +import org.apache.solr.client.solrj.SolrQuery; +import org.apache.solr.client.solrj.SolrServerException; +import org.apache.solr.client.solrj.response.QueryResponse; +import org.apache.solr.common.SolrDocument; +import org.apache.solr.common.SolrInputDocument; +import org.dspace.AbstractIntegrationTestWithDatabase; +import org.dspace.builder.CommunityBuilder; +import org.dspace.content.Community; +import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.core.Constants; +import org.dspace.core.factory.CoreServiceFactory; +import org.dspace.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; +import org.dspace.utils.DSpace; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +/** + * Test some methods of SolrLoggerServiceImpl. + * + * @author mwood + */ +public class SolrLoggerServiceImplIT + extends AbstractIntegrationTestWithDatabase { + private static final ConfigurationService cfg + = DSpaceServicesFactory.getInstance().getConfigurationService(); + + // Bot IP list should contain no RFC 1918 private addresses. + private static final String NOT_BOT_IP = "192.168.1.1"; + private static final String BOT_IP = "192.168.2.1"; + + private static final String NOT_BOT_DNS = "angel.com"; + private static final String BOT_DNS = "demon.com"; + + private static final String NOT_BOT_AGENT = "Firefox"; + private static final String BOT_AGENT = "Punchbot"; + + private static final String F_AGENT = "userAgent"; + private static final String F_DNS = "dns"; + private static final String F_EPERSON = "epersonid"; + private static final String F_ID = "id"; + private static final String F_IP = "ip"; + private static final String F_IS_BOT = "isBot"; + private static final String F_STATISTICS_TYPE = "statistics_type"; + private static final String F_TIME = "time"; + private static final String F_TYPE = "type"; + + private static final String Q_ALL = "*:*"; + + private static final String COMMUNITY_NAME = "Top"; + + private static Path testAddressesPath; + private static Path testAgentsPath; + + @BeforeClass + public static void setUpClass() + throws IOException { + Path spidersPath = Paths.get(cfg.getProperty("dspace.dir"), "config", "spiders"); + Writer writer; + + // Ensure the presence of a known "bot" address. + testAddressesPath = Files.createTempFile(spidersPath, "test-ips-", ".txt"); + writer = Files.newBufferedWriter(testAddressesPath, StandardCharsets.UTF_8, + StandardOpenOption.WRITE); + writer.append(BOT_IP) + .append('\n') + .close(); + + // Ensure the presence of a known "bot" agent. + testAgentsPath = Files.createTempFile(spidersPath.resolve("agents"), "test-agents-", ".txt"); + writer = Files.newBufferedWriter(testAgentsPath, StandardCharsets.UTF_8, + StandardOpenOption.WRITE); + writer.append('^') + .append(BOT_AGENT) + .append('\n') + .close(); + } + + @AfterClass + public static void tearDownClass() + throws IOException { + Files.deleteIfExists(testAddressesPath); + Files.deleteIfExists(testAgentsPath); + } + + @Before + public void setUpTest() { + } + + @After + public void tearDownTest() { + } + + /** + * Test of markRobots method, of class SolrLoggerServiceImpl. + * + * @throws SolrServerException passed through. + * @throws IOException passed through. + */ + @Test + public void testMarkRobots() + throws SolrServerException, IOException, Exception { + System.out.println("markRobots"); + + EmbeddedSolrClientFactory clientFactory = new EmbeddedSolrClientFactory(); + ContentServiceFactory csf = ContentServiceFactory.getInstance(); + DSpace dspace = new DSpace(); + + SolrLoggerServiceImpl instance = new SolrLoggerServiceImpl(); + instance.bitstreamService = csf.getBitstreamService(); + instance.contentServiceFactory = csf; + instance.configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); + instance.clientInfoService = CoreServiceFactory.getInstance().getClientInfoService(); + instance.geoIpService = dspace.getSingletonService(GeoIpService.class); + instance.solrStatisticsCore = dspace.getSingletonService(SolrStatisticsCore.class); + instance.afterPropertiesSet(); + + // Create objects to view. + context.turnOffAuthorisationSystem(); + Community topCommunity = CommunityBuilder.createCommunity(context) + .withName(COMMUNITY_NAME) + .build(); + context.restoreAuthSystemState(); + + // Set up some documents. + SolrClient client = clientFactory.getClient(cfg.getProperty("solr-statistics.server")); + SolrInputDocument doc = new SolrInputDocument(); + doc.setField(F_STATISTICS_TYPE, SolrLoggerServiceImpl.StatisticsType.VIEW); + doc.setField(F_TYPE, String.valueOf(Constants.COMMUNITY)); + doc.setField(F_ID, topCommunity.getID().toString()); + doc.setField(F_EPERSON, eperson.getID().toString()); + + doc.setField(F_IP, NOT_BOT_IP); + doc.setField(F_DNS, NOT_BOT_DNS); + doc.setField(F_AGENT, NOT_BOT_AGENT); + doc.setField(F_TIME, DateFormatUtils.format(new Date(), DATE_FORMAT_8601)); + client.add(doc); + + doc.setField(F_IP, BOT_IP); + doc.setField(F_DNS, BOT_DNS); + doc.setField(F_AGENT, NOT_BOT_AGENT); + doc.setField(F_TIME, DateFormatUtils.format(new Date(), DATE_FORMAT_8601)); + client.add(doc); + + doc.setField(F_IP, NOT_BOT_IP); + doc.setField(F_DNS, NOT_BOT_DNS); + doc.setField(F_AGENT, BOT_AGENT); + doc.setField(F_TIME, DateFormatUtils.format(new Date(), DATE_FORMAT_8601)); + client.add(doc); + + doc.setField(F_IP, BOT_IP); + doc.setField(F_DNS, BOT_DNS); + doc.setField(F_AGENT, BOT_AGENT); + doc.setField(F_TIME, DateFormatUtils.format(new Date(), DATE_FORMAT_8601)); + client.add(doc); + + client.commit(true, true); + + // Scan the core for robot entries and mark them. + cfg.setProperty("solr-statistics.query.filter.isBot", "false"); + instance.markRobots(); + + // Check that documents are marked correctly. + SolrQuery readbackQuery = new SolrQuery() + .setRows(10) + .setQuery(Q_ALL); + QueryResponse response = client.query(readbackQuery); + long nDocs = 0; + long nGood = 0; + for (SolrDocument document : response.getResults()) { + String ip = (String) document.getFieldValue(F_IP); + String agent = (String) document.getFieldValue(F_AGENT); + Object isBotRaw = document.getFieldValue(F_IS_BOT); + boolean isBot = (null == isBotRaw) ? false : (Boolean) isBotRaw; + + if (NOT_BOT_IP.equals(ip) && NOT_BOT_AGENT.equals(agent)) { + assertFalse(String.format("IP %s plus Agent %s is marked as bot --", ip, agent), + isBot); + } else { + assertTrue(String.format("IP %s or Agent %s is not marked as bot --", ip, agent), + isBot); + } + + nDocs++; + if (!isBot) { + nGood++; + } + } + assertEquals("Wrong number of documents", 4, nDocs); + assertEquals("Wrong number of non-bot views", 1, nGood); + } + + /** + * Test of deleteRobots method, of class SolrLoggerServiceImpl. + * @throws SolrServerException passed through. + * @throws IOException passed through. + */ + @Test + public void testDeleteRobots() + throws SolrServerException, IOException, Exception { + System.out.println("deleteRobots"); + + EmbeddedSolrClientFactory clientFactory = new EmbeddedSolrClientFactory(); + ContentServiceFactory csf = ContentServiceFactory.getInstance(); + DSpace dspace = new DSpace(); + + SolrLoggerServiceImpl instance = new SolrLoggerServiceImpl(); + instance.bitstreamService = csf.getBitstreamService(); + instance.contentServiceFactory = csf; + instance.configurationService = cfg; + instance.clientInfoService = CoreServiceFactory.getInstance().getClientInfoService(); + instance.geoIpService = dspace.getSingletonService(GeoIpService.class); + instance.solrStatisticsCore = dspace.getSingletonService(SolrStatisticsCore.class); + instance.afterPropertiesSet(); + + // Create objects to view. + context.turnOffAuthorisationSystem(); + Community topCommunity = CommunityBuilder.createCommunity(context) + .withName(COMMUNITY_NAME) + .build(); + context.restoreAuthSystemState(); + + // Set up some documents. + SolrClient client = clientFactory.getClient(cfg.getProperty("solr-statistics.server")); + SolrInputDocument doc = new SolrInputDocument(); + doc.setField(F_STATISTICS_TYPE, SolrLoggerServiceImpl.StatisticsType.VIEW); + doc.setField(F_TYPE, String.valueOf(Constants.COMMUNITY)); + doc.setField(F_ID, topCommunity.getID().toString()); + doc.setField(F_EPERSON, eperson.getID().toString()); + + doc.setField(F_IP, NOT_BOT_IP); + doc.setField(F_DNS, NOT_BOT_DNS); + doc.setField(F_AGENT, NOT_BOT_AGENT); + doc.setField(F_TIME, DateFormatUtils.format(new Date(), DATE_FORMAT_8601)); + doc.setField(F_IS_BOT, Boolean.FALSE.toString()); + client.add(doc); + + doc.setField(F_IP, BOT_IP); + doc.setField(F_DNS, BOT_DNS); + doc.setField(F_AGENT, NOT_BOT_AGENT); + doc.setField(F_TIME, DateFormatUtils.format(new Date(), DATE_FORMAT_8601)); + doc.setField(F_IS_BOT, Boolean.TRUE.toString()); + client.add(doc); + + doc.setField(F_IP, NOT_BOT_IP); + doc.setField(F_DNS, NOT_BOT_DNS); + doc.setField(F_AGENT, BOT_AGENT); + doc.setField(F_TIME, DateFormatUtils.format(new Date(), DATE_FORMAT_8601)); + doc.setField(F_IS_BOT, Boolean.TRUE.toString()); + client.add(doc); + + doc.setField(F_IP, BOT_IP); + doc.setField(F_DNS, BOT_DNS); + doc.setField(F_AGENT, BOT_AGENT); + doc.setField(F_TIME, DateFormatUtils.format(new Date(), DATE_FORMAT_8601)); + doc.setField(F_IS_BOT, Boolean.TRUE.toString()); + client.add(doc); + + client.commit(true, true); + + // Scan the core for marked robot entries and delete them. + instance.deleteRobots(); + + // Check that the correct documents (and only those) are gone. + QueryResponse response = instance.query(Q_ALL, null, null, + Integer.MAX_VALUE, -1, + null, null, null, null, null, true, 0); + long nDocs = 0; + for (SolrDocument document : response.getResults()) { + nDocs++; + + Object isBotRaw = document.getFieldValue(F_IS_BOT); + boolean isBot = (null == isBotRaw) ? false : (Boolean) isBotRaw; + + assertEquals("Marked document was not removed --", + false, isBot); + } + assertEquals("Wrong number of documents remaining --", 1, nDocs); + } +} diff --git a/dspace-api/src/test/java/org/dspace/statistics/export/ITIrusExportUsageEventListener.java b/dspace-api/src/test/java/org/dspace/statistics/export/ITIrusExportUsageEventListener.java index e28e8284a2..0c861a0d29 100644 --- a/dspace-api/src/test/java/org/dspace/statistics/export/ITIrusExportUsageEventListener.java +++ b/dspace-api/src/test/java/org/dspace/statistics/export/ITIrusExportUsageEventListener.java @@ -21,8 +21,8 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import java.util.regex.Pattern; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.codec.CharEncoding; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; diff --git a/dspace-api/src/test/java/org/dspace/statistics/export/ITRetryFailedOpenUrlTracker.java b/dspace-api/src/test/java/org/dspace/statistics/export/ITRetryFailedOpenUrlTracker.java index 7a9d031a0a..054b612d63 100644 --- a/dspace-api/src/test/java/org/dspace/statistics/export/ITRetryFailedOpenUrlTracker.java +++ b/dspace-api/src/test/java/org/dspace/statistics/export/ITRetryFailedOpenUrlTracker.java @@ -100,7 +100,7 @@ public class ITRetryFailedOpenUrlTracker extends AbstractIntegrationTest { } /** - * Test to check that all logged failed urls are reprocessed succesfully and removed from the db + * Test to check that all logged failed urls are reprocessed successfully and removed from the db * * @throws Exception */ diff --git a/dspace-api/src/test/java/org/dspace/statistics/export/processor/BitstreamEventProcessorIT.java b/dspace-api/src/test/java/org/dspace/statistics/export/processor/BitstreamEventProcessorIT.java index a690b1a1c6..605264e4ba 100644 --- a/dspace-api/src/test/java/org/dspace/statistics/export/processor/BitstreamEventProcessorIT.java +++ b/dspace-api/src/test/java/org/dspace/statistics/export/processor/BitstreamEventProcessorIT.java @@ -10,13 +10,14 @@ package org.dspace.statistics.export.processor; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.Is.is; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import java.io.File; import java.io.FileInputStream; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.codec.CharEncoding; import org.dspace.AbstractIntegrationTestWithDatabase; import org.dspace.builder.BitstreamBuilder; @@ -63,6 +64,7 @@ public class BitstreamEventProcessorIT extends AbstractIntegrationTestWithDataba */ public void testAddObectSpecificData() throws Exception { HttpServletRequest request = mock(HttpServletRequest.class); + when(request.getRemoteAddr()).thenReturn("127.0.0.1"); context.turnOffAuthorisationSystem(); Community community = CommunityBuilder.createCommunity(context).build(); diff --git a/dspace-api/src/test/java/org/dspace/statistics/export/processor/ExportEventProcessorIT.java b/dspace-api/src/test/java/org/dspace/statistics/export/processor/ExportEventProcessorIT.java index e42003e4fc..fb53d0c83c 100644 --- a/dspace-api/src/test/java/org/dspace/statistics/export/processor/ExportEventProcessorIT.java +++ b/dspace-api/src/test/java/org/dspace/statistics/export/processor/ExportEventProcessorIT.java @@ -18,8 +18,8 @@ import static org.mockito.Mockito.when; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.sql.SQLException; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.codec.CharEncoding; import org.dspace.AbstractIntegrationTestWithDatabase; import org.dspace.builder.CollectionBuilder; diff --git a/dspace-api/src/test/java/org/dspace/statistics/util/DummyHttpServletRequest.java b/dspace-api/src/test/java/org/dspace/statistics/util/DummyHttpServletRequest.java index 61325c652c..0059ce4f3f 100644 --- a/dspace-api/src/test/java/org/dspace/statistics/util/DummyHttpServletRequest.java +++ b/dspace-api/src/test/java/org/dspace/statistics/util/DummyHttpServletRequest.java @@ -19,21 +19,22 @@ import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; -import javax.servlet.AsyncContext; -import javax.servlet.DispatcherType; -import javax.servlet.RequestDispatcher; -import javax.servlet.ServletContext; -import javax.servlet.ServletException; -import javax.servlet.ServletInputStream; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; -import javax.servlet.http.HttpUpgradeHandler; -import javax.servlet.http.Part; +import jakarta.servlet.AsyncContext; +import jakarta.servlet.DispatcherType; +import jakarta.servlet.RequestDispatcher; +import jakarta.servlet.ServletConnection; +import jakarta.servlet.ServletContext; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletInputStream; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; +import jakarta.servlet.http.HttpUpgradeHandler; +import jakarta.servlet.http.Part; import org.apache.commons.collections.CollectionUtils; import org.dspace.core.Utils; @@ -64,7 +65,7 @@ public class DummyHttpServletRequest implements HttpServletRequest { } /* (non-Javadoc) - * @see javax.servlet.http.HttpServletRequest#changeSessionId + * @see jakarta.servlet.http.HttpServletRequest#changeSessionId */ @Override public String changeSessionId() { @@ -73,7 +74,7 @@ public class DummyHttpServletRequest implements HttpServletRequest { } /* (non-Javadoc) - * @see javax.servlet.http.HttpServletRequest#getAuthType() + * @see jakarta.servlet.http.HttpServletRequest#getAuthType() */ @Override public String getAuthType() { @@ -82,7 +83,7 @@ public class DummyHttpServletRequest implements HttpServletRequest { } /* (non-Javadoc) - * @see javax.servlet.http.HttpServletRequest#getContextPath() + * @see jakarta.servlet.http.HttpServletRequest#getContextPath() */ @Override public String getContextPath() { @@ -91,7 +92,7 @@ public class DummyHttpServletRequest implements HttpServletRequest { } /* (non-Javadoc) - * @see javax.servlet.http.HttpServletRequest#getCookies() + * @see jakarta.servlet.http.HttpServletRequest#getCookies() */ @Override public Cookie[] getCookies() { @@ -100,7 +101,7 @@ public class DummyHttpServletRequest implements HttpServletRequest { } /* (non-Javadoc) - * @see javax.servlet.http.HttpServletRequest#getDateHeader(java.lang.String) + * @see jakarta.servlet.http.HttpServletRequest#getDateHeader(java.lang.String) */ @Override public long getDateHeader(String arg0) { @@ -118,7 +119,7 @@ public class DummyHttpServletRequest implements HttpServletRequest { values.add(headerValue); } /* (non-Javadoc) - * @see javax.servlet.http.HttpServletRequest#getDispatcherType() + * @see jakarta.servlet.http.HttpServletRequest#getDispatcherType() */ @Override public DispatcherType getDispatcherType() { @@ -127,7 +128,31 @@ public class DummyHttpServletRequest implements HttpServletRequest { } /* (non-Javadoc) - * @see javax.servlet.http.HttpServletRequest#getHeader(java.lang.String) + * @see jakarta.servlet.http.HttpServletRequest#getRequestId() + */ + @Override + public String getRequestId() { + return null; + } + + /* (non-Javadoc) + * @see jakarta.servlet.http.HttpServletRequest#getProtocolRequestId() + */ + @Override + public String getProtocolRequestId() { + return null; + } + + /* (non-Javadoc) + * @see jakarta.servlet.http.HttpServletRequest#getServletConnection() + */ + @Override + public ServletConnection getServletConnection() { + return null; + } + + /* (non-Javadoc) + * @see jakarta.servlet.http.HttpServletRequest#getHeader(java.lang.String) */ @Override public String getHeader(String key) { @@ -139,7 +164,7 @@ public class DummyHttpServletRequest implements HttpServletRequest { } /* (non-Javadoc) - * @see javax.servlet.http.HttpServletRequest#getHeaderNames() + * @see jakarta.servlet.http.HttpServletRequest#getHeaderNames() */ @Override public Enumeration getHeaderNames() { @@ -147,7 +172,7 @@ public class DummyHttpServletRequest implements HttpServletRequest { } /* (non-Javadoc) - * @see javax.servlet.http.HttpServletRequest#getHeaders(java.lang.String) + * @see jakarta.servlet.http.HttpServletRequest#getHeaders(java.lang.String) */ @Override public Enumeration getHeaders(String arg0) { @@ -155,7 +180,7 @@ public class DummyHttpServletRequest implements HttpServletRequest { } /* (non-Javadoc) - * @see javax.servlet.http.HttpServletRequest#getIntHeader(java.lang.String) + * @see jakarta.servlet.http.HttpServletRequest#getIntHeader(java.lang.String) */ @Override public int getIntHeader(String arg0) { @@ -163,7 +188,7 @@ public class DummyHttpServletRequest implements HttpServletRequest { } /* (non-Javadoc) - * @see javax.servlet.http.HttpServletRequest#getMethod() + * @see jakarta.servlet.http.HttpServletRequest#getMethod() */ @Override public String getMethod() { @@ -172,7 +197,7 @@ public class DummyHttpServletRequest implements HttpServletRequest { } /* (non-Javadoc) - * @see javax.servlet.http.HttpServletRequest#getPathInfo() + * @see jakarta.servlet.http.HttpServletRequest#getPathInfo() */ @Override public String getPathInfo() { @@ -181,7 +206,7 @@ public class DummyHttpServletRequest implements HttpServletRequest { } /* (non-Javadoc) - * @see javax.servlet.http.HttpServletRequest#getPathTranslated() + * @see jakarta.servlet.http.HttpServletRequest#getPathTranslated() */ @Override public String getPathTranslated() { @@ -190,7 +215,7 @@ public class DummyHttpServletRequest implements HttpServletRequest { } /* (non-Javadoc) - * @see javax.servlet.http.HttpServletRequest#getQueryString() + * @see jakarta.servlet.http.HttpServletRequest#getQueryString() */ @Override public String getQueryString() { @@ -199,7 +224,7 @@ public class DummyHttpServletRequest implements HttpServletRequest { } /* (non-Javadoc) - * @see javax.servlet.http.HttpServletRequest#getRemoteUser() + * @see jakarta.servlet.http.HttpServletRequest#getRemoteUser() */ @Override public String getRemoteUser() { @@ -208,7 +233,7 @@ public class DummyHttpServletRequest implements HttpServletRequest { } /* (non-Javadoc) - * @see javax.servlet.http.HttpServletRequest#getRequestURI() + * @see jakarta.servlet.http.HttpServletRequest#getRequestURI() */ @Override public String getRequestURI() { @@ -217,7 +242,7 @@ public class DummyHttpServletRequest implements HttpServletRequest { } /* (non-Javadoc) - * @see javax.servlet.http.HttpServletRequest#getRequestURL() + * @see jakarta.servlet.http.HttpServletRequest#getRequestURL() */ @Override public StringBuffer getRequestURL() { @@ -226,7 +251,7 @@ public class DummyHttpServletRequest implements HttpServletRequest { } /* (non-Javadoc) - * @see javax.servlet.http.HttpServletRequest#getRequestedSessionId() + * @see jakarta.servlet.http.HttpServletRequest#getRequestedSessionId() */ @Override public String getRequestedSessionId() { @@ -235,7 +260,7 @@ public class DummyHttpServletRequest implements HttpServletRequest { } /* (non-Javadoc) - * @see javax.servlet.http.HttpServletRequest#getServletPath() + * @see jakarta.servlet.http.HttpServletRequest#getServletPath() */ @Override public String getServletPath() { @@ -244,7 +269,7 @@ public class DummyHttpServletRequest implements HttpServletRequest { } /* (non-Javadoc) - * @see javax.servlet.http.HttpServletRequest#getSession() + * @see jakarta.servlet.http.HttpServletRequest#getSession() */ @Override public HttpSession getSession() { @@ -253,7 +278,7 @@ public class DummyHttpServletRequest implements HttpServletRequest { } /* (non-Javadoc) - * @see javax.servlet.http.HttpServletRequest#getSession(boolean) + * @see jakarta.servlet.http.HttpServletRequest#getSession(boolean) */ @Override public HttpSession getSession(boolean arg0) { @@ -262,7 +287,7 @@ public class DummyHttpServletRequest implements HttpServletRequest { } /* (non-Javadoc) - * @see javax.servlet.http.HttpServletRequest#getUserPrincipal() + * @see jakarta.servlet.http.HttpServletRequest#getUserPrincipal() */ @Override public Principal getUserPrincipal() { @@ -271,7 +296,7 @@ public class DummyHttpServletRequest implements HttpServletRequest { } /* (non-Javadoc) - * @see javax.servlet.http.HttpServletRequest#isRequestedSessionIdFromCookie() + * @see jakarta.servlet.http.HttpServletRequest#isRequestedSessionIdFromCookie() */ @Override public boolean isRequestedSessionIdFromCookie() { @@ -280,7 +305,7 @@ public class DummyHttpServletRequest implements HttpServletRequest { } /* (non-Javadoc) - * @see javax.servlet.http.HttpServletRequest#isRequestedSessionIdFromURL() + * @see jakarta.servlet.http.HttpServletRequest#isRequestedSessionIdFromURL() */ @Override public boolean isRequestedSessionIdFromURL() { @@ -289,17 +314,7 @@ public class DummyHttpServletRequest implements HttpServletRequest { } /* (non-Javadoc) - * @see javax.servlet.http.HttpServletRequest#isRequestedSessionIdFromUrl() - */ - @Override - @Deprecated - public boolean isRequestedSessionIdFromUrl() { - // TODO Auto-generated method stub - return false; - } - - /* (non-Javadoc) - * @see javax.servlet.http.HttpServletRequest#authenticate(javax.servlet.http.HttpServletResponse) + * @see jakarta.servlet.http.HttpServletRequest#authenticate(jakarta.servlet.http.HttpServletResponse) */ @Override public boolean authenticate(HttpServletResponse httpServletResponse) { @@ -307,7 +322,7 @@ public class DummyHttpServletRequest implements HttpServletRequest { } /* (non-Javadoc) - * @see javax.servlet.http.HttpServletRequest#login(java.lang.String,java.lang.String) + * @see jakarta.servlet.http.HttpServletRequest#login(java.lang.String,java.lang.String) */ @Override public void login(String s, String s1) { @@ -315,7 +330,7 @@ public class DummyHttpServletRequest implements HttpServletRequest { } /* (non-Javadoc) - * @see javax.servlet.http.HttpServletRequest#logout() + * @see jakarta.servlet.http.HttpServletRequest#logout() */ @Override public void logout() { @@ -323,7 +338,7 @@ public class DummyHttpServletRequest implements HttpServletRequest { } /* (non-Javadoc) - * @see javax.servlet.http.HttpServletRequest#getPart(java.lang.String) + * @see jakarta.servlet.http.HttpServletRequest#getPart(java.lang.String) */ @Override public Part getPart(String arg0) { @@ -332,7 +347,7 @@ public class DummyHttpServletRequest implements HttpServletRequest { } /* (non-Javadoc) - * @see javax.servlet.http.HttpServletRequest#getParts() + * @see jakarta.servlet.http.HttpServletRequest#getParts() */ @Override public Collection getParts() { @@ -340,7 +355,7 @@ public class DummyHttpServletRequest implements HttpServletRequest { } /* (non-Javadoc) - * @see javax.servlet.http.HttpServletRequest#upgrade(java.lang.Class) + * @see jakarta.servlet.http.HttpServletRequest#upgrade(java.lang.Class) */ @Override public T upgrade(Class aClass) throws IOException, ServletException { @@ -348,7 +363,7 @@ public class DummyHttpServletRequest implements HttpServletRequest { } /* (non-Javadoc) - * @see javax.servlet.http.HttpServletRequest#isRequestedSessionIdValid() + * @see jakarta.servlet.http.HttpServletRequest#isRequestedSessionIdValid() */ @Override public boolean isRequestedSessionIdValid() { @@ -357,7 +372,7 @@ public class DummyHttpServletRequest implements HttpServletRequest { } /* (non-Javadoc) - * @see javax.servlet.http.HttpServletRequest#isUserInRole(java.lang.String) + * @see jakarta.servlet.http.HttpServletRequest#isUserInRole(java.lang.String) */ @Override public boolean isUserInRole(String arg0) { @@ -366,7 +381,7 @@ public class DummyHttpServletRequest implements HttpServletRequest { } /* (non-Javadoc) - * @see javax.servlet.ServletRequest#getAttribute(java.lang.String) + * @see jakarta.servlet.ServletRequest#getAttribute(java.lang.String) */ @Override public Object getAttribute(String arg0) { @@ -375,7 +390,7 @@ public class DummyHttpServletRequest implements HttpServletRequest { } /* (non-Javadoc) - * @see javax.servlet.ServletRequest#getAttributeNames() + * @see jakarta.servlet.ServletRequest#getAttributeNames() */ @Override public Enumeration getAttributeNames() { @@ -384,7 +399,7 @@ public class DummyHttpServletRequest implements HttpServletRequest { } /* (non-Javadoc) - * @see javax.servlet.ServletRequest#getCharacterEncoding() + * @see jakarta.servlet.ServletRequest#getCharacterEncoding() */ @Override public String getCharacterEncoding() { @@ -393,7 +408,7 @@ public class DummyHttpServletRequest implements HttpServletRequest { } /* (non-Javadoc) - * @see javax.servlet.ServletRequest#getContentLength() + * @see jakarta.servlet.ServletRequest#getContentLength() */ @Override public int getContentLength() { @@ -402,7 +417,7 @@ public class DummyHttpServletRequest implements HttpServletRequest { } /* (non-Javadoc) - * @see javax.servlet.ServletRequest#getContentLengthLong() + * @see jakarta.servlet.ServletRequest#getContentLengthLong() */ @Override public long getContentLengthLong() { @@ -410,7 +425,7 @@ public class DummyHttpServletRequest implements HttpServletRequest { } /* (non-Javadoc) - * @see javax.servlet.ServletRequest#getContentType() + * @see jakarta.servlet.ServletRequest#getContentType() */ @Override public String getContentType() { @@ -419,7 +434,7 @@ public class DummyHttpServletRequest implements HttpServletRequest { } /* (non-Javadoc) - * @see javax.servlet.ServletRequest#getInputStream() + * @see jakarta.servlet.ServletRequest#getInputStream() */ @Override public ServletInputStream getInputStream() throws IOException { @@ -428,7 +443,7 @@ public class DummyHttpServletRequest implements HttpServletRequest { } /* (non-Javadoc) - * @see javax.servlet.ServletRequest#getLocale() + * @see jakarta.servlet.ServletRequest#getLocale() */ @Override public Locale getLocale() { @@ -437,7 +452,7 @@ public class DummyHttpServletRequest implements HttpServletRequest { } /* (non-Javadoc) - * @see javax.servlet.ServletRequest#getLocales() + * @see jakarta.servlet.ServletRequest#getLocales() */ @Override public Enumeration getLocales() { @@ -446,7 +461,7 @@ public class DummyHttpServletRequest implements HttpServletRequest { } /* (non-Javadoc) - * @see javax.servlet.ServletRequest#getParameter(java.lang.String) + * @see jakarta.servlet.ServletRequest#getParameter(java.lang.String) */ @Override public String getParameter(String arg0) { @@ -455,7 +470,7 @@ public class DummyHttpServletRequest implements HttpServletRequest { } /* (non-Javadoc) - * @see javax.servlet.ServletRequest#getParameterMap() + * @see jakarta.servlet.ServletRequest#getParameterMap() */ @Override public Map getParameterMap() { @@ -464,7 +479,7 @@ public class DummyHttpServletRequest implements HttpServletRequest { } /* (non-Javadoc) - * @see javax.servlet.ServletRequest#getParameterNames() + * @see jakarta.servlet.ServletRequest#getParameterNames() */ @Override public Enumeration getParameterNames() { @@ -473,7 +488,7 @@ public class DummyHttpServletRequest implements HttpServletRequest { } /* (non-Javadoc) - * @see javax.servlet.ServletRequest#getParameterValues(java.lang.String) + * @see jakarta.servlet.ServletRequest#getParameterValues(java.lang.String) */ @Override public String[] getParameterValues(String arg0) { @@ -482,7 +497,7 @@ public class DummyHttpServletRequest implements HttpServletRequest { } /* (non-Javadoc) - * @see javax.servlet.ServletRequest#getProtocol() + * @see jakarta.servlet.ServletRequest#getProtocol() */ @Override public String getProtocol() { @@ -491,7 +506,7 @@ public class DummyHttpServletRequest implements HttpServletRequest { } /* (non-Javadoc) - * @see javax.servlet.ServletRequest#getReader() + * @see jakarta.servlet.ServletRequest#getReader() */ @Override public BufferedReader getReader() throws IOException { @@ -500,17 +515,7 @@ public class DummyHttpServletRequest implements HttpServletRequest { } /* (non-Javadoc) - * @see javax.servlet.ServletRequest#getRealPath(java.lang.String) - */ - @Override - @Deprecated - public String getRealPath(String arg0) { - // TODO Auto-generated method stub - return null; - } - - /* (non-Javadoc) - * @see javax.servlet.ServletRequest#getRemoteAddr() + * @see jakarta.servlet.ServletRequest#getRemoteAddr() */ @Override public String getRemoteAddr() { @@ -518,7 +523,7 @@ public class DummyHttpServletRequest implements HttpServletRequest { } /* (non-Javadoc) - * @see javax.servlet.ServletRequest#getRemoteHost() + * @see jakarta.servlet.ServletRequest#getRemoteHost() */ @Override public String getRemoteHost() { @@ -526,7 +531,7 @@ public class DummyHttpServletRequest implements HttpServletRequest { } /* (non-Javadoc) - * @see javax.servlet.ServletRequest#getRequestDispatcher(java.lang.String) + * @see jakarta.servlet.ServletRequest#getRequestDispatcher(java.lang.String) */ @Override public RequestDispatcher getRequestDispatcher(String arg0) { @@ -535,7 +540,7 @@ public class DummyHttpServletRequest implements HttpServletRequest { } /* (non-Javadoc) - * @see javax.servlet.ServletRequest#getScheme() + * @see jakarta.servlet.ServletRequest#getScheme() */ @Override public String getScheme() { @@ -544,7 +549,7 @@ public class DummyHttpServletRequest implements HttpServletRequest { } /* (non-Javadoc) - * @see javax.servlet.ServletRequest#getServerName() + * @see jakarta.servlet.ServletRequest#getServerName() */ @Override public String getServerName() { @@ -553,7 +558,7 @@ public class DummyHttpServletRequest implements HttpServletRequest { } /* (non-Javadoc) - * @see javax.servlet.ServletRequest#getServerPort() + * @see jakarta.servlet.ServletRequest#getServerPort() */ @Override public int getServerPort() { @@ -562,7 +567,7 @@ public class DummyHttpServletRequest implements HttpServletRequest { } /* (non-Javadoc) - * @see javax.servlet.ServletRequest#isSecure() + * @see jakarta.servlet.ServletRequest#isSecure() */ @Override public boolean isSecure() { @@ -571,7 +576,7 @@ public class DummyHttpServletRequest implements HttpServletRequest { } /* (non-Javadoc) - * @see javax.servlet.ServletRequest#removeAttribute(java.lang.String) + * @see jakarta.servlet.ServletRequest#removeAttribute(java.lang.String) */ @Override public void removeAttribute(String arg0) { @@ -579,7 +584,7 @@ public class DummyHttpServletRequest implements HttpServletRequest { } /* (non-Javadoc) - * @see javax.servlet.ServletRequest#setAttribute(java.lang.String, java.lang.Object) + * @see jakarta.servlet.ServletRequest#setAttribute(java.lang.String, java.lang.Object) */ @Override public void setAttribute(String arg0, Object arg1) { @@ -587,7 +592,7 @@ public class DummyHttpServletRequest implements HttpServletRequest { } /* (non-Javadoc) - * @see javax.servlet.ServletRequest#setCharacterEncoding(java.lang.String) + * @see jakarta.servlet.ServletRequest#setCharacterEncoding(java.lang.String) */ @Override public void setCharacterEncoding(String arg0) @@ -596,7 +601,7 @@ public class DummyHttpServletRequest implements HttpServletRequest { } /* (non-Javadoc) - * @see javax.servlet.ServletRequest#startAsync + * @see jakarta.servlet.ServletRequest#startAsync */ @Override public AsyncContext startAsync() throws IllegalStateException { @@ -604,7 +609,7 @@ public class DummyHttpServletRequest implements HttpServletRequest { } /* (non-Javadoc) - * @see javax.servlet.ServletRequest#startAsync(javax.servlet.ServletRequest,javax.servlet.ServletResponse) + * @see jakarta.servlet.ServletRequest#startAsync(jakarta.servlet.ServletRequest,jakarta.servlet.ServletResponse) */ @Override public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) @@ -613,7 +618,7 @@ public class DummyHttpServletRequest implements HttpServletRequest { } /* (non-Javadoc) - * @see javax.servlet.ServletRequest#isAsyncStarted + * @see jakarta.servlet.ServletRequest#isAsyncStarted */ @Override public boolean isAsyncStarted() { @@ -621,7 +626,7 @@ public class DummyHttpServletRequest implements HttpServletRequest { } /* (non-Javadoc) - * @see javax.servlet.ServletRequest#isAsyncSupported + * @see jakarta.servlet.ServletRequest#isAsyncSupported */ @Override public boolean isAsyncSupported() { @@ -629,7 +634,7 @@ public class DummyHttpServletRequest implements HttpServletRequest { } /* (non-Javadoc) - * @see javax.servlet.ServletRequest#getAsyncContext + * @see jakarta.servlet.ServletRequest#getAsyncContext */ @Override public AsyncContext getAsyncContext() { @@ -657,7 +662,7 @@ public class DummyHttpServletRequest implements HttpServletRequest { } /* (non-Javadoc) - * @see javax.servlet.ServletRequest#getServletContext + * @see jakarta.servlet.ServletRequest#getServletContext */ @Override public ServletContext getServletContext() { diff --git a/dspace-api/src/test/java/org/dspace/statistics/util/SpiderDetectorServiceImplTest.java b/dspace-api/src/test/java/org/dspace/statistics/util/SpiderDetectorServiceImplTest.java index 24f8c0f124..0b311a3115 100644 --- a/dspace-api/src/test/java/org/dspace/statistics/util/SpiderDetectorServiceImplTest.java +++ b/dspace-api/src/test/java/org/dspace/statistics/util/SpiderDetectorServiceImplTest.java @@ -94,7 +94,7 @@ public class SpiderDetectorServiceImplTest extends AbstractDSpaceTest { /** * Test method for - * {@link org.dspace.statistics.util.SpiderDetectorService#isSpider(javax.servlet.http.HttpServletRequest)}. + * {@link org.dspace.statistics.util.SpiderDetectorService#isSpider(jakarta.servlet.http.HttpServletRequest)}. */ @Test public void testIsSpiderHttpServletRequest() { diff --git a/dspace-api/src/test/java/org/dspace/statistics/util/SpiderDetectorTest.java b/dspace-api/src/test/java/org/dspace/statistics/util/SpiderDetectorTest.java index 63046b32b6..ba638e4ed7 100644 --- a/dspace-api/src/test/java/org/dspace/statistics/util/SpiderDetectorTest.java +++ b/dspace-api/src/test/java/org/dspace/statistics/util/SpiderDetectorTest.java @@ -49,7 +49,7 @@ public class SpiderDetectorTest extends AbstractDSpaceTest { /** * Test method for - * {@link org.dspace.statistics.util.SpiderDetector#isSpider(javax.servlet.http.HttpServletRequest)}. + * {@link org.dspace.statistics.util.SpiderDetector#isSpider(jakarta.servlet.http.HttpServletRequest)}. */ @Test public void testIsSpiderHttpServletRequest() { diff --git a/dspace-api/src/test/java/org/dspace/supervision/SupervisionOrderServiceIT.java b/dspace-api/src/test/java/org/dspace/supervision/SupervisionOrderServiceIT.java index 6040782348..aa4cd8bd4e 100644 --- a/dspace-api/src/test/java/org/dspace/supervision/SupervisionOrderServiceIT.java +++ b/dspace-api/src/test/java/org/dspace/supervision/SupervisionOrderServiceIT.java @@ -18,6 +18,7 @@ import org.dspace.builder.CollectionBuilder; import org.dspace.builder.CommunityBuilder; import org.dspace.builder.EPersonBuilder; import org.dspace.builder.GroupBuilder; +import org.dspace.builder.SupervisionOrderBuilder; import org.dspace.builder.WorkspaceItemBuilder; import org.dspace.content.Collection; import org.dspace.content.Item; @@ -85,10 +86,10 @@ public class SupervisionOrderServiceIT extends AbstractIntegrationTestWithDataba .build(); SupervisionOrder supervisionOrderOne = - supervisionOrderService.create(context, item, groupA); + SupervisionOrderBuilder.createSupervisionOrder(context, item, groupA).build(); SupervisionOrder supervisionOrderTwo = - supervisionOrderService.create(context, item, groupB); + SupervisionOrderBuilder.createSupervisionOrder(context, item, groupB).build(); context.restoreAuthSystemState(); @@ -136,7 +137,8 @@ public class SupervisionOrderServiceIT extends AbstractIntegrationTestWithDataba .build(); SupervisionOrder supervisionOrderOne = - supervisionOrderService.create(context, workspaceItem.getItem(), groupA); + SupervisionOrderBuilder.createSupervisionOrder(context, workspaceItem.getItem(), groupA) + .build(); context.restoreAuthSystemState(); @@ -205,9 +207,12 @@ public class SupervisionOrderServiceIT extends AbstractIntegrationTestWithDataba .addMember(userB) .build(); - supervisionOrderService.create(context, workspaceItem.getItem(), groupA); - supervisionOrderService.create(context, workspaceItem.getItem(), groupB); - supervisionOrderService.create(context, workspaceItemTwo.getItem(), groupA); + SupervisionOrderBuilder.createSupervisionOrder(context, workspaceItem.getItem(), groupA) + .build(); + SupervisionOrderBuilder.createSupervisionOrder(context, workspaceItem.getItem(), groupB) + .build(); + SupervisionOrderBuilder.createSupervisionOrder(context, workspaceItemTwo.getItem(), groupA) + .build(); context.restoreAuthSystemState(); @@ -259,9 +264,12 @@ public class SupervisionOrderServiceIT extends AbstractIntegrationTestWithDataba .addMember(eperson) .build(); - supervisionOrderService.create(context, workspaceItem.getItem(), groupA); - supervisionOrderService.create(context, workspaceItem.getItem(), groupB); - supervisionOrderService.create(context, workspaceItemTwo.getItem(), groupA); + SupervisionOrderBuilder.createSupervisionOrder(context, workspaceItem.getItem(), groupA) + .build(); + SupervisionOrderBuilder.createSupervisionOrder(context, workspaceItem.getItem(), groupB) + .build(); + SupervisionOrderBuilder.createSupervisionOrder(context, workspaceItemTwo.getItem(), groupA) + .build(); context.restoreAuthSystemState(); @@ -310,7 +318,8 @@ public class SupervisionOrderServiceIT extends AbstractIntegrationTestWithDataba .addMember(eperson) .build(); - supervisionOrderService.create(context, item, groupA); + SupervisionOrderBuilder.createSupervisionOrder(context, item, groupA) + .build(); context.restoreAuthSystemState(); @@ -370,7 +379,8 @@ public class SupervisionOrderServiceIT extends AbstractIntegrationTestWithDataba .addMember(userB) .build(); - supervisionOrderService.create(context, workspaceItem.getItem(), groupA); + SupervisionOrderBuilder.createSupervisionOrder(context, workspaceItem.getItem(), groupA) + .build(); context.restoreAuthSystemState(); diff --git a/dspace-api/src/test/java/org/dspace/util/DSpaceH2Dialect.java b/dspace-api/src/test/java/org/dspace/util/DSpaceH2Dialect.java new file mode 100644 index 0000000000..cd37a6fb31 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/util/DSpaceH2Dialect.java @@ -0,0 +1,50 @@ +/** + * 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.util; + +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +import org.hibernate.boot.model.FunctionContributions; +import org.hibernate.dialect.H2Dialect; +import org.hibernate.type.BasicTypeRegistry; +import org.hibernate.type.StandardBasicTypes; + +/** + * H2-specific dialect that adds regular expression support as a function. + * @author Jean-François Morin (Université Laval) + */ +public class DSpaceH2Dialect extends H2Dialect { + + private static Map regexCache = new HashMap<>(); + + @Override + public void initializeFunctionRegistry(FunctionContributions functionContributions) { + super.initializeFunctionRegistry(functionContributions); + + BasicTypeRegistry basicTypeRegistry = functionContributions.getTypeConfiguration().getBasicTypeRegistry(); + + functionContributions.getFunctionRegistry().registerPattern( + "matches", + "matches(?1, ?2)", + basicTypeRegistry.resolve(StandardBasicTypes.BOOLEAN)); + + // The SQL function is registered in AbstractIntegrationTestWithDatabase.initDatabase(). + } + + public static boolean matches(String regex, String value) { + Pattern pattern = regexCache.get(regex); + if (pattern == null) { + pattern = Pattern.compile(regex); + regexCache.put(regex, pattern); + } + return pattern.matcher(value).matches(); + } + +} diff --git a/dspace-api/src/test/java/org/dspace/util/DSpaceKernelInitializer.java b/dspace-api/src/test/java/org/dspace/util/DSpaceKernelInitializer.java index a6f381bafb..8f9169875a 100644 --- a/dspace-api/src/test/java/org/dspace/util/DSpaceKernelInitializer.java +++ b/dspace-api/src/test/java/org/dspace/util/DSpaceKernelInitializer.java @@ -83,6 +83,7 @@ public class DSpaceKernelInitializer * Initially look for JNDI Resource called "java:/comp/env/dspace.dir". * If not found, use value provided in "dspace.dir" in Spring Environment */ + @SuppressWarnings("BanJNDI") private String getDSpaceHome(ConfigurableEnvironment environment) { // Load the "dspace.dir" property from Spring's configuration. // This gives us the location of our DSpace configuration, which is diff --git a/dspace-api/src/test/java/org/dspace/util/RawJsonDeserializerTest.java b/dspace-api/src/test/java/org/dspace/util/RawJsonDeserializerTest.java new file mode 100644 index 0000000000..e1e6e246b9 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/util/RawJsonDeserializerTest.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.util; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import org.junit.Test; + +/** + * Unit tests for {@link RawJsonDeserializer}. + * + * @author Luca Giamminonni (luca.giamminonni at 4science.it) + * + */ +public class RawJsonDeserializerTest { + + private String json = "" + + "{" + + " \"attribute\": {" + + " \"firstField\":\"value\"," + + " \"secondField\": 1" + + " }" + + "}"; + + @Test + public void testDeserialization() throws JsonMappingException, JsonProcessingException { + + ObjectMapper mapper = new ObjectMapper(); + + DeserializationTestClass object = mapper.readValue(json, DeserializationTestClass.class); + assertThat(object, notNullValue()); + assertThat(object.getAttribute(), is("{\"firstField\":\"value\",\"secondField\":1}")); + + } + + private static class DeserializationTestClass { + + @JsonDeserialize(using = RawJsonDeserializer.class) + private String attribute; + + public String getAttribute() { + return attribute; + } + + } + +} diff --git a/dspace-api/src/test/java/org/dspace/workflow/CurationTaskConfigTest.java b/dspace-api/src/test/java/org/dspace/workflow/CurationTaskConfigTest.java index 92d6ff9148..61a283c89e 100644 --- a/dspace-api/src/test/java/org/dspace/workflow/CurationTaskConfigTest.java +++ b/dspace-api/src/test/java/org/dspace/workflow/CurationTaskConfigTest.java @@ -16,8 +16,8 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.List; -import javax.xml.bind.JAXBException; +import jakarta.xml.bind.JAXBException; import org.junit.BeforeClass; import org.junit.Test; import org.xml.sax.SAXException; diff --git a/dspace-api/src/test/java/org/dspace/workflow/WorkflowCurationIT.java b/dspace-api/src/test/java/org/dspace/workflow/WorkflowCurationIT.java index 66dd2cee80..d3866d534b 100644 --- a/dspace-api/src/test/java/org/dspace/workflow/WorkflowCurationIT.java +++ b/dspace-api/src/test/java/org/dspace/workflow/WorkflowCurationIT.java @@ -11,8 +11,8 @@ import static org.hamcrest.MatcherAssert.assertThat; import java.util.List; import java.util.regex.Pattern; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.dspace.AbstractIntegrationTestWithDatabase; import org.dspace.builder.CollectionBuilder; import org.dspace.builder.CommunityBuilder; diff --git a/dspace-api/src/test/java/org/dspace/xmlworkflow/XmlWorkflowServiceIT.java b/dspace-api/src/test/java/org/dspace/xmlworkflow/XmlWorkflowServiceIT.java index 865abaca21..20fd0cde26 100644 --- a/dspace-api/src/test/java/org/dspace/xmlworkflow/XmlWorkflowServiceIT.java +++ b/dspace-api/src/test/java/org/dspace/xmlworkflow/XmlWorkflowServiceIT.java @@ -12,8 +12,8 @@ import static org.junit.Assert.assertTrue; import java.io.IOException; import java.sql.SQLException; import java.util.List; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.AbstractIntegrationTestWithDatabase; import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.ResourcePolicy; diff --git a/dspace-api/src/test/java/org/dspace/xmlworkflow/state/actions/processingaction/NoAction.java b/dspace-api/src/test/java/org/dspace/xmlworkflow/state/actions/processingaction/NoAction.java index ae387c9749..f5e4521fcd 100644 --- a/dspace-api/src/test/java/org/dspace/xmlworkflow/state/actions/processingaction/NoAction.java +++ b/dspace-api/src/test/java/org/dspace/xmlworkflow/state/actions/processingaction/NoAction.java @@ -11,8 +11,8 @@ import java.io.IOException; import java.sql.SQLException; import java.util.Collections; import java.util.List; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.authorize.AuthorizeException; import org.dspace.core.Context; import org.dspace.workflow.WorkflowException; diff --git a/dspace-api/src/test/resources/org/dspace/app/openaire-events/empty-events-list.json b/dspace-api/src/test/resources/org/dspace/app/openaire-events/empty-events-list.json new file mode 100644 index 0000000000..0637a088a0 --- /dev/null +++ b/dspace-api/src/test/resources/org/dspace/app/openaire-events/empty-events-list.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/dspace/modules/rest/src/main/webapp/.gitignore b/dspace-api/src/test/resources/org/dspace/app/openaire-events/empty-file.json similarity index 100% rename from dspace/modules/rest/src/main/webapp/.gitignore rename to dspace-api/src/test/resources/org/dspace/app/openaire-events/empty-file.json diff --git a/dspace-api/src/test/resources/org/dspace/app/openaire-events/events.json b/dspace-api/src/test/resources/org/dspace/app/openaire-events/events.json new file mode 100644 index 0000000000..9bb8daae36 --- /dev/null +++ b/dspace-api/src/test/resources/org/dspace/app/openaire-events/events.json @@ -0,0 +1,62 @@ +[ + + { + "originalId": "oai:www.openstarts.units.it:123456789/99998", + "title": "Egypt, crossroad of translations and literary interweavings", + "topic": "ENRICH/MORE/PROJECT", + "trust": 1.0, + "message": { + "projects[0].acronym": "PAThs", + "projects[0].code": "687567", + "projects[0].funder": "EC", + "projects[0].fundingProgram": "H2020", + "projects[0].jurisdiction": "EU", + "projects[0].openaireId": "40|corda__h2020::6e32f5eb912688f2424c68b851483ea4", + "projects[0].title": "Tracking Papyrus and Parchment Paths" + } + }, + + { + "originalId": "oai:www.openstarts.units.it:123456789/99999", + "title": "Test Publication", + "topic": "ENRICH/MISSING/ABSTRACT", + "trust": 1.0, + "message": { + "abstracts[0]": "Missing Abstract" + } + }, + { + "originalId": "oai:www.openstarts.units.it:123456789/99998", + "title": "Egypt, crossroad of translations and literary interweavings", + "topic": "ENRICH/MISSING/PID", + "trust": 1.0, + "message": { + "pids[0].type": "doi", + "pids[0].value": "10.13137/2282-572x/987" + } + }, + { + "originalId": "oai:www.openstarts.units.it:123456789/99999", + "title": "Test Publication", + "topic": "ENRICH/MORE/PID", + "trust": 0.375, + "message": { + "pids[0].type": "doi", + "pids[0].value": "987654" + } + }, + { + "originalId": "oai:www.openstarts.units.it:123456789/99999", + "title": "Test Publication", + "topic": "ENRICH/MISSING/PROJECT", + "trust": 1.0, + "message": { + "projects[0].acronym": "02.SNES missing project acronym", + "projects[0].code": "prjcode_snes", + "projects[0].funder": "02.SNES missing project funder", + "projects[0].fundingProgram": "02.SNES missing project fundingProgram", + "projects[0].jurisdiction": "02.SNES missing project jurisdiction", + "projects[0].title": "Project01" + } + } +] \ No newline at end of file diff --git a/dspace-api/src/test/resources/org/dspace/app/openaire-events/unknown-topic-events.json b/dspace-api/src/test/resources/org/dspace/app/openaire-events/unknown-topic-events.json new file mode 100644 index 0000000000..3caa72cf35 --- /dev/null +++ b/dspace-api/src/test/resources/org/dspace/app/openaire-events/unknown-topic-events.json @@ -0,0 +1,20 @@ +[ + + { + "originalId": "oai:www.openstarts.units.it:123456789/99998", + "title": "Egypt, crossroad of translations and literary interweavings (3rd-6th centuries). A reconsideration of earlier Coptic literature", + "topic": "ENRICH/MORE/UNKNOWN", + "trust": 1.0 + }, + + { + "originalId": "oai:www.openstarts.units.it:123456789/999991", + "title": "Test Publication 2", + "topic": "ENRICH/MISSING/ABSTRACT", + "trust": 1.0, + "message": { + "abstracts[0]": "Missing Abstract" + } + } + +] \ No newline at end of file diff --git a/dspace-api/src/test/resources/org/dspace/app/orcid-works/works.xml b/dspace-api/src/test/resources/org/dspace/app/orcid-works/works.xml index 411160ef8e..9ad07607e3 100644 --- a/dspace-api/src/test/resources/org/dspace/app/orcid-works/works.xml +++ b/dspace-api/src/test/resources/org/dspace/app/orcid-works/works.xml @@ -77,6 +77,7 @@ Another cautionary tale. + Second title journal-article diff --git a/dspace-iiif/pom.xml b/dspace-iiif/pom.xml index 8cba905524..98683cdc9f 100644 --- a/dspace-iiif/pom.xml +++ b/dspace-iiif/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.6.2-SNAPSHOT + 9.0-SNAPSHOT .. @@ -28,6 +28,12 @@ + + org.apache.logging.log4j + log4j-api + ${log4j.version} + + org.springframework.boot @@ -69,6 +75,13 @@ org.springframework.boot spring-boot-starter-security ${spring-boot.version} + + + + io.micrometer + micrometer-observation + + diff --git a/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/AnnotationGenerator.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/AnnotationGenerator.java index 3947df3533..f6d7aa8950 100644 --- a/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/AnnotationGenerator.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/AnnotationGenerator.java @@ -9,11 +9,11 @@ package org.dspace.app.iiif.model.generator; import java.util.ArrayList; import java.util.List; -import javax.validation.constraints.NotNull; import de.digitalcollections.iiif.model.Motivation; import de.digitalcollections.iiif.model.openannotation.Annotation; import de.digitalcollections.iiif.model.sharedcanvas.Resource; +import jakarta.validation.constraints.NotNull; /** diff --git a/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/AnnotationListGenerator.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/AnnotationListGenerator.java index da977a5ccc..924cf48a9f 100644 --- a/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/AnnotationListGenerator.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/AnnotationListGenerator.java @@ -9,11 +9,11 @@ package org.dspace.app.iiif.model.generator; import java.util.ArrayList; import java.util.List; -import javax.validation.constraints.NotNull; import de.digitalcollections.iiif.model.openannotation.Annotation; import de.digitalcollections.iiif.model.sharedcanvas.AnnotationList; import de.digitalcollections.iiif.model.sharedcanvas.Resource; +import jakarta.validation.constraints.NotNull; import org.springframework.stereotype.Component; import org.springframework.web.context.annotation.RequestScope; diff --git a/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/CanvasGenerator.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/CanvasGenerator.java index f064a1b974..701a79aa4b 100644 --- a/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/CanvasGenerator.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/CanvasGenerator.java @@ -9,12 +9,12 @@ package org.dspace.app.iiif.model.generator; import java.util.ArrayList; import java.util.List; -import javax.validation.constraints.NotNull; import de.digitalcollections.iiif.model.ImageContent; import de.digitalcollections.iiif.model.MetadataEntry; import de.digitalcollections.iiif.model.sharedcanvas.Canvas; import de.digitalcollections.iiif.model.sharedcanvas.Resource; +import jakarta.validation.constraints.NotNull; /** * This generator wraps the domain model for a single {@code Canvas}. diff --git a/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/ContentSearchGenerator.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/ContentSearchGenerator.java index 28cc13c07d..80af636d35 100644 --- a/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/ContentSearchGenerator.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/ContentSearchGenerator.java @@ -10,11 +10,11 @@ package org.dspace.app.iiif.model.generator; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; -import javax.validation.constraints.NotNull; import de.digitalcollections.iiif.model.Profile; import de.digitalcollections.iiif.model.Service; import de.digitalcollections.iiif.model.search.ContentSearchService; +import jakarta.validation.constraints.NotNull; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.context.annotation.RequestScope; diff --git a/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/ExternalLinksGenerator.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/ExternalLinksGenerator.java index 94c1828375..709983e9c5 100644 --- a/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/ExternalLinksGenerator.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/ExternalLinksGenerator.java @@ -7,11 +7,10 @@ */ package org.dspace.app.iiif.model.generator; -import javax.validation.constraints.NotNull; - import de.digitalcollections.iiif.model.OtherContent; import de.digitalcollections.iiif.model.PropertyValue; import de.digitalcollections.iiif.model.sharedcanvas.Resource; +import jakarta.validation.constraints.NotNull; /** * This generator wraps the other content domain model. diff --git a/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/ImageContentGenerator.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/ImageContentGenerator.java index aef979b635..235e7413f6 100644 --- a/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/ImageContentGenerator.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/ImageContentGenerator.java @@ -7,10 +7,9 @@ */ package org.dspace.app.iiif.model.generator; -import javax.validation.constraints.NotNull; - import de.digitalcollections.iiif.model.ImageContent; import de.digitalcollections.iiif.model.sharedcanvas.Resource; +import jakarta.validation.constraints.NotNull; /** * This service generator wraps the image content model. diff --git a/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/ManifestGenerator.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/ManifestGenerator.java index 8072692640..dd196154ec 100644 --- a/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/ManifestGenerator.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/ManifestGenerator.java @@ -10,7 +10,6 @@ package org.dspace.app.iiif.model.generator; import java.net.URI; import java.util.ArrayList; import java.util.List; -import javax.validation.constraints.NotNull; import de.digitalcollections.iiif.model.ImageContent; import de.digitalcollections.iiif.model.MetadataEntry; @@ -22,6 +21,7 @@ import de.digitalcollections.iiif.model.sharedcanvas.Manifest; import de.digitalcollections.iiif.model.sharedcanvas.Range; import de.digitalcollections.iiif.model.sharedcanvas.Resource; import de.digitalcollections.iiif.model.sharedcanvas.Sequence; +import jakarta.validation.constraints.NotNull; import org.springframework.stereotype.Component; import org.springframework.web.context.annotation.RequestScope; diff --git a/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/RangeGenerator.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/RangeGenerator.java index fe8acf31c4..5b022bc916 100644 --- a/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/RangeGenerator.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/RangeGenerator.java @@ -9,12 +9,12 @@ package org.dspace.app.iiif.model.generator; import java.util.ArrayList; import java.util.List; -import javax.validation.constraints.NotNull; import de.digitalcollections.iiif.model.enums.ViewingHint; import de.digitalcollections.iiif.model.sharedcanvas.Canvas; import de.digitalcollections.iiif.model.sharedcanvas.Range; import de.digitalcollections.iiif.model.sharedcanvas.Resource; +import jakarta.validation.constraints.NotNull; import org.dspace.app.iiif.service.RangeService; /** diff --git a/dspace-iiif/src/main/java/org/dspace/app/iiif/service/utils/IIIFUtils.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/utils/IIIFUtils.java index 782a5a9852..1a1005993e 100644 --- a/dspace-iiif/src/main/java/org/dspace/app/iiif/service/utils/IIIFUtils.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/utils/IIIFUtils.java @@ -175,7 +175,7 @@ public class IIIFUtils { } /** - * Retrives a bitstream based on its position in the IIIF bundle. + * Retrieves a bitstream based on its position in the IIIF bundle. * * @param context DSpace Context * @param item DSpace Item @@ -361,7 +361,7 @@ public class IIIFUtils { * * @param item the dspace item * @param defaultHint the default hint to apply if nothing else is defined at - * the item leve + * the item level * @return the iiif viewing hint for the item */ public String getIIIFViewingHint(Item item, String defaultHint) { diff --git a/dspace-oai/pom.xml b/dspace-oai/pom.xml index 748f6ad2de..dbfaee3f69 100644 --- a/dspace-oai/pom.xml +++ b/dspace-oai/pom.xml @@ -8,7 +8,7 @@ dspace-parent org.dspace - 7.6.2-SNAPSHOT + 9.0-SNAPSHOT .. @@ -118,8 +118,8 @@ - javax.servlet - javax.servlet-api + jakarta.servlet + jakarta.servlet-api provided diff --git a/dspace-oai/src/main/java/org/dspace/app/configuration/OAIWebConfig.java b/dspace-oai/src/main/java/org/dspace/app/configuration/OAIWebConfig.java index 806ecf9d07..dc4efde880 100644 --- a/dspace-oai/src/main/java/org/dspace/app/configuration/OAIWebConfig.java +++ b/dspace-oai/src/main/java/org/dspace/app/configuration/OAIWebConfig.java @@ -19,7 +19,7 @@ import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; /** * OAI-PMH webapp configuration. Replaces the old web.xml @@ -36,7 +36,7 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter @Import(BasicConfiguration.class) // Scan for controllers in this package @ComponentScan("org.dspace.xoai.controller") -public class OAIWebConfig extends WebMvcConfigurerAdapter implements JtwigViewResolverConfigurer { +public class OAIWebConfig implements WebMvcConfigurer, JtwigViewResolverConfigurer { // Path where OAI is deployed. Defaults to "oai" // NOTE: deployment on this path is handled by org.dspace.xoai.controller.DSpaceOAIDataProvider diff --git a/dspace-oai/src/main/java/org/dspace/xoai/app/XOAI.java b/dspace-oai/src/main/java/org/dspace/xoai/app/XOAI.java index 4f842b8e94..25cc1ee365 100644 --- a/dspace-oai/src/main/java/org/dspace/xoai/app/XOAI.java +++ b/dspace-oai/src/main/java/org/dspace/xoai/app/XOAI.java @@ -450,6 +450,16 @@ public class XOAI { doc.addField("item.communities", "com_" + com.getHandle().replace("/", "_")); } + boolean hasBitstream = false; + + for (Bundle b : item.getBundles("ORIGINAL")) { + if (b.getBitstreams().size() > 0) { + hasBitstream = true; + } + } + + doc.addField("item.hasbitstream", hasBitstream); + List allData = itemService.getMetadata(item, Item.ANY, Item.ANY, Item.ANY, Item.ANY); for (MetadataValue dc : allData) { MetadataField field = dc.getMetadataField(); diff --git a/dspace-oai/src/main/java/org/dspace/xoai/controller/DSpaceOAIDataProvider.java b/dspace-oai/src/main/java/org/dspace/xoai/controller/DSpaceOAIDataProvider.java index 379f2fa181..3d826152c6 100644 --- a/dspace-oai/src/main/java/org/dspace/xoai/controller/DSpaceOAIDataProvider.java +++ b/dspace-oai/src/main/java/org/dspace/xoai/controller/DSpaceOAIDataProvider.java @@ -7,8 +7,8 @@ */ package org.dspace.xoai.controller; +import static jakarta.servlet.http.HttpServletResponse.SC_BAD_REQUEST; import static java.util.Arrays.asList; -import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST; import static org.apache.logging.log4j.LogManager.getLogger; import java.io.IOException; @@ -17,9 +17,6 @@ import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Map; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import javax.xml.stream.XMLStreamException; import com.lyncode.xoai.dataprovider.OAIDataProvider; @@ -28,6 +25,9 @@ import com.lyncode.xoai.dataprovider.core.XOAIManager; import com.lyncode.xoai.dataprovider.exceptions.InvalidContextException; import com.lyncode.xoai.dataprovider.exceptions.OAIException; import com.lyncode.xoai.dataprovider.exceptions.WritingXmlException; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.apache.logging.log4j.Logger; import org.dspace.core.Context; import org.dspace.xoai.services.api.cache.XOAICacheService; diff --git a/dspace-oai/src/main/java/org/dspace/xoai/filter/ItemsWithBitstreamFilter.java b/dspace-oai/src/main/java/org/dspace/xoai/filter/ItemsWithBitstreamFilter.java new file mode 100644 index 0000000000..3599c5b9e1 --- /dev/null +++ b/dspace-oai/src/main/java/org/dspace/xoai/filter/ItemsWithBitstreamFilter.java @@ -0,0 +1,57 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.xoai.filter; + +import java.sql.SQLException; + +import org.apache.log4j.LogManager; +import org.apache.log4j.Logger; +import org.dspace.content.Bundle; +import org.dspace.content.Item; +import org.dspace.handle.factory.HandleServiceFactory; +import org.dspace.handle.service.HandleService; +import org.dspace.xoai.data.DSpaceItem; +import org.dspace.xoai.filter.results.SolrFilterResult; + + +/** + * Created by Philip Vissenaekens (philip at atmire dot com) + * Date: 21/04/15 + * Time: 15:18 + */ +public class ItemsWithBitstreamFilter extends DSpaceFilter { + + private static Logger log = LogManager.getLogger(ItemsWithBitstreamFilter.class); + + private static final HandleService handleService + = HandleServiceFactory.getInstance().getHandleService(); + + @Override + public SolrFilterResult buildSolrQuery() { + return new SolrFilterResult("item.hasbitstream:true"); + } + + @Override + public boolean isShown(DSpaceItem item) { + try { + String handle = DSpaceItem.parseHandle(item.getIdentifier()); + if (handle == null) { + return false; + } + Item dspaceItem = (Item) handleService.resolveToObject(context, handle); + for (Bundle b : dspaceItem.getBundles("ORIGINAL")) { + if (b.getBitstreams().size() > 0) { + return true; + } + } + } catch (SQLException e) { + log.error(e.getMessage(), e); + } + return false; + } +} diff --git a/dspace-oai/src/main/java/org/dspace/xoai/services/impl/DSpaceEarliestDateResolver.java b/dspace-oai/src/main/java/org/dspace/xoai/services/impl/DSpaceEarliestDateResolver.java index 6c378ff6d1..733c1b67e0 100644 --- a/dspace-oai/src/main/java/org/dspace/xoai/services/impl/DSpaceEarliestDateResolver.java +++ b/dspace-oai/src/main/java/org/dspace/xoai/services/impl/DSpaceEarliestDateResolver.java @@ -9,8 +9,8 @@ package org.dspace.xoai.services.impl; import java.sql.SQLException; import java.util.Date; -import javax.persistence.NoResultException; +import jakarta.persistence.NoResultException; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.content.MetadataValue; diff --git a/dspace-oai/src/main/java/org/dspace/xoai/services/impl/DSpaceHandleResolver.java b/dspace-oai/src/main/java/org/dspace/xoai/services/impl/DSpaceHandleResolver.java index 6ed44bdcc2..fdce0a23a6 100644 --- a/dspace-oai/src/main/java/org/dspace/xoai/services/impl/DSpaceHandleResolver.java +++ b/dspace-oai/src/main/java/org/dspace/xoai/services/impl/DSpaceHandleResolver.java @@ -8,8 +8,8 @@ package org.dspace.xoai.services.impl; import java.sql.SQLException; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.dspace.content.DSpaceObject; import org.dspace.handle.factory.HandleServiceFactory; import org.dspace.handle.service.HandleService; diff --git a/dspace-oai/src/main/java/org/dspace/xoai/services/impl/context/DSpaceContextService.java b/dspace-oai/src/main/java/org/dspace/xoai/services/impl/context/DSpaceContextService.java index 904b7f8885..e86d15a4fa 100644 --- a/dspace-oai/src/main/java/org/dspace/xoai/services/impl/context/DSpaceContextService.java +++ b/dspace-oai/src/main/java/org/dspace/xoai/services/impl/context/DSpaceContextService.java @@ -7,8 +7,7 @@ */ package org.dspace.xoai.services.impl.context; -import javax.servlet.http.HttpServletRequest; - +import jakarta.servlet.http.HttpServletRequest; import org.dspace.core.Context; import org.dspace.xoai.services.api.context.ContextService; import org.dspace.xoai.services.api.context.ContextServiceException; diff --git a/dspace-oai/src/main/java/org/dspace/xoai/services/impl/xoai/DSpaceRepositoryConfiguration.java b/dspace-oai/src/main/java/org/dspace/xoai/services/impl/xoai/DSpaceRepositoryConfiguration.java index 2a000f43ea..8c9841dfb9 100644 --- a/dspace-oai/src/main/java/org/dspace/xoai/services/impl/xoai/DSpaceRepositoryConfiguration.java +++ b/dspace-oai/src/main/java/org/dspace/xoai/services/impl/xoai/DSpaceRepositoryConfiguration.java @@ -14,11 +14,11 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.Date; import java.util.List; -import javax.servlet.http.HttpServletRequest; import com.lyncode.xoai.dataprovider.core.DeleteMethod; import com.lyncode.xoai.dataprovider.core.Granularity; import com.lyncode.xoai.dataprovider.services.api.RepositoryConfiguration; +import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.io.FileUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; diff --git a/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java b/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java index b329835813..20dcabcb20 100644 --- a/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java +++ b/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java @@ -11,6 +11,8 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.sql.SQLException; +import java.text.SimpleDateFormat; +import java.util.Date; import java.util.List; import com.lyncode.xoai.dataprovider.xml.xoai.Element; @@ -21,6 +23,7 @@ import org.apache.logging.log4j.Logger; import org.dspace.app.util.factory.UtilServiceFactory; import org.dspace.app.util.service.MetadataExposureService; import org.dspace.authorize.AuthorizeException; +import org.dspace.authorize.ResourcePolicy; import org.dspace.authorize.factory.AuthorizeServiceFactory; import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.Bitstream; @@ -63,7 +66,7 @@ public class ItemUtils { = DSpaceServicesFactory.getInstance().getConfigurationService(); private static final AuthorizeService authorizeService - = AuthorizeServiceFactory.getInstance().getAuthorizeService(); + = AuthorizeServiceFactory.getInstance().getAuthorizeService(); /** * Default constructor @@ -114,23 +117,21 @@ public class ItemUtils { log.error("Null bitstream found, check item uuid: " + item.getID()); break; } + boolean primary = false; + // Check if current bitstream is in original bundle + 1 of the 2 following + // Bitstream = primary bitstream in bundle -> true + // No primary bitstream found in bundle-> only the first one gets flagged as "primary" + if (b.getName() != null && b.getName().equals("ORIGINAL") && (b.getPrimaryBitstream() != null + && b.getPrimaryBitstream().getID() == bit.getID() + || b.getPrimaryBitstream() == null && bit.getID() == bits.get(0).getID())) { + primary = true; + } + Element bitstream = create("bitstream"); bitstreams.getElement().add(bitstream); - String url = ""; - String bsName = bit.getName(); - String sid = String.valueOf(bit.getSequenceID()); + String baseUrl = configurationService.getProperty("oai.bitstream.baseUrl"); - String handle = null; - // get handle of parent Item of this bitstream, if there - // is one: - List bn = bit.getBundles(); - if (!bn.isEmpty()) { - List bi = bn.get(0).getItems(); - if (!bi.isEmpty()) { - handle = bi.get(0).getHandle(); - } - } - url = baseUrl + "/bitstreams/" + bit.getID().toString() + "/download"; + String url = baseUrl + "/bitstreams/" + bit.getID().toString() + "/download"; String cks = bit.getChecksum(); String cka = bit.getChecksumAlgorithm(); @@ -147,18 +148,65 @@ public class ItemUtils { if (description != null) { bitstream.getField().add(createValue("description", description)); } + // Add bitstream embargo information (READ policy present, for Anonymous group with a start date) + addResourcePolicyInformation(context, bit, bitstream); + bitstream.getField().add(createValue("format", bit.getFormat(context).getMIMEType())); bitstream.getField().add(createValue("size", "" + bit.getSizeBytes())); bitstream.getField().add(createValue("url", url)); bitstream.getField().add(createValue("checksum", cks)); bitstream.getField().add(createValue("checksumAlgorithm", cka)); bitstream.getField().add(createValue("sid", bit.getSequenceID() + "")); + // Add primary bitstream field to allow locating easily the primary bitstream information + bitstream.getField().add(createValue("primary", primary + "")); } } return bundles; } + /** + * This method will add metadata information about associated resource policies for a give bitstream. + * It will parse of relevant policies and add metadata information + * @param context + * @param bitstream the bitstream object + * @param bitstreamEl the bitstream metadata object to add resource policy information to + * @throws SQLException + */ + private static void addResourcePolicyInformation(Context context, Bitstream bitstream, Element bitstreamEl) + throws SQLException { + // Pre-filter access policies by DSO (bitstream) and Action (READ) + List policies = authorizeService.getPoliciesActionFilter(context, bitstream, Constants.READ); + + // Create resourcePolicies container + Element resourcePolicies = create("resourcePolicies"); + + for (ResourcePolicy policy : policies) { + String groupName = policy.getGroup() != null ? policy.getGroup().getName() : null; + String user = policy.getEPerson() != null ? policy.getEPerson().getName() : null; + String action = Constants.actionText[policy.getAction()]; + Date startDate = policy.getStartDate(); + Date endDate = policy.getEndDate(); + + SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd"); + + Element resourcePolicyEl = create("resourcePolicy"); + resourcePolicyEl.getField().add(createValue("group", groupName)); + resourcePolicyEl.getField().add(createValue("user", user)); + resourcePolicyEl.getField().add(createValue("action", action)); + if (startDate != null) { + resourcePolicyEl.getField().add(createValue("start-date", formatter.format(startDate))); + } + if (endDate != null) { + resourcePolicyEl.getField().add(createValue("end-date", formatter.format(endDate))); + } + // Add resourcePolicy to list of resourcePolicies + resourcePolicies.getElement().add(resourcePolicyEl); + } + // Add list of resource policies to the corresponding Bitstream XML Element + bitstreamEl.getElement().add(resourcePolicies); + } + private static Element createLicenseElement(Context context, Item item) throws SQLException, AuthorizeException, IOException { Element license = create("license"); @@ -178,7 +226,7 @@ public class ItemUtils { license.getField().add(createValue("bin", Base64Utils.encode(out.toString()))); } else { log.info("Missing READ rights for license bitstream. Did not include license bitstream for item: " - + item.getID() + "."); + + item.getID() + "."); } } } diff --git a/dspace-oai/src/test/java/org/dspace/xoai/tests/stylesheets/RioxxXslTest.java b/dspace-oai/src/test/java/org/dspace/xoai/tests/stylesheets/RioxxXslTest.java new file mode 100644 index 0000000000..74dfaf2902 --- /dev/null +++ b/dspace-oai/src/test/java/org/dspace/xoai/tests/stylesheets/RioxxXslTest.java @@ -0,0 +1,35 @@ +/** + * 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.xoai.tests.stylesheets; + +import static org.dspace.xoai.tests.support.XmlMatcherBuilder.xml; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; +import static org.hamcrest.core.IsEqual.equalTo; + +import org.dspace.xoai.tests.support.XmlMatcherBuilder; +import org.junit.Test; + +public class RioxxXslTest extends AbstractXSLTest { + @Test + public void rioxxCanTransformInput() throws Exception { + String result = apply("rioxx.xsl").to(resource("xoai-rioxx-test.xml")); + + assertThat(result, is(rioxx().withXPath("//dc:title", equalTo("The Intercorrelation Between " + + "Executive Function, Physics Problem Solving, Mathematical, and Matrix Reasoning Skills: " + + "Reflections from a Small-Scale Experiment")))); + } + + private XmlMatcherBuilder rioxx() { + return xml() + .withNamespace("rioxx", "http://www.rioxx.net/schema/v3.0/rioxx/") + .withNamespace("rioxxterms", "http://docs.rioxx.net/schema/v3.0/rioxxterms/") + .withNamespace("dcterms", "http://purl.org/dc/terms/") + .withNamespace("dc", "http://purl.org/dc/elements/1.1/"); + } +} diff --git a/dspace-oai/src/test/java/org/dspace/xoai/tests/unit/services/impl/AbstractQueryResolverTest.java b/dspace-oai/src/test/java/org/dspace/xoai/tests/unit/services/impl/AbstractQueryResolverTest.java index 53fc643449..b1ae4d209f 100644 --- a/dspace-oai/src/test/java/org/dspace/xoai/tests/unit/services/impl/AbstractQueryResolverTest.java +++ b/dspace-oai/src/test/java/org/dspace/xoai/tests/unit/services/impl/AbstractQueryResolverTest.java @@ -38,7 +38,7 @@ public abstract class AbstractQueryResolverTest { @After public void tearDown() { - //Nullify all resoruces so that JUnit cleans them up + //Nullify all resources so that JUnit cleans them up applicationContext = null; handleResolver = null; collectionsService = null; diff --git a/dspace-oai/src/test/resources/rioxx-test-invalid.xml b/dspace-oai/src/test/resources/rioxx-test-invalid.xml new file mode 100644 index 0000000000..c8daf1a28d --- /dev/null +++ b/dspace-oai/src/test/resources/rioxx-test-invalid.xml @@ -0,0 +1,89 @@ + + + Data on Secchi disc depth (the depth at which a standard white disc lowered into the water just becomes invisible to a surface observer) show that water clarity in the North Sea declined during the 20th century, with likely consequences for marine primary production. However, the causes of this trend remain unknown. Here we analyse the hypothesis that changes in the North Sea's wave climate were largely responsible by causing an increase in the concentrations of suspended particulate matter (SPM) in the water column through the resuspension of seabed sediments. First, we analysed the broad-scale statistical relationships between SPM and bed shear stress due to waves and tides. We used hindcasts of wave and current data to construct a space–time dataset of bed shear stress between 1997 and 2017 across the northwest European Continental Shelf and compared the results with satellite-derived SPM concentrations. Bed shear stress was found to drive most of the inter-annual variation in SPM in the hydrographically mixed waters of the central and southern North Sea. We then used a long-term wave reanalysis to construct a time series of bed shear stress from 1900 to 2010. This shows that bed shear stress increased significantly across much of the shelf during this period, with increases of over 20 % in the southeastern North Sea. An increase in bed shear stress of this magnitude would have resulted in a large reduction in water clarity. Wave-driven processes are rarely included in projections of climate change impacts on marine ecosystems, but our analysis indicates that this should be reconsidered for shelf sea regions. + + en + + + European Geosciences Union + https://isni.org/isni/0000000110927289 + + + 1812-0792 + + Increasing turbidity in the North Sea during the 20th century due to changing wave climate + + 2019-10-02 + + + Wilson, Robert J. + https://orcid.org/0000-0002-0592-366X + + + + Heath, Michael R. + https://orcid.org/0000-0001-6602-3107 + https://viaf.org/viaf/15147423189944882613 + + + 2019-12-09 + + 2019-10-15 + + https://purl.org/coar/resource_type/c_2df8fbb1 + + + DP190101507 + + + + 61387 + + + + https://strathprints.strath.ac.uk/70117/7/Wilson_Heath_OS2019_Increasing_turbidity_in_the_North_Sea_during_the_20th_century.pdf + + + + + https://doi.org/10.1007/s11229-020-02724-x + + + + + https://doi.org/10.15129/5d28213e-8f9f-402a-b550-fc588518cb8b + + + + + https://doi.org/10.5281/zenodo.3478185 + + diff --git a/dspace-oai/src/test/resources/rioxx-test-valid.xml b/dspace-oai/src/test/resources/rioxx-test-valid.xml new file mode 100644 index 0000000000..74ffd43eb6 --- /dev/null +++ b/dspace-oai/src/test/resources/rioxx-test-valid.xml @@ -0,0 +1,92 @@ + + + + Data on Secchi disc depth (the depth at which a standard white disc lowered into the water just becomes invisible to a surface observer) show that water clarity in the North Sea declined during the 20th century, with likely consequences for marine primary production. However, the causes of this trend remain unknown. Here we analyse the hypothesis that changes in the North Sea's wave climate were largely responsible by causing an increase in the concentrations of suspended particulate matter (SPM) in the water column through the resuspension of seabed sediments. First, we analysed the broad-scale statistical relationships between SPM and bed shear stress due to waves and tides. We used hindcasts of wave and current data to construct a space–time dataset of bed shear stress between 1997 and 2017 across the northwest European Continental Shelf and compared the results with satellite-derived SPM concentrations. Bed shear stress was found to drive most of the inter-annual variation in SPM in the hydrographically mixed waters of the central and southern North Sea. We then used a long-term wave reanalysis to construct a time series of bed shear stress from 1900 to 2010. This shows that bed shear stress increased significantly across much of the shelf during this period, with increases of over 20 % in the southeastern North Sea. An increase in bed shear stress of this magnitude would have resulted in a large reduction in water clarity. Wave-driven processes are rarely included in projections of climate change impacts on marine ecosystems, but our analysis indicates that this should be reconsidered for shelf sea regions. + + en + + + European Geosciences Union + https://isni.org/isni/0000000110927289 + + + 1812-0792 + + Increasing turbidity in the North Sea during the 20th century due to changing wave climate + + 2019-10-02 + + + Wilson, Robert J. + https://orcid.org/0000-0002-0592-366X + + + + Heath, Michael R. + https://orcid.org/0000-0001-6602-3107 + https://viaf.org/viaf/15147423189944882613 + + + 2019-12-09 + + 2019-10-15 + + https://purl.org/coar/resource_type/c_2df8fbb1 + + + DP190101507 + + + + 61387 + + + https://strathprints.strath.ac.uk/70117/ + + + https://strathprints.strath.ac.uk/70117/7/Wilson_Heath_OS2019_Increasing_turbidity_in_the_North_Sea_during_the_20th_century.pdf + + + + + https://doi.org/10.1007/s11229-020-02724-x + + + + + https://doi.org/10.15129/5d28213e-8f9f-402a-b550-fc588518cb8b + + + + + https://doi.org/10.5281/zenodo.3478185 + + diff --git a/dspace-oai/src/test/resources/xoai-rioxx-test.xml b/dspace-oai/src/test/resources/xoai-rioxx-test.xml new file mode 100644 index 0000000000..33c2c3d101 --- /dev/null +++ b/dspace-oai/src/test/resources/xoai-rioxx-test.xml @@ -0,0 +1,217 @@ + + + + + + + + Publication + + + + + + + + 2023-11-07 + + + + + + + + Tsigaridis, Konstantinos G. + virtual::44 + -1 + Wang, Rui + virtual::46 + -1 + Ellefson, Michelle R. + virtual::47 + -1 + + + + + + + 2023-11-07T11:34:10Z + + + + + 2023-11-07T11:34:10Z + + + + + 2022-11-30 + + + + + + + https://example.org/handle/1811/160 + + + + + + + eng + + + + + + The Intercorrelation Between Executive Function, Physics Problem Solving, Mathematical, and Matrix Reasoning Skills: Reflections from a Small-Scale Experiment + + + + + Article + + + + + + + a57363fa-f82e-4684-bd76-f7bc1e893603 + virtual::44 + -1 + e00b3d0d-65e2-4c30-825d-1a4839845790 + virtual::46 + -1 + bdd38a03-206d-4f9b-bafb-70e060ad176f + virtual::47 + -1 + + + + a57363fa-f82e-4684-bd76-f7bc1e893603 + virtual::44 + -1 + e00b3d0d-65e2-4c30-825d-1a4839845790 + virtual::46 + -1 + bdd38a03-206d-4f9b-bafb-70e060ad176f + virtual::47 + -1 + + + + + + 05a400b1-ff0b-4e40-80cd-a7d1b712ace2 + virtual::71 + -1 + + + + + 7524a0cf-3ea2-40c7-a265-d583425ed4d7 + virtual::71 + -1 + + + + 7524a0cf-3ea2-40c7-a265-d583425ed4d7 + virtual::71 + -1 + + + + + + + + + 0000-0003-0407-9767 + virtual::47 + -1 + + + + + + + + 2634-9876 + virtual::71 + -1 + + + + + + ORIGINAL + + + Tsigaridis et al., 2022.pdf + application/pdf + 1554917 + https://example.org/bitstreams/9121e795-0af3-4ff3-be2a-4b28418fb269/download + 42d8cd076931e43e02d0af70a36d704e + MD5 + 1 + true + + + Anonymous + Anonymous + READ + + + + + + + THUMBNAIL + + + cerj_volume_9_thumbnail.jpg + image/jpeg + 14513 + https://example.org/bitstreams/16245937-10bb-46db-9817-683a5ebd8d63/download + 8c39d691daa8e5f9d668668db7910cd6 + MD5 + 2 + false + + + Anonymous + Anonymous + READ + + + + + + + + 1811/160 + oai:example.org:1811/160 + 2023-12-13 13:07:56.51 + + open.access + + + + https://example.org + Diamond DSpace (dev) + support@example.org + + + diff --git a/dspace-rdf/pom.xml b/dspace-rdf/pom.xml index f118ebddb3..45ab69b483 100644 --- a/dspace-rdf/pom.xml +++ b/dspace-rdf/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - 7.6.2-SNAPSHOT + 9.0-SNAPSHOT .. @@ -71,8 +71,8 @@ - javax.servlet - javax.servlet-api + jakarta.servlet + jakarta.servlet-api provided diff --git a/dspace-rdf/src/main/java/org/dspace/rdf/providing/DataProviderServlet.java b/dspace-rdf/src/main/java/org/dspace/rdf/providing/DataProviderServlet.java index 007f865fb7..7344b2c74e 100644 --- a/dspace-rdf/src/main/java/org/dspace/rdf/providing/DataProviderServlet.java +++ b/dspace-rdf/src/main/java/org/dspace/rdf/providing/DataProviderServlet.java @@ -11,13 +11,13 @@ package org.dspace.rdf.providing; import java.io.IOException; import java.io.PrintWriter; import java.sql.SQLException; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import com.hp.hpl.jena.rdf.model.Model; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.apache.commons.lang3.StringUtils; +import org.apache.jena.rdf.model.Model; import org.apache.logging.log4j.Logger; import org.dspace.content.DSpaceObject; import org.dspace.core.Context; @@ -159,7 +159,7 @@ public class DataProviderServlet extends HttpServlet { } protected String detectContentType(HttpServletRequest request, String lang) { - // It is usefull to be able to overwrite the content type, to see the + // It is useful to be able to overwrite the content type, to see the // request result directly in the browser. If a parameter "text" is part // of the request, we send the result with the content type "text/plain". if (request.getParameter("text") != null) { diff --git a/dspace-rdf/src/main/java/org/dspace/rdf/providing/LocalURIRedirectionServlet.java b/dspace-rdf/src/main/java/org/dspace/rdf/providing/LocalURIRedirectionServlet.java index 7224bb9bfb..d985740f52 100644 --- a/dspace-rdf/src/main/java/org/dspace/rdf/providing/LocalURIRedirectionServlet.java +++ b/dspace-rdf/src/main/java/org/dspace/rdf/providing/LocalURIRedirectionServlet.java @@ -9,11 +9,11 @@ package org.dspace.rdf.providing; import java.io.IOException; import java.sql.SQLException; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.dspace.content.DSpaceObject; diff --git a/dspace-rest/README.md b/dspace-rest/README.md deleted file mode 100644 index 07d71d66ed..0000000000 --- a/dspace-rest/README.md +++ /dev/null @@ -1,194 +0,0 @@ -#DSpace REST API (Jersey) - DEPRECATED - -A RESTful web services API for DSpace, built using JAX-RS1 JERSEY. - -_This REST API has been deprecated and will be removed in v8. Please use the Server API (/server) webapp instead._ - -##Getting Started -This REST API is integrated directly into the DSpace codebase. - - * Rebuild as usual: mvn + ant - * Deploy the webapp (i.e to Tomcat) - * `````` - - -REST API can do all CRUD (create, read, update, delete) operations over communities, collections, items, bitstream and bitstream policies. Without logging into the REST API, you have read access as an anonymous user (member of the Anonymous group). If you want to make changes in DSpace using the REST API, you must log into the API using the "login" endpoint and then use the returned token in request header of your subsequent API calls. - -##Endpoints - -| Resource |CREATE|READ list|READ single|Edit|Delete|Search| -| ------------- |------|:-------:|-----------|----|------|------| -| /communities | Y | Y | Y | Y | Y | | -| /collections | Y | Y | Y | Y | Y | Y | -| /items | Y | Y | Y | Y | Y | Y | -| /bitstreams | Y | Y | Y | Y | Y | || - -Search in collections is possible only by name and search in items only by metadata field. - -###Index -Get information on how to use the API -- GET http://localhost:8080 - -Test whether the REST API is running and available -- GET http://localhost:8080/rest/test - -Log into REST API -- POST http://localhost:8080/rest/login - -Logout from REST API -- POST http://localhost:8080/rest/logout - -Get status of REST API and the logged-in user -- GET http://localhost:8080/rest/status - - -###Communities -View the list of top-level communities -- GET http://localhost:8080/rest/communities/top-communities - -View the list of all communities -- GET http://localhost:8080/rest/communities[?expand={collections,parentCommunity,subCommunities,logo,all}] - -View a specific community -- GET http://localhost:8080/rest/communities/:ID[?expand={collections,parentCommunity,subCommunities,logo,all}] - -View the list of subcollections in community -- GET http://localhost:8080/rest/communities/:ID/collections[?expand={items,parentCommunityList,license,logo,all}] - -View the list of subcommunities in community -- GET http://localhost:8080/rest/communities/:ID/communities[?expand={collections,parentCommunity,subCommunities,logo,all}] - -Create new top-level community -- POST http://localhost:8080/rest/communities - -Create new subcollection in community -- POST http://localhost:8080/rest/communities/:ID/collections - -Create new subcommunity in community -- POST http://localhost:8080/rest/communities/:ID/communities - -Update community -- PUT http://localhost:8080/rest/communities/:ID - -Delete community -- DELETE http://localhost:8080/rest/communities/:ID - -Delete subcollection in community -- DELETE http://localhost:8080/rest/communities/:ID/collections/:ID - -Delete subcommunity in community -- DELETE http://localhost:8080/rest/communities/:ID/communities/:ID - - -###Collections -View the list of collections -- GET http://localhost:8080/rest/collections[?expand={items,parentCommunityList,license,logo,all}] - -View a specific collection -- GET http://localhost:8080/rest/collections/:ID[?expand={items,parentCommunityList,license,logo,all}] - -View items in collection -- GET http://localhost:8080/rest/collections/:ID/items[?expand={metadata,parentCollection,parentcollectionList,parentCommunityList,bitstreams,all}] - -Create item in collection -- POST http://localhost:8080/rest/collections/:ID/items - -Find collection by name -- POST http://localhost:8080/rest/collections/find-collection - -Update collection -- PUT http://localhost:8080/rest/collections/:ID - -Delete collection -- DELETE http://localhost:8080/rest/collections/:ID - -Delete item in collection -- DELETE http://localhost:8080/rest/collections/:ID/items/:ID - - -###Items -View the list of items -- GET http://localhost:8080/rest/items[?expand={metadata,parentCollection,parentcollectionList,parentCommunityList,bitstreams,all}] - -View speciific item -- GET http://localhost:8080/rest/items/:ID[?expand={metadata,parentCollection,parentcollectionList,parentCommunityList,bitstreams,all}] - -View an Item and view its bitstreams -- GET http://localhost:8080/rest/items/:ID/bitstreams[?expand={parent,policies,all}] - -View an Item, and view its metadata -- GET http://localhost:8080/rest/items/:ID/metadata - -Find item by metadata -- POST http://localhost:8080/rest/items/find-by-metadata-field - -Add metadata to item -- POST http://localhost:8080/rest/items/:ID/metadata - -Create bitstream in item -- POST http://localhost:8080/rest/items/:ID/bitstreams - -Update metadata in item -- PUT http://localhost:8080/rest/items/:ID/metadata - -Delete item -- DELETE http://localhost:8080/rest/items/:ID - -Delete all metadata in item -- DELETE http://localhost:8080/rest/items/:ID/metadata - -Delete bitstream in item -- DELETE http://localhost:8080/rest/items/:ID/bitstreams/:ID - - -###Bitstreams -View the list of bitstreams -- GET http://localhost:8080/rest/bitstreams[?expand={parent,policies,all}] - -View information about a bitstream -- GET http://localhost:8080/rest/bitstreams/:ID[?expand={parent,policies,all}] - -View/Download a specific Bitstream -- GET http://localhost:8080/rest/bitstreams/:ID/retrieve - -View the list of policies of bitstream -- GET http://localhost:8080/rest/bitstreams/:ID/policy - -Add policy to bitstream -- POST http://localhost:8080/rest/bitstreams/:ID/policy - -Update bitstream -- PUT http://localhost:8080/rest/bitstreams/:ID - -Update data of bitstream -- PUT http://localhost:8080/rest/bitstreams/:ID/data - -Delete bitstream -- DELETE http://localhost:8080/rest/bitstreams/:ID - -Delete policy of bitstream -- DELETE http://localhost:8080/rest/bitstreams/:ID/policy/:ID - - -####Statistics -Recording view events of items and download events of bitstreams (set stats = true in rest.cfg to enable recording of events) -http://localhost:8080/rest/items/:ID?userIP=ip&userAgent=userAgent&xforwardedfor=xforwardedfor -If no parameters are given, the details of the HTTP request sender are used in statistics. -This enables tools like proxies to supply the details of their user rather than themselves. - - -###Handles -Lookup a DSpaceObject by its Handle, this produces the name/ID that you look up in /bitstreams, /items, /collections, /communities -- http://localhost:8080/rest/handle/{prefix}/{suffix} - -##Expand -There is an ?expand= query parameter for more expensive operations. You can add it at the end of the request URL. -It is optional, all, some or none. The response will usually indicate what the available "expand" options are. - -##HTTP Responses -* 200 OK - The requested object/objects exists -* 401 Unauthorized - The anonymous user does not have READ access to that object -* 404 Not Found - The specified object doesn't exist -* 405 Method Not Allowed - Wrong request method (GET,POST,PUT,DELETE) or wrong data format (JSON/XML). -* 415 Unsupported Media Type - Missing "Content-Type: application/json" or "Content-Type: application/xml" request header -* 500 Server Error - Likely a SQLException, IOException, more details in the logs. diff --git a/dspace-rest/pom.xml b/dspace-rest/pom.xml deleted file mode 100644 index 5688424a1c..0000000000 --- a/dspace-rest/pom.xml +++ /dev/null @@ -1,202 +0,0 @@ - - 4.0.0 - org.dspace - dspace-rest - war - 7.6.2-SNAPSHOT - DSpace (Deprecated) REST Webapp - DSpace RESTful Web Services API. NOTE: this REST API is DEPRECATED. - Please consider using the REST API in the dspace-server-webapp instead! - http://demo.dspace.org - - - org.dspace - dspace-parent - 7.6.2-SNAPSHOT - .. - - - - - ${basedir}/.. - - - - - org.apache.maven.plugins - maven-war-plugin - - true - - true - - - - com.mycila - license-maven-plugin - - - - **/static/reports/spin.js - **/static/reports/README.md - **/*.xsd - - - - - - - - - - org.glassfish.jersey.core - jersey-server - ${jersey.version} - - - org.glassfish.jersey.containers - jersey-container-servlet - ${jersey.version} - - - org.glassfish.jersey.media - jersey-media-json-jackson - ${jersey.version} - - - org.glassfish.jersey.media - jersey-media-jaxb - ${jersey.version} - - - - - org.springframework - spring-core - - - - org.springframework - spring-context - - - - org.springframework - spring-web - - - - - org.glassfish.jersey.ext - jersey-spring5 - ${jersey.version} - - - - org.springframework - spring - - - org.springframework - spring-core - - - org.springframework - spring-web - - - org.springframework - spring-beans - - - org.springframework - spring-context - - - org.springframework - spring-aop - - - - jakarta.annotation - jakarta.annotation-api - - - - - org.springframework.security - spring-security-core - ${spring-security.version} - - - - org.springframework - spring-expression - - - - - org.springframework.security - spring-security-web - ${spring-security.version} - - - - org.springframework - spring-expression - - - - - org.springframework.security - spring-security-config - ${spring-security.version} - - - - org.dspace - dspace-api - - - - - org.apache.commons - commons-dbcp2 - - - org.postgresql - postgresql - - - javax.servlet - javax.servlet-api - provided - - - org.atteo - evo-inflector - 1.2.1 - - - org.apache.logging.log4j - log4j-api - - - org.apache.logging.log4j - log4j-core - - - org.apache.logging.log4j - log4j-web - - - org.dspace - dspace-services - - - junit - junit - test - - - diff --git a/dspace-rest/src/main/java/org/dspace/rest/BitstreamResource.java b/dspace-rest/src/main/java/org/dspace/rest/BitstreamResource.java deleted file mode 100644 index b71bfad592..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/BitstreamResource.java +++ /dev/null @@ -1,784 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.rest; - -import java.io.IOException; -import java.io.InputStream; -import java.net.URLConnection; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; -import javax.servlet.http.HttpServletRequest; -import javax.ws.rs.Consumes; -import javax.ws.rs.DELETE; -import javax.ws.rs.DefaultValue; -import javax.ws.rs.GET; -import javax.ws.rs.POST; -import javax.ws.rs.PUT; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.Response.Status; - -import org.apache.logging.log4j.Logger; -import org.dspace.authorize.AuthorizeException; -import org.dspace.authorize.factory.AuthorizeServiceFactory; -import org.dspace.authorize.service.AuthorizeService; -import org.dspace.authorize.service.ResourcePolicyService; -import org.dspace.content.BitstreamFormat; -import org.dspace.content.factory.ContentServiceFactory; -import org.dspace.content.service.BitstreamFormatService; -import org.dspace.content.service.BitstreamService; -import org.dspace.content.service.BundleService; -import org.dspace.eperson.factory.EPersonServiceFactory; -import org.dspace.eperson.service.GroupService; -import org.dspace.rest.common.Bitstream; -import org.dspace.rest.common.ResourcePolicy; -import org.dspace.rest.exceptions.ContextException; -import org.dspace.storage.bitstore.factory.StorageServiceFactory; -import org.dspace.storage.bitstore.service.BitstreamStorageService; -import org.dspace.usage.UsageEvent; - -/** - * @author Rostislav Novak (Computing and Information Centre, CTU in Prague) - */ -// Every DSpace class used without namespace is from package -// org.dspace.rest.common.*. Otherwise namespace is defined. -@Path("/bitstreams") -public class BitstreamResource extends Resource { - protected BitstreamService bitstreamService = ContentServiceFactory.getInstance().getBitstreamService(); - protected BundleService bundleService = ContentServiceFactory.getInstance().getBundleService(); - protected AuthorizeService authorizeService = AuthorizeServiceFactory.getInstance().getAuthorizeService(); - protected BitstreamFormatService bitstreamFormatService = ContentServiceFactory.getInstance() - .getBitstreamFormatService(); - protected BitstreamStorageService bitstreamStorageService = StorageServiceFactory.getInstance() - .getBitstreamStorageService(); - protected ResourcePolicyService resourcePolicyService = AuthorizeServiceFactory.getInstance() - .getResourcePolicyService(); - protected GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); - - private static Logger log = org.apache.logging.log4j.LogManager.getLogger(BitstreamResource.class); - - /** - * Return bitstream properties without file data. It can throw - * WebApplicationException with three response codes. Response code - * NOT_FOUND(404) or UNAUTHORIZED(401) or INTERNAL_SERVER_ERROR(500). Bad - * request is when the bitstream id does not exist. UNAUTHORIZED if the user - * logged into the DSpace context does not have the permission to access the - * bitstream. Server error when something went wrong. - * - * @param bitstreamId Id of bitstream in DSpace. - * @param expand This string defines which additional optional fields will be added - * to bitstream response. Individual options are separated by commas without - * spaces. The options are: "all", "parent". - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the item as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @return If user is allowed to read bitstream, it returns instance of - * bitstream. Otherwise, it throws WebApplicationException with - * response code UNAUTHORIZED. - * @throws WebApplicationException It can happen on: Bad request, unauthorized, SQL exception - * and context exception(could not create context). - */ - @GET - @Path("/{bitstream_id}") - @Produces( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public Bitstream getBitstream(@PathParam("bitstream_id") String bitstreamId, @QueryParam("expand") String expand, - @QueryParam("userIP") String user_ip, @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, @Context HttpHeaders headers, - @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Reading bitstream(id=" + bitstreamId + ") metadata."); - org.dspace.core.Context context = null; - Bitstream bitstream = null; - - try { - context = createContext(); - org.dspace.content.Bitstream dspaceBitstream = findBitstream(context, bitstreamId, - org.dspace.core.Constants.READ); - - writeStats(dspaceBitstream, UsageEvent.Action.VIEW, user_ip, user_agent, xforwardedfor, headers, - request, context); - - bitstream = new Bitstream(dspaceBitstream, servletContext, expand, context); - context.complete(); - log.trace("Bitstream(id=" + bitstreamId + ") was successfully read."); - - } catch (SQLException e) { - processException( - "Someting went wrong while reading bitstream(id=" + bitstreamId + ") from database! Message: " + e, - context); - } catch (ContextException e) { - processException( - "Someting went wrong while reading bitstream(id=" + bitstreamId + "), ContextException. Message: " - + e.getMessage(), context); - } finally { - processFinally(context); - } - - return bitstream; - } - - /** - * Return all bitstream resource policies from all bundles, in which - * the bitstream is present. - * - * @param bitstreamId Id of bitstream in DSpace. - * @param headers If you want to access the item as the user logged into the context. - * The header "rest-dspace-token" with the token passed - * from the login method must be set. - * @return Returns an array of ResourcePolicy objects. - */ - @GET - @Path("/{bitstream_id}/policy") - @Produces( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public ResourcePolicy[] getBitstreamPolicies(@PathParam("bitstream_id") String bitstreamId, - @Context HttpHeaders headers) { - - log.info("Reading bitstream(id=" + bitstreamId + ") policies."); - org.dspace.core.Context context = null; - ResourcePolicy[] policies = null; - - try { - context = createContext(); - org.dspace.content.Bitstream dspaceBitstream = findBitstream(context, bitstreamId, - org.dspace.core.Constants.READ); - policies = new Bitstream(dspaceBitstream, servletContext, "policies", context).getPolicies(); - - context.complete(); - log.trace("Policies for bitstream(id=" + bitstreamId + ") was successfully read."); - - } catch (SQLException e) { - processException("Someting went wrong while reading policies of bitstream(id=" + bitstreamId - + "), SQLException! Message: " + e, context); - } catch (ContextException e) { - processException("Someting went wrong while reading policies of bitstream(id=" + bitstreamId - + "), ContextException. Message: " + e.getMessage(), context); - } finally { - processFinally(context); - } - - return policies; - } - - /** - * Read list of bitstreams. It throws WebApplicationException with response - * code INTERNAL_SERVER_ERROR(500), if there was problem while reading - * bitstreams from database. - * - * @param expand This string defines which additional optional fields will be added - * to bitstream response. Individual options are separated by commas without - * spaces. The options are: "all", "parent". - * @param limit How many bitstreams will be in the list. Default value is 100. - * @param offset On which offset (item) the list starts. Default value is 0. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the item as the user logged into the context. - * The header "rest-dspace-token" with the token passed - * from the login method must be set. - * @param request Servlet's HTTP request object. - * @return Returns an array of bistreams. Array doesn't contain bitstreams for - * which the user doesn't have read permission. - * @throws WebApplicationException Thrown in case of a problem with reading the database or with - * creating a context. - */ - @GET - @Produces( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public Bitstream[] getBitstreams(@QueryParam("expand") String expand, - @QueryParam("limit") @DefaultValue("100") Integer limit, - @QueryParam("offset") @DefaultValue("0") Integer offset, - @QueryParam("userIP") String user_ip, @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, @Context HttpHeaders headers, - @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Reading bitstreams.(offset=" + offset + ",limit=" + limit + ")"); - org.dspace.core.Context context = null; - List bitstreams = new ArrayList(); - - try { - context = createContext(); - List dspaceBitstreams = bitstreamService.findAll(context); - - if (!((limit != null) && (limit >= 0) && (offset != null) && (offset >= 0))) { - log.warn("Paging was badly set."); - limit = 100; - offset = 0; - } - - // TODO If bitstream doesn't exist, throws exception. - for (int i = offset; (i < (offset + limit)) && (i < dspaceBitstreams.size()); i++) { - if (authorizeService - .authorizeActionBoolean(context, dspaceBitstreams.get(i), org.dspace.core.Constants.READ)) { - if (bitstreamService.getParentObject(context, dspaceBitstreams - .get(i)) != null) { // To eliminate bitstreams which cause exception, because of - // reading under administrator permissions - bitstreams.add(new Bitstream(dspaceBitstreams.get(i), servletContext, expand, context)); - writeStats(dspaceBitstreams.get(i), UsageEvent.Action.VIEW, user_ip, user_agent, - xforwardedfor, headers, request, context); - } - } - } - - context.complete(); - log.trace("Bitstreams were successfully read."); - - } catch (SQLException e) { - processException("Something went wrong while reading bitstreams from database!. Message: " + e, context); - } catch (ContextException e) { - processException( - "Something went wrong while reading bitstreams, ContextException. Message: " + e.getMessage(), - context); - } finally { - processFinally(context); - } - - return bitstreams.toArray(new Bitstream[0]); - } - - /** - * Read bitstream data. May throw WebApplicationException with the - * INTERNAL_SERVER_ERROR(500) code. Caused by three exceptions: IOException if - * there was a problem with reading bitstream file. SQLException if there was - * a problem while reading from database. And AuthorizeException if there was - * a problem with authorization of user logged to DSpace context. - * - * @param bitstreamId Id of the bitstream, whose data will be read. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the item as the user logged into the context. - * The header "rest-dspace-token" with the token passed - * from the login method must be set. - * @param request Servlet's HTTP request object. - * @return Returns response with data with file content type. It can - * return the NOT_FOUND(404) response code in case of wrong bitstream - * id. Or response code UNAUTHORIZED(401) if user is not - * allowed to read bitstream. - * @throws WebApplicationException Thrown if there was a problem: reading the file data; or reading - * the database; or creating the context; or with authorization. - */ - @GET - @Path("/{bitstream_id}/retrieve") - public javax.ws.rs.core.Response getBitstreamData(@PathParam("bitstream_id") String bitstreamId, - @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, - @Context HttpHeaders headers, @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Reading data of bitstream(id=" + bitstreamId + ")."); - org.dspace.core.Context context = null; - InputStream inputStream = null; - String type = null; - String name = null; - - try { - context = createContext(); - org.dspace.content.Bitstream dspaceBitstream = findBitstream(context, bitstreamId, - org.dspace.core.Constants.READ); - - writeStats(dspaceBitstream, UsageEvent.Action.VIEW, user_ip, user_agent, xforwardedfor, headers, - request, context); - - log.trace("Bitstream(id=" + bitstreamId + ") data was successfully read."); - inputStream = bitstreamService.retrieve(context, dspaceBitstream); - type = dspaceBitstream.getFormat(context).getMIMEType(); - name = dspaceBitstream.getName(); - - context.complete(); - } catch (IOException e) { - processException("Could not read file of bitstream(id=" + bitstreamId + ")! Message: " + e, context); - } catch (SQLException e) { - processException( - "Something went wrong while reading bitstream(id=" + bitstreamId + ") from database! Message: " + e, - context); - } catch (AuthorizeException e) { - processException( - "Could not retrieve file of bitstream(id=" + bitstreamId + "), AuthorizeException! Message: " + e, - context); - } catch (ContextException e) { - processException( - "Could not retrieve file of bitstream(id=" + bitstreamId + "), ContextException! Message: " + e - .getMessage(), - context); - } finally { - processFinally(context); - } - - return Response.ok(inputStream).type(type) - .header("Content-Disposition", "attachment; filename=\"" + name + "\"") - .build(); - } - - /** - * Add bitstream policy to all bundles containing the bitstream. - * - * @param bitstreamId Id of bitstream in DSpace. - * @param policy Policy to be added. The following attributes are not - * applied: epersonId, - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the item as the user logged into the context. - * The header "rest-dspace-token" with the token passed - * from the login method must be set. - * @param request Servlet's HTTP request object. - * @return Returns ok, if all was ok. Otherwise status code 500. - */ - @POST - @Path("/{bitstream_id}/policy") - @Consumes( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public javax.ws.rs.core.Response addBitstreamPolicy(@PathParam("bitstream_id") String bitstreamId, - ResourcePolicy policy, - @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, - @Context HttpHeaders headers, - @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Adding bitstream(id=" + bitstreamId + ") " + policy - .getAction() + " policy with permission for group(id=" + policy.getGroupId() - + ")."); - org.dspace.core.Context context = null; - - try { - context = createContext(); - org.dspace.content.Bitstream dspaceBitstream = findBitstream(context, bitstreamId, - org.dspace.core.Constants.WRITE); - - writeStats(dspaceBitstream, UsageEvent.Action.UPDATE, user_ip, user_agent, xforwardedfor, headers, - request, context); - - addPolicyToBitstream(context, policy, dspaceBitstream); - - context.complete(); - log.trace("Policy for bitstream(id=" + bitstreamId + ") was successfully added."); - - } catch (SQLException e) { - processException("Someting went wrong while adding policy to bitstream(id=" + bitstreamId - + "), SQLException! Message: " + e, context); - } catch (ContextException e) { - processException("Someting went wrong while adding policy to bitstream(id=" + bitstreamId - + "), ContextException. Message: " + e.getMessage(), context); - } catch (AuthorizeException e) { - processException("Someting went wrong while adding policy to bitstream(id=" + bitstreamId - + "), AuthorizeException! Message: " + e, context); - } finally { - processFinally(context); - } - return Response.status(Status.OK).build(); - } - - /** - * Update bitstream metadata. Replaces everything on targeted bitstream. - * May throw WebApplicationException caused by two exceptions: - * SQLException, if there was a problem with the database. AuthorizeException if - * there was a problem with the authorization to edit bitstream metadata. - * - * @param bitstreamId Id of bistream to be updated. - * @param bitstream Bitstream with will be placed. It must have filled user - * credentials. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the item as the user logged into the context. - * The header "rest-dspace-token" with the token passed - * from the login method must be set. - * @param request Servlet's HTTP request object. - * @return Return response codes: OK(200), NOT_FOUND(404) if bitstream does - * not exist and UNAUTHORIZED(401) if user is not allowed to write - * to bitstream. - * @throws WebApplicationException Thrown when: Error reading from database; or error - * creating context; or error regarding bitstream authorization. - */ - @PUT - @Path("/{bitstream_id}") - @Consumes( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public Response updateBitstream(@PathParam("bitstream_id") String bitstreamId, Bitstream bitstream, - @QueryParam("userIP") String user_ip, @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, @Context HttpHeaders headers, - @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Updating bitstream(id=" + bitstreamId + ") metadata."); - org.dspace.core.Context context = null; - - try { - context = createContext(); - org.dspace.content.Bitstream dspaceBitstream = findBitstream(context, bitstreamId, - org.dspace.core.Constants.WRITE); - - writeStats(dspaceBitstream, UsageEvent.Action.UPDATE, user_ip, user_agent, xforwardedfor, - headers, request, context); - - log.trace("Updating bitstream metadata."); - - dspaceBitstream.setDescription(context, bitstream.getDescription()); - if (getMimeType(bitstream.getName()) == null) { - BitstreamFormat unknownFormat = bitstreamFormatService.findUnknown(context); - bitstreamService.setFormat(context, dspaceBitstream, unknownFormat); - } else { - BitstreamFormat guessedFormat = bitstreamFormatService - .findByMIMEType(context, getMimeType(bitstream.getName())); - bitstreamService.setFormat(context, dspaceBitstream, guessedFormat); - } - dspaceBitstream.setName(context, bitstream.getName()); - Integer sequenceId = bitstream.getSequenceId(); - if (sequenceId != null && sequenceId.intValue() != -1) { - dspaceBitstream.setSequenceID(sequenceId); - } - - bitstreamService.update(context, dspaceBitstream); - - if (bitstream.getPolicies() != null) { - log.trace("Updating bitstream policies."); - - // Remove all old bitstream policies. - authorizeService.removeAllPolicies(context, dspaceBitstream); - - // Add all new bitstream policies - for (ResourcePolicy policy : bitstream.getPolicies()) { - addPolicyToBitstream(context, policy, dspaceBitstream); - } - } - - context.complete(); - - } catch (SQLException e) { - processException("Could not update bitstream(id=" + bitstreamId + ") metadata, SQLException. Message: " + e, - context); - } catch (AuthorizeException e) { - processException( - "Could not update bitstream(id=" + bitstreamId + ") metadata, AuthorizeException. Message: " + e, - context); - } catch (ContextException e) { - processException( - "Could not update bitstream(id=" + bitstreamId + ") metadata, ContextException. Message: " + e - .getMessage(), - context); - } finally { - processFinally(context); - } - - log.info("Bitstream metadata(id=" + bitstreamId + ") were successfully updated."); - return Response.ok().build(); - } - - /** - * Update bitstream data. Changes bitstream data by editing database rows. - * May throw WebApplicationException caused by: SQLException if there was - * a problem editing or reading the database, IOException if there was - * a problem with reading from InputStream, Exception if there was another - * problem. - * - * @param bitstreamId Id of bistream to be updated. - * @param is InputStream filled with new data. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the item as the user logged into the context. - * The header "rest-dspace-token" with the token passed - * from the login method must be set. - * @param request Servlet's HTTP request object. - * @return Return response if bitstream was updated. Response codes: - * OK(200), NOT_FOUND(404) if id of bitstream was bad. And - * UNAUTHORIZED(401) if user is not allowed to update bitstream. - * @throws WebApplicationException This exception can be thrown in this cases: Problem with - * reading or writing to database. Or problem with reading from - * InputStream. - */ - // TODO Change to better logic, without editing database. - @PUT - @Path("/{bitstream_id}/data") - public Response updateBitstreamData(@PathParam("bitstream_id") String bitstreamId, InputStream is, - @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, @Context HttpHeaders headers, - @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Updating bitstream(id=" + bitstreamId + ") data."); - org.dspace.core.Context context = null; - - try { - context = createContext(); - org.dspace.content.Bitstream dspaceBitstream = findBitstream(context, bitstreamId, - org.dspace.core.Constants.WRITE); - - writeStats(dspaceBitstream, UsageEvent.Action.UPDATE, user_ip, user_agent, xforwardedfor, - headers, request, context); - - log.trace("Creating new bitstream."); - - UUID newBitstreamId = bitstreamStorageService.store(context, dspaceBitstream, is); - log.trace("Bitstream data stored: " + newBitstreamId); - context.complete(); - } catch (SQLException e) { - processException("Could not update bitstream(id=" + bitstreamId + ") data, SQLException. Message: " + e, - context); - } catch (IOException e) { - processException("Could not update bitstream(id=" + bitstreamId + ") data, IOException. Message: " + e, - context); - } catch (ContextException e) { - processException( - "Could not update bitstream(id=" + bitstreamId + ") data, ContextException. Message: " + e.getMessage(), - context); - } finally { - processFinally(context); - } - - log.info("Bitstream(id=" + bitstreamId + ") data was successfully updated."); - return Response.ok().build(); - } - - /** - * Delete bitstream from all bundles in DSpace. May throw - * WebApplicationException, which can be caused by three exceptions. - * SQLException if there was a problem reading from database or removing - * from database. AuthorizeException, if user doesn't have permission to delete - * the bitstream or file. IOException, if there was a problem deleting the file. - * - * @param bitstreamId Id of bitstream to be deleted. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the item as the user logged into the context. - * The header "rest-dspace-token" with the token passed - * from the login method must be set. - * @param request Servlet's HTTP request object. - * @return Return response codes: OK(200), NOT_FOUND(404) if bitstream of - * that id does not exist and UNAUTHORIZED(401) if user is not - * allowed to delete bitstream. - * @throws WebApplicationException Can be thrown if there was a problem reading or editing - * the database. Or problem deleting the file. Or problem with - * authorization to bitstream and bundles. Or problem with - * creating context. - */ - @DELETE - @Path("/{bitstream_id}") - public Response deleteBitstream(@PathParam("bitstream_id") String bitstreamId, @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, - @Context HttpHeaders headers, @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Deleting bitstream(id=" + bitstreamId + ")."); - org.dspace.core.Context context = null; - - try { - context = createContext(); - org.dspace.content.Bitstream dspaceBitstream = findBitstream(context, bitstreamId, - org.dspace.core.Constants.DELETE); - - writeStats(dspaceBitstream, UsageEvent.Action.DELETE, user_ip, user_agent, xforwardedfor, - headers, request, context); - - log.trace("Deleting bitstream from all bundles."); - bitstreamService.delete(context, dspaceBitstream); - - context.complete(); - } catch (SQLException e) { - processException("Could not delete bitstream(id=" + bitstreamId + "), SQLException. Message: " + e, - context); - } catch (AuthorizeException e) { - processException("Could not delete bitstream(id=" + bitstreamId + "), AuthorizeException. Message: " + e, - context); - } catch (IOException e) { - processException("Could not delete bitstream(id=" + bitstreamId + "), IOException. Message: " + e, context); - } catch (ContextException e) { - processException( - "Could not delete bitstream(id=" + bitstreamId + "), ContextException. Message:" + e.getMessage(), - context); - } finally { - processFinally(context); - } - - log.info("Bitstream(id=" + bitstreamId + ") was successfully deleted."); - return Response.ok().build(); - } - - /** - * Delete policy. - * - * @param bitstreamId Id of the DSpace bitstream whose policy will be deleted. - * @param policyId Id of the policy to delete. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the item as the user logged into the context. - * The header "rest-dspace-token" with the token passed - * from the login method must be set. - * @param request Servlet's HTTP request object. - * @return It returns Ok, if all was ok. Otherwise status code 500. - */ - @DELETE - @Path("/{bitstream_id}/policy/{policy_id}") - public javax.ws.rs.core.Response deleteBitstreamPolicy(@PathParam("bitstream_id") String bitstreamId, - @PathParam("policy_id") Integer policyId, - @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, - @Context HttpHeaders headers, - @Context HttpServletRequest request) - throws WebApplicationException { - log.info("Deleting policy(id=" + policyId + ") from bitstream(id=" + bitstreamId + ")."); - org.dspace.core.Context context = null; - - try { - context = createContext(); - org.dspace.content.Bitstream dspaceBitstream = findBitstream(context, bitstreamId, - org.dspace.core.Constants.WRITE); - - writeStats(dspaceBitstream, UsageEvent.Action.UPDATE, user_ip, user_agent, xforwardedfor, headers, - request, context); - - org.dspace.authorize.ResourcePolicy resourcePolicy = resourcePolicyService.find(context, policyId); - if (resourcePolicy.getdSpaceObject().getID().equals(dspaceBitstream.getID()) && authorizeService - .authorizeActionBoolean(context, dspaceBitstream, org.dspace.core.Constants.REMOVE)) { - - try { - resourcePolicyService.delete(context, resourcePolicy); - } catch (AuthorizeException e) { - processException( - "Someting went wrong while deleting policy(id=" + policyId + ") to bitstream(id=" + bitstreamId - + "), AuthorizeException! Message: " + e, context); - } - log.trace("Policy for bitstream(id=" + bitstreamId + ") was successfully removed."); - } - - context.complete(); - } catch (SQLException e) { - processException( - "Someting went wrong while deleting policy(id=" + policyId + ") to bitstream(id=" + bitstreamId - + "), SQLException! Message: " + e, context); - } catch (ContextException e) { - processException( - "Someting went wrong while deleting policy(id=" + policyId + ") to bitstream(id=" + bitstreamId - + "), ContextException. Message: " + e.getMessage(), context); - } finally { - processFinally(context); - } - - return Response.status(Status.OK).build(); - } - - /** - * Return the MIME type of the file, by file extension. - * - * @param name Name of file. - * @return String filled with type of file in MIME style. - */ - static String getMimeType(String name) { - return URLConnection.guessContentTypeFromName(name); - } - - /** - * Add policy(org.dspace.rest.common.ResourcePolicy) to bitstream. - * - * @param context Context to create DSpace ResourcePolicy. - * @param policy Policy which will be added to bitstream. - * @param dspaceBitstream DSpace Bitstream object. - * @throws SQLException An exception that provides information on a database access error or other errors. - * @throws AuthorizeException Exception indicating the current user of the context does not have permission - * to perform a particular action. - */ - private void addPolicyToBitstream(org.dspace.core.Context context, ResourcePolicy policy, - org.dspace.content.Bitstream dspaceBitstream) - throws SQLException, AuthorizeException { - org.dspace.authorize.ResourcePolicy dspacePolicy = - resourcePolicyService.create(context, null, - groupService.findByIdOrLegacyId(context, policy.getGroupId())); - dspacePolicy.setAction(policy.getActionInt()); - dspacePolicy.setdSpaceObject(dspaceBitstream); - dspacePolicy.setStartDate(policy.getStartDate()); - dspacePolicy.setEndDate(policy.getEndDate()); - dspacePolicy.setRpDescription(policy.getRpDescription()); - dspacePolicy.setRpName(policy.getRpName()); - - resourcePolicyService.update(context, dspacePolicy); - } - - /** - * Find bitstream from DSpace database. This encapsulates the - * org.dspace.content.Bitstream.find method with a check whether the item exists and - * whether the user logged into the context has permission to preform the requested action. - * - * @param context Context of actual logged user. - * @param id Id of bitstream in DSpace. - * @param action Constant from org.dspace.core.Constants. - * @return Returns DSpace bitstream. - * @throws WebApplicationException Is thrown when item with passed id is not exists and if user - * has no permission to do passed action. - */ - private org.dspace.content.Bitstream findBitstream(org.dspace.core.Context context, String id, int action) - throws WebApplicationException { - org.dspace.content.Bitstream bitstream = null; - try { - bitstream = bitstreamService.findByIdOrLegacyId(context, id); - - if ((bitstream == null) || (bitstreamService.getParentObject(context, bitstream) == null)) { - context.abort(); - log.warn("Bitstream(id=" + id + ") was not found!"); - throw new WebApplicationException(Response.Status.NOT_FOUND); - } else if (!authorizeService.authorizeActionBoolean(context, bitstream, action)) { - context.abort(); - if (context.getCurrentUser() != null) { - log.error("User(" + context.getCurrentUser().getEmail() + ") doesn't have the permission to " - + getActionString(action) + " bitstream!"); - } else { - log.error( - "User(anonymous) doesn't have the permission to " + getActionString(action) + " bitsteam!"); - } - throw new WebApplicationException(Response.Status.UNAUTHORIZED); - } - - } catch (SQLException e) { - processException("Something went wrong while finding bitstream. SQLException, Message:" + e, context); - } - return bitstream; - } -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/CollectionsResource.java b/dspace-rest/src/main/java/org/dspace/rest/CollectionsResource.java deleted file mode 100644 index 395a0af766..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/CollectionsResource.java +++ /dev/null @@ -1,755 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.rest; - -import static org.dspace.content.service.DSpaceObjectService.MD_COPYRIGHT_TEXT; -import static org.dspace.content.service.DSpaceObjectService.MD_INTRODUCTORY_TEXT; -import static org.dspace.content.service.DSpaceObjectService.MD_LICENSE; -import static org.dspace.content.service.DSpaceObjectService.MD_NAME; -import static org.dspace.content.service.DSpaceObjectService.MD_SHORT_DESCRIPTION; -import static org.dspace.content.service.DSpaceObjectService.MD_SIDEBAR_TEXT; - -import java.io.IOException; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import javax.servlet.http.HttpServletRequest; -import javax.ws.rs.Consumes; -import javax.ws.rs.DELETE; -import javax.ws.rs.DefaultValue; -import javax.ws.rs.GET; -import javax.ws.rs.POST; -import javax.ws.rs.PUT; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; - -import org.apache.logging.log4j.Logger; -import org.dspace.authorize.AuthorizeException; -import org.dspace.authorize.factory.AuthorizeServiceFactory; -import org.dspace.authorize.service.AuthorizeService; -import org.dspace.content.factory.ContentServiceFactory; -import org.dspace.content.service.CollectionService; -import org.dspace.content.service.ItemService; -import org.dspace.content.service.WorkspaceItemService; -import org.dspace.core.Constants; -import org.dspace.core.LogHelper; -import org.dspace.rest.common.Collection; -import org.dspace.rest.common.Item; -import org.dspace.rest.common.MetadataEntry; -import org.dspace.rest.exceptions.ContextException; -import org.dspace.usage.UsageEvent; -import org.dspace.workflow.WorkflowService; -import org.dspace.workflow.factory.WorkflowServiceFactory; - -/** - * This class provides all CRUD operation over collections. - * - * @author Rostislav Novak (Computing and Information Centre, CTU in Prague) - */ -@Path("/collections") -public class CollectionsResource extends Resource { - protected CollectionService collectionService = ContentServiceFactory.getInstance().getCollectionService(); - protected ItemService itemService = ContentServiceFactory.getInstance().getItemService(); - protected AuthorizeService authorizeService = AuthorizeServiceFactory.getInstance().getAuthorizeService(); - protected WorkspaceItemService workspaceItemService = ContentServiceFactory.getInstance().getWorkspaceItemService(); - protected WorkflowService workflowService = WorkflowServiceFactory.getInstance().getWorkflowService(); - - private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(CollectionsResource.class); - - /** - * Return instance of collection with passed id. You can add more properties - * through expand parameter. - * - * @param collectionId Id of collection in DSpace. - * @param expand String in which is what you want to add to returned instance - * of collection. Options are: "all", "parentCommunityList", - * "parentCommunity", "items", "license" and "logo". If you want - * to use multiple options, it must be separated by commas. - * @param limit Limit value for items in list in collection. Default value is - * 100. - * @param offset Offset of start index in list of items of collection. Default - * value is 0. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the collection as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @return Return instance of collection. It can also return status code - * NOT_FOUND(404) if id of collection is incorrect or status code - * UNATHORIZED(401) if user has no permission to read collection. - * @throws WebApplicationException It is thrown when was problem with database reading - * (SQLException) or problem with creating - * context(ContextException). It is thrown by NOT_FOUND and - * UNATHORIZED status codes, too. - */ - @GET - @Path("/{collection_id}") - @Produces( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public org.dspace.rest.common.Collection getCollection(@PathParam("collection_id") String collectionId, - @QueryParam("expand") String expand, - @QueryParam("limit") @DefaultValue("100") Integer limit, - @QueryParam("offset") @DefaultValue("0") Integer offset, - @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, - @Context HttpHeaders headers, - @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Reading collection(id=" + collectionId + ")."); - org.dspace.core.Context context = null; - Collection collection = null; - - try { - context = createContext(); - - org.dspace.content.Collection dspaceCollection = findCollection(context, collectionId, - org.dspace.core.Constants.READ); - writeStats(dspaceCollection, UsageEvent.Action.VIEW, user_ip, user_agent, xforwardedfor, - headers, request, context); - - collection = new Collection(dspaceCollection, servletContext, expand, context, limit, offset); - context.complete(); - - } catch (SQLException e) { - processException("Could not read collection(id=" + collectionId + "), SQLException. Message: " + e, - context); - } catch (ContextException e) { - processException( - "Could not read collection(id=" + collectionId + "), ContextException. Message: " + e.getMessage(), - context); - } finally { - processFinally(context); - } - - log.trace("Collection(id=" + collectionId + ") has been successfully read."); - return collection; - } - - /** - * Return array of all collections in DSpace. You can add more properties - * through expand parameter. - * - * @param expand String in which is what you want to add to returned instance - * of collection. Options are: "all", "parentCommunityList", - * "parentCommunity", "items", "license" and "logo". If you want - * to use multiple options, it must be separated by commas. - * @param limit Limit value for items in list in collection. Default value is - * 100. - * @param offset Offset of start index in list of items of collection. Default - * value is 0. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the collections as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @return Return array of collection, on which has logged user permission - * to view. - * @throws WebApplicationException It is thrown when was problem with database reading - * (SQLException) or problem with creating - * context(ContextException). - */ - @GET - @Produces( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public org.dspace.rest.common.Collection[] getCollections(@QueryParam("expand") String expand, - @QueryParam("limit") @DefaultValue("100") Integer limit, - @QueryParam("offset") @DefaultValue("0") Integer offset, - @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, - @Context HttpHeaders headers, - @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Reading all collections.(offset=" + offset + ",limit=" + limit + ")"); - org.dspace.core.Context context = null; - List collections = new ArrayList<>(); - - try { - context = createContext(); - - if (!((limit != null) && (limit >= 0) && (offset != null) && (offset >= 0))) { - log.warn("Paging was badly set."); - limit = 100; - offset = 0; - } - - List dspaceCollections = collectionService.findAll(context, limit, offset); - for (org.dspace.content.Collection dspaceCollection : dspaceCollections) { - if (authorizeService - .authorizeActionBoolean(context, dspaceCollection, org.dspace.core.Constants.READ)) { - Collection collection = new org.dspace.rest.common.Collection(dspaceCollection, servletContext, - null, context, limit, - offset); - collections.add(collection); - writeStats(dspaceCollection, UsageEvent.Action.VIEW, user_ip, user_agent, - xforwardedfor, headers, request, context); - } - } - context.complete(); - } catch (SQLException e) { - processException("Something went wrong while reading collections from database. Message: " + e, context); - } catch (ContextException e) { - processException("Something went wrong while reading collections, ContextError. Message: " + e.getMessage(), - context); - } finally { - processFinally(context); - } - - log.trace("All collections were successfully read."); - return collections.toArray(new org.dspace.rest.common.Collection[0]); - } - - /** - * Return array of items in collection. You can add more properties to items - * with expand parameter. - * - * @param collectionId Id of collection in DSpace. - * @param expand String which define, what additional properties will be in - * returned item. Options are separeted by commas and are: "all", - * "metadata", "parentCollection", "parentCollectionList", - * "parentCommunityList" and "bitstreams". - * @param limit Limit value for items in array. Default value is 100. - * @param offset Offset of start index in array of items of collection. Default - * value is 0. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the collection as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @return Return array of items, on which has logged user permission to - * read. It can also return status code NOT_FOUND(404) if id of - * collection is incorrect or status code UNATHORIZED(401) if user - * has no permission to read collection. - * @throws WebApplicationException It is thrown when was problem with database reading - * (SQLException) or problem with creating - * context(ContextException). It is thrown by NOT_FOUND and - * UNATHORIZED status codes, too. - */ - @GET - @Path("/{collection_id}/items") - @Produces( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public org.dspace.rest.common.Item[] getCollectionItems(@PathParam("collection_id") String collectionId, - @QueryParam("expand") String expand, - @QueryParam("limit") @DefaultValue("100") Integer limit, - @QueryParam("offset") @DefaultValue("0") Integer offset, - @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, - @Context HttpHeaders headers, - @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Reading collection(id=" + collectionId + ") items."); - org.dspace.core.Context context = null; - List items = null; - - try { - context = createContext(); - - org.dspace.content.Collection dspaceCollection = findCollection(context, collectionId, - org.dspace.core.Constants.READ); - writeStats(dspaceCollection, UsageEvent.Action.VIEW, user_ip, user_agent, xforwardedfor, - headers, request, context); - - items = new ArrayList<>(); - Iterator dspaceItems = itemService.findByCollection(context, dspaceCollection, - limit, offset); - - while (dspaceItems.hasNext()) { - org.dspace.content.Item dspaceItem = dspaceItems.next(); - - if (itemService.isItemListedForUser(context, dspaceItem)) { - items.add(new Item(dspaceItem, servletContext, expand, context)); - writeStats(dspaceItem, UsageEvent.Action.VIEW, user_ip, user_agent, xforwardedfor, - headers, request, context); - } - } - - context.complete(); - } catch (SQLException e) { - processException("Could not read collection items, SQLException. Message: " + e, context); - } catch (ContextException e) { - processException("Could not read collection items, ContextException. Message: " + e.getMessage(), context); - } finally { - processFinally(context); - } - - log.trace("All items in collection(id=" + collectionId + ") were successfully read."); - return items.toArray(new Item[0]); - } - - /** - * Create item in collection. Item can be without filled metadata. - * - * @param collectionId Id of collection in which will be item created. - * @param item Item filled only with metadata, other variables are ignored. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the collection as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @return Return status code with item. Return status (OK)200 if item was - * created. NOT_FOUND(404) if id of collection does not exists. - * UNAUTHORIZED(401) if user have not permission to write items in - * collection. - * @throws WebApplicationException It is thrown when was problem with database reading or - * writing (SQLException) or problem with creating - * context(ContextException) or problem with authorization to - * collection or IOException or problem with index item into - * browse index. It is thrown by NOT_FOUND and UNATHORIZED - * status codes, too. - */ - @POST - @Path("/{collection_id}/items") - @Consumes( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public Item addCollectionItem(@PathParam("collection_id") String collectionId, Item item, - @QueryParam("userIP") String user_ip, @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, @Context HttpHeaders headers, - @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Create item in collection(id=" + collectionId + ")."); - org.dspace.core.Context context = null; - Item returnItem = null; - - try { - context = createContext(); - org.dspace.content.Collection dspaceCollection = findCollection(context, collectionId, - org.dspace.core.Constants.WRITE); - - writeStats(dspaceCollection, UsageEvent.Action.UPDATE, user_ip, user_agent, xforwardedfor, - headers, request, context); - - log.trace("Creating item in collection(id=" + collectionId + ")."); - org.dspace.content.WorkspaceItem workspaceItem = workspaceItemService - .create(context, dspaceCollection, false); - org.dspace.content.Item dspaceItem = workspaceItem.getItem(); - - log.trace("Adding metadata to item(id=" + dspaceItem.getID() + ")."); - if (item.getMetadata() != null) { - for (MetadataEntry entry : item.getMetadata()) { - String data[] = mySplit(entry.getKey()); - itemService.addMetadata(context, dspaceItem, data[0], data[1], data[2], entry.getLanguage(), - entry.getValue()); - } - } - - workspaceItemService.update(context, workspaceItem); - - try { - // Must insert the item into workflow - log.trace("Starting workflow for item(id=" + dspaceItem.getID() + ")."); - workflowService.start(context, workspaceItem); - } catch (Exception e) { - log.error( - LogHelper.getHeader(context, "Error while starting workflow", - "Item id: " + dspaceItem.getID()), - e); - throw new ContextException("Error while starting workflow for item(id=" + dspaceItem.getID() + ")", e); - } - - returnItem = new Item(workspaceItem.getItem(), servletContext, "", context); - - context.complete(); - - } catch (SQLException e) { - processException("Could not add item into collection(id=" + collectionId + "), SQLException. Message: " + e, - context); - } catch (AuthorizeException e) { - processException( - "Could not add item into collection(id=" + collectionId + "), AuthorizeException. Message: " + e, - context); - } catch (ContextException e) { - processException( - "Could not add item into collection(id=" + collectionId + "), ContextException. Message: " + e - .getMessage(), - context); - } finally { - processFinally(context); - } - - log.info( - "Item successfully created in collection(id=" + collectionId + "). Item handle=" + returnItem.getHandle()); - return returnItem; - } - - /** - * Update collection. It replace all properties. - * - * @param collectionId Id of collection in DSpace. - * @param collection Collection which will replace properties of actual collection. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the collection as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @return Return response 200 if was everything all right. Otherwise 400 - * when id of community was incorrect or 401 if was problem with - * permission to write into collection. - * @throws WebApplicationException It is thrown when was problem with database reading or - * writing. Or problem with authorization to collection. Or - * problem with creating context. - */ - @PUT - @Path("/{collection_id}") - @Consumes( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public Response updateCollection(@PathParam("collection_id") String collectionId, - org.dspace.rest.common.Collection collection, @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, - @Context HttpHeaders headers, @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Updating collection(id=" + collectionId + ")."); - org.dspace.core.Context context = null; - - try { - context = createContext(); - org.dspace.content.Collection dspaceCollection = findCollection(context, collectionId, - org.dspace.core.Constants.WRITE); - - writeStats(dspaceCollection, UsageEvent.Action.UPDATE, user_ip, user_agent, xforwardedfor, - headers, request, context); - - collectionService.setMetadataSingleValue(context, dspaceCollection, - MD_NAME, collection.getName(), null); - collectionService.setMetadataSingleValue(context, dspaceCollection, - MD_LICENSE, collection.getLicense(), null); - - // dspaceCollection.setLogo(collection.getLogo()); // TODO Add this option. - collectionService.setMetadataSingleValue(context, dspaceCollection, - MD_COPYRIGHT_TEXT, collection.getCopyrightText(), null); - collectionService.setMetadataSingleValue(context, dspaceCollection, - MD_INTRODUCTORY_TEXT, collection.getIntroductoryText(), null); - collectionService.setMetadataSingleValue(context, dspaceCollection, - MD_SHORT_DESCRIPTION, collection.getShortDescription(), null); - collectionService.setMetadataSingleValue(context, dspaceCollection, - MD_SIDEBAR_TEXT, collection.getSidebarText(), null); - collectionService.update(context, dspaceCollection); - - context.complete(); - - } catch (ContextException e) { - processException( - "Could not update collection(id=" + collectionId + "), ContextException. Message: " + e.getMessage(), - context); - } catch (SQLException e) { - processException("Could not update collection(id=" + collectionId + "), SQLException. Message: " + e, - context); - } catch (AuthorizeException e) { - processException("Could not update collection(id=" + collectionId + "), AuthorizeException. Message: " + e, - context); - } finally { - processFinally(context); - } - - log.info("Collection(id=" + collectionId + ") successfully updated."); - return Response.ok().build(); - } - - /** - * Delete collection. - * - * @param collectionId Id of collection which will be deleted. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the collection as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @return Return response code OK(200) if was everything all right. - * Otherwise return NOT_FOUND(404) if was id of community or - * collection incorrect. Or (UNAUTHORIZED)401 if was problem with - * permission to community or collection. - * @throws WebApplicationException Thrown if there was a problem with creating context or problem - * with database reading or writing. Or problem with deleting - * collection caused by IOException or authorization. - */ - @DELETE - @Path("/{collection_id}") - @Consumes( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public Response deleteCollection(@PathParam("collection_id") String collectionId, - @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, - @Context HttpHeaders headers, @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Delete collection(id=" + collectionId + ")."); - org.dspace.core.Context context = null; - - try { - context = createContext(); - org.dspace.content.Collection dspaceCollection = findCollection(context, collectionId, - org.dspace.core.Constants.DELETE); - - writeStats(dspaceCollection, UsageEvent.Action.REMOVE, user_ip, user_agent, xforwardedfor, - headers, request, context); - - collectionService.delete(context, dspaceCollection); - collectionService.update(context, dspaceCollection); - - context.complete(); - } catch (ContextException e) { - processException( - "Could not delete collection(id=" + collectionId + "), ContextException. Message: " + e.getMessage(), - context); - } catch (SQLException e) { - processException("Could not delete collection(id=" + collectionId + "), SQLException. Message: " + e, - context); - } catch (AuthorizeException e) { - processException("Could not delete collection(id=" + collectionId + "), AuthorizeException. Message: " + e, - context); - } catch (IOException e) { - processException("Could not delete collection(id=" + collectionId + "), IOException. Message: " + e, - context); - } finally { - processFinally(context); - } - - log.info("Collection(id=" + collectionId + ") was successfully deleted."); - return Response.ok().build(); - } - - /** - * Delete item in collection. - * - * @param collectionId Id of collection which will be deleted. - * @param itemId Id of item in colletion. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the collection as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @return It returns status code: OK(200). NOT_FOUND(404) if item or - * collection was not found, UNAUTHORIZED(401) if user is not - * allowed to delete item or permission to write into collection. - * @throws WebApplicationException It can be thrown by: SQLException, when was problem with - * database reading or writting. AuthorizeException, when was - * problem with authorization to item or collection. - * IOException, when was problem with removing item. - * ContextException, when was problem with creating context of - * DSpace. - */ - @DELETE - @Path("/{collection_id}/items/{item_id}") - @Consumes( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public Response deleteCollectionItem(@PathParam("collection_id") String collectionId, - @PathParam("item_id") String itemId, - @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, - @Context HttpHeaders headers, @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Delete item(id=" + itemId + ") in collection(id=" + collectionId + ")."); - org.dspace.core.Context context = null; - - try { - context = createContext(); - - org.dspace.content.Collection dspaceCollection = collectionService - .findByIdOrLegacyId(context, collectionId); - org.dspace.content.Item item = itemService.findByIdOrLegacyId(context, itemId); - - - if (dspaceCollection == null) { - //throw collection not exist - log.warn("Collection(id=" + itemId + ") was not found!"); - throw new WebApplicationException(Response.Status.NOT_FOUND); - } - - if (item == null) { - //throw item not exist - log.warn("Item(id=" + itemId + ") was not found!"); - throw new WebApplicationException(Response.Status.NOT_FOUND); - } - - if (!authorizeService.authorizeActionBoolean(context, item, Constants.REMOVE) - || !authorizeService.authorizeActionBoolean(context, dspaceCollection, Constants.REMOVE)) { - //throw auth - if (context.getCurrentUser() != null) { - log.error( - "User(" + context.getCurrentUser().getEmail() + ") does not have permission to delete item!"); - } else { - log.error("User(anonymous) has not permission to delete item!"); - } - throw new WebApplicationException(Response.Status.UNAUTHORIZED); - } - - collectionService.removeItem(context, dspaceCollection, item); - collectionService.update(context, dspaceCollection); - itemService.update(context, item); - - writeStats(dspaceCollection, UsageEvent.Action.UPDATE, user_ip, user_agent, xforwardedfor, - headers, request, context); - writeStats(item, UsageEvent.Action.REMOVE, user_ip, user_agent, xforwardedfor, headers, request, context); - - context.complete(); - - } catch (ContextException e) { - processException("Could not delete item(id=" + itemId + ") in collection(id=" + collectionId - + "), ContextException. Message: " + e.getMessage(), context); - } catch (SQLException e) { - processException("Could not delete item(id=" + itemId + ") in collection(id=" + collectionId - + "), SQLException. Message: " + e, context); - } catch (AuthorizeException e) { - processException("Could not delete item(id=" + itemId + ") in collection(id=" + collectionId - + "), AuthorizeException. Message: " + e, context); - } catch (IOException e) { - processException("Could not delete item(id=" + itemId + ") in collection(id=" + collectionId - + "), IOException. Message: " + e, context); - } finally { - processFinally(context); - } - - log.info("Item(id=" + itemId + ") in collection(id=" + collectionId + ") was successfully deleted."); - return Response.ok().build(); - } - - /** - * Search for first collection with passed name. - * - * @param name Name of collection. - * @param headers If you want to access the collection as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @return It returns null if collection was not found. Otherwise returns - * first founded collection. - * @throws WebApplicationException A general exception a servlet can throw when it encounters difficulty. - */ - @POST - @Path("/find-collection") - @Consumes( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - @Produces( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public Collection findCollectionByName(String name, @Context HttpHeaders headers) throws WebApplicationException { - log.info("Searching for first collection with name=" + name + "."); - org.dspace.core.Context context = null; - Collection collection = null; - - try { - context = createContext(); - - List dspaceCollections = collectionService.findAll(context); - //TODO, this would be more efficient with a findByName query - - for (org.dspace.content.Collection dspaceCollection : dspaceCollections) { - if (authorizeService - .authorizeActionBoolean(context, dspaceCollection, org.dspace.core.Constants.READ)) { - if (dspaceCollection.getName().equals(name)) { - collection = new Collection(dspaceCollection, servletContext, "", context, 100, 0); - break; - } - } - } - - context.complete(); - - } catch (SQLException e) { - processException( - "Something went wrong while searching for collection(name=" + name + ") from database. Message: " - + e, context); - } catch (ContextException e) { - processException( - "Something went wrong while searching for collection(name=" + name + "), ContextError. Message: " - + e.getMessage(), context); - } finally { - processFinally(context); - } - - if (collection == null) { - log.info("Collection was not found."); - } else { - log.info("Collection was found with id(" + collection.getUUID() + ")."); - } - return collection; - } - - /** - * Find collection from DSpace database. It is encapsulation of method - * org.dspace.content.Collection.find with checking if item exist and if - * user logged into context has permission to do passed action. - * - * @param context Context of actual logged user. - * @param id Id of collection in DSpace. - * @param action Constant from org.dspace.core.Constants. - * @return It returns DSpace collection. - * @throws WebApplicationException Is thrown when item with passed id is not exists and if user - * has no permission to do passed action. - */ - private org.dspace.content.Collection findCollection(org.dspace.core.Context context, String id, int action) - throws WebApplicationException { - org.dspace.content.Collection collection = null; - try { - collection = collectionService.findByIdOrLegacyId(context, id); - - if (collection == null) { - context.abort(); - log.warn("Collection(id=" + id + ") was not found!"); - throw new WebApplicationException(Response.Status.NOT_FOUND); - } else if (!authorizeService.authorizeActionBoolean(context, collection, action)) { - context.abort(); - if (context.getCurrentUser() != null) { - log.error("User(" + context.getCurrentUser().getEmail() + ") has not permission to " - + getActionString(action) + " collection!"); - } else { - log.error("User(anonymous) has not permission to " + getActionString(action) + " collection!"); - } - throw new WebApplicationException(Response.Status.UNAUTHORIZED); - } - - } catch (SQLException e) { - processException("Something get wrong while finding collection(id=" + id + "). SQLException, Message: " + e, - context); - } - return collection; - } -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/CommunitiesResource.java b/dspace-rest/src/main/java/org/dspace/rest/CommunitiesResource.java deleted file mode 100644 index c3d4840910..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/CommunitiesResource.java +++ /dev/null @@ -1,1052 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.rest; - -import static org.dspace.content.service.DSpaceObjectService.MD_COPYRIGHT_TEXT; -import static org.dspace.content.service.DSpaceObjectService.MD_INTRODUCTORY_TEXT; -import static org.dspace.content.service.DSpaceObjectService.MD_LICENSE; -import static org.dspace.content.service.DSpaceObjectService.MD_NAME; -import static org.dspace.content.service.DSpaceObjectService.MD_SHORT_DESCRIPTION; -import static org.dspace.content.service.DSpaceObjectService.MD_SIDEBAR_TEXT; - -import java.io.IOException; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.List; -import javax.servlet.http.HttpServletRequest; -import javax.ws.rs.Consumes; -import javax.ws.rs.DELETE; -import javax.ws.rs.DefaultValue; -import javax.ws.rs.GET; -import javax.ws.rs.POST; -import javax.ws.rs.PUT; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; - -import org.apache.logging.log4j.Logger; -import org.dspace.authorize.AuthorizeException; -import org.dspace.authorize.factory.AuthorizeServiceFactory; -import org.dspace.authorize.service.AuthorizeService; -import org.dspace.content.factory.ContentServiceFactory; -import org.dspace.content.service.CollectionService; -import org.dspace.content.service.CommunityService; -import org.dspace.rest.common.Collection; -import org.dspace.rest.common.Community; -import org.dspace.rest.exceptions.ContextException; -import org.dspace.usage.UsageEvent; - -/** - * Class which provides CRUD methods over communities. - * - * @author Rostislav Novak (Computing and Information Centre, CTU in Prague) - */ -@Path("/communities") -public class CommunitiesResource extends Resource { - protected CommunityService communityService = ContentServiceFactory.getInstance().getCommunityService(); - protected CollectionService collectionService = ContentServiceFactory.getInstance().getCollectionService(); - protected AuthorizeService authorizeService = AuthorizeServiceFactory.getInstance().getAuthorizeService(); - - private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(CommunitiesResource.class); - - /** - * Returns community with basic properties. If you want more, use expand - * parameter or method for community collections or subcommunities. - * - * @param communityId Id of community in DSpace. - * @param expand String in which is what you want to add to returned instance - * of community. Options are: "all", "parentCommunity", - * "collections", "subCommunities" and "logo". If you want to use - * multiple options, it must be separated by commas. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the community as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @return Return instance of org.dspace.rest.common.Community. - * @throws WebApplicationException Thrown if there was a problem with creating context or problem - * with database reading. Also if id of community is incorrect - * or logged user into context has no permission to read. - */ - @GET - @Path("/{community_id}") - @Produces( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public Community getCommunity(@PathParam("community_id") String communityId, @QueryParam("expand") String expand, - @QueryParam("userIP") String user_ip, @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, @Context HttpHeaders headers, - @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Reading community(id=" + communityId + ")."); - org.dspace.core.Context context = null; - Community community = null; - - try { - context = createContext(); - - org.dspace.content.Community dspaceCommunity = findCommunity(context, communityId, - org.dspace.core.Constants.READ); - writeStats(dspaceCommunity, UsageEvent.Action.VIEW, user_ip, user_agent, xforwardedfor, headers, - request, context); - - community = new Community(dspaceCommunity, servletContext, expand, context); - context.complete(); - - } catch (SQLException e) { - processException("Could not read community(id=" + communityId + "), SQLException. Message:" + e, context); - } catch (ContextException e) { - processException( - "Could not read community(id=" + communityId + "), ContextException. Message:" + e.getMessage(), - context); - } finally { - processFinally(context); - } - - - log.trace("Community(id=" + communityId + ") was successfully read."); - return community; - } - - /** - * Return all communities in DSpace. - * - * @param expand String in which is what you want to add to returned instance - * of community. Options are: "all", "parentCommunity", - * "collections", "subCommunities" and "logo". If you want to use - * multiple options, it must be separated by commas. - * @param limit Maximum communities in array. Default value is 100. - * @param offset Index from which will start array of communities. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the community as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @return Return array of communities. - * @throws WebApplicationException It can be caused by creating context or while was problem - * with reading community from database(SQLException). - */ - @GET - @Produces( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public Community[] getCommunities(@QueryParam("expand") String expand, - @QueryParam("limit") @DefaultValue("100") Integer limit, - @QueryParam("offset") @DefaultValue("0") Integer offset, - @QueryParam("userIP") String user_ip, @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, @Context HttpHeaders headers, - @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Reading all communities.(offset=" + offset + " ,limit=" + limit + ")."); - org.dspace.core.Context context = null; - ArrayList communities = null; - - try { - context = createContext(); - - List dspaceCommunities = communityService.findAll(context); - communities = new ArrayList<>(); - - if (!((limit != null) && (limit >= 0) && (offset != null) && (offset >= 0))) { - log.warn("Paging was badly set, using default values."); - limit = 100; - offset = 0; - } - - for (int i = offset; (i < (offset + limit)) && i < dspaceCommunities.size(); i++) { - if (authorizeService - .authorizeActionBoolean(context, dspaceCommunities.get(i), org.dspace.core.Constants.READ)) { - Community community = new Community(dspaceCommunities.get(i), servletContext, expand, context); - writeStats(dspaceCommunities.get(i), UsageEvent.Action.VIEW, user_ip, user_agent, - xforwardedfor, headers, request, context); - communities.add(community); - } - } - - context.complete(); - } catch (SQLException e) { - processException("Could not read communities, SQLException. Message:" + e, context); - } catch (ContextException e) { - processException("Could not read communities, ContextException. Message:" + e.getMessage(), context); - } finally { - processFinally(context); - } - - log.trace("All communities successfully read."); - return communities.toArray(new Community[0]); - } - - /** - * Return all top communities in DSpace. Top communities are communities on - * the root of tree. - * - * @param expand String in which is what you want to add to returned instance - * of community. Options are: "all", "parentCommunity", - * "collections", "subCommunities" and "logo". If you want to use - * multiple options, it must be separated by commas. - * @param limit Maximum communities in array. Default value is 100. - * @param offset Index from which will start array of communities. Default - * value is 0. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the community as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @return Return array of top communities. - * @throws WebApplicationException It can be caused by creating context or while was problem - * with reading community from database(SQLException). - */ - @GET - @Path("/top-communities") - @Produces( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public Community[] getTopCommunities(@QueryParam("expand") String expand, - @QueryParam("limit") @DefaultValue("20") Integer limit, - @QueryParam("offset") @DefaultValue("0") Integer offset, - @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, - @Context HttpHeaders headers, @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Reading all top communities.(offset=" + offset + " ,limit=" + limit + ")."); - org.dspace.core.Context context = null; - ArrayList communities = null; - - try { - context = createContext(); - - List dspaceCommunities = communityService.findAllTop(context); - communities = new ArrayList<>(); - - if (!((limit != null) && (limit >= 0) && (offset != null) && (offset >= 0))) { - log.warn("Paging was badly set, using default values."); - limit = 100; - offset = 0; - } - - for (int i = offset; (i < (offset + limit)) && i < dspaceCommunities.size(); i++) { - if (authorizeService - .authorizeActionBoolean(context, dspaceCommunities.get(i), org.dspace.core.Constants.READ)) { - Community community = new Community(dspaceCommunities.get(i), servletContext, expand, context); - writeStats(dspaceCommunities.get(i), UsageEvent.Action.VIEW, user_ip, user_agent, - xforwardedfor, headers, request, context); - communities.add(community); - } - } - - context.complete(); - } catch (SQLException e) { - processException("Could not read top communities, SQLException. Message:" + e, context); - } catch (ContextException e) { - processException("Could not read top communities, ContextException. Message:" + e.getMessage(), context); - } finally { - processFinally(context); - } - - log.trace("All top communities successfully read."); - return communities.toArray(new Community[0]); - } - - /** - * Return all collections of community. - * - * @param communityId Id of community in DSpace. - * @param expand String in which is what you want to add to returned instance - * of collection. Options are: "all", "parentCommunityList", - * "parentCommunity", "items", "license" and "logo". If you want - * to use multiple options, it must be separated by commas. - * @param limit Maximum collection in array. Default value is 100. - * @param offset Index from which will start array of collections. Default - * value is 0. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the community as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @return Return array of collections of community. - * @throws WebApplicationException It can be caused by creating context or while was problem - * with reading community from database(SQLException). - */ - @GET - @Path("/{community_id}/collections") - @Produces( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public Collection[] getCommunityCollections(@PathParam("community_id") String communityId, - @QueryParam("expand") String expand, - @QueryParam("limit") @DefaultValue("100") Integer limit, - @QueryParam("offset") @DefaultValue("0") Integer offset, - @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, - @Context HttpHeaders headers, @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Reading community(id=" + communityId + ") collections."); - org.dspace.core.Context context = null; - ArrayList collections = null; - - try { - context = createContext(); - - org.dspace.content.Community dspaceCommunity = findCommunity(context, communityId, - org.dspace.core.Constants.READ); - writeStats(dspaceCommunity, UsageEvent.Action.VIEW, user_ip, user_agent, xforwardedfor, headers, - request, context); - - if (!((limit != null) && (limit >= 0) && (offset != null) && (offset >= 0))) { - log.warn("Pagging was badly set, using default values."); - limit = 100; - offset = 0; - } - - collections = new ArrayList<>(); - List dspaceCollections = dspaceCommunity.getCollections(); - for (int i = offset; (i < (offset + limit)) && (i < dspaceCollections.size()); i++) { - if (authorizeService - .authorizeActionBoolean(context, dspaceCollections.get(i), org.dspace.core.Constants.READ)) { - collections.add(new Collection(dspaceCollections.get(i), servletContext, expand, context, 20, 0)); - writeStats(dspaceCollections.get(i), UsageEvent.Action.VIEW, user_ip, user_agent, - xforwardedfor, headers, request, context); - } - } - - context.complete(); - } catch (SQLException e) { - processException("Could not read community(id=" + communityId + ") collections, SQLException. Message:" + e, - context); - } catch (ContextException e) { - processException( - "Could not read community(id=" + communityId + ") collections, ContextException. Message:" + e - .getMessage(), - context); - } finally { - processFinally(context); - } - - log.trace("Community(id=" + communityId + ") collections were successfully read."); - return collections.toArray(new Collection[0]); - } - - /** - * Return all subcommunities of community. - * - * @param communityId Id of community in DSpace. - * @param expand String in which is what you want to add to returned instance - * of community. Options are: "all", "parentCommunity", - * "collections", "subCommunities" and "logo". If you want to use - * multiple options, it must be separated by commas. - * @param limit Maximum communities in array. Default value is 20. - * @param offset Index from which will start array of communities. Default - * value is 0. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the community as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @return Return array of subcommunities of community. - * @throws WebApplicationException It can be caused by creating context or while was problem - * with reading community from database(SQLException). - */ - @GET - @Path("/{community_id}/communities") - @Produces( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public Community[] getCommunityCommunities(@PathParam("community_id") String communityId, - @QueryParam("expand") String expand, - @QueryParam("limit") @DefaultValue("20") Integer limit, - @QueryParam("offset") @DefaultValue("0") Integer offset, - @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, - @Context HttpHeaders headers, @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Reading community(id=" + communityId + ") subcommunities."); - org.dspace.core.Context context = null; - ArrayList communities = null; - - try { - context = createContext(); - - org.dspace.content.Community dspaceCommunity = findCommunity(context, communityId, - org.dspace.core.Constants.READ); - writeStats(dspaceCommunity, UsageEvent.Action.VIEW, user_ip, user_agent, xforwardedfor, headers, - request, context); - - if (!((limit != null) && (limit >= 0) && (offset != null) && (offset >= 0))) { - log.warn("Pagging was badly set, using default values."); - limit = 100; - offset = 0; - } - - communities = new ArrayList<>(); - List dspaceCommunities = dspaceCommunity.getSubcommunities(); - for (int i = offset; (i < (offset + limit)) && (i < dspaceCommunities.size()); i++) { - if (authorizeService - .authorizeActionBoolean(context, dspaceCommunities.get(i), org.dspace.core.Constants.READ)) { - communities.add(new Community(dspaceCommunities.get(i), servletContext, expand, context)); - writeStats(dspaceCommunities.get(i), UsageEvent.Action.VIEW, user_ip, user_agent, - xforwardedfor, headers, request, context); - } - } - - context.complete(); - } catch (SQLException e) { - processException( - "Could not read community(id=" + communityId + ") subcommunities, SQLException. Message:" + e, - context); - } catch (ContextException e) { - processException( - "Could not read community(id=" + communityId + ") subcommunities, ContextException. Message:" - + e.getMessage(), context); - } finally { - processFinally(context); - } - - log.trace("Community(id=" + communityId + ") subcommunities were successfully read."); - return communities.toArray(new Community[0]); - } - - /** - * Create community at top level. Creating community at top level has - * permission only admin. - * - * @param community Community which will be created at top level of communities. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the community as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @return Returns response with handle of community, if was all ok. - * @throws WebApplicationException It can be thrown by SQLException, AuthorizeException and - * ContextException. - */ - @POST - @Consumes( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public Community createCommunity(Community community, @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, - @Context HttpHeaders headers, @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Creating community at top level."); - org.dspace.core.Context context = null; - Community retCommunity = null; - - try { - context = createContext(); - if (!authorizeService.isAdmin(context)) { - context.abort(); - String user = "anonymous"; - if (context.getCurrentUser() != null) { - user = context.getCurrentUser().getEmail(); - } - log.error("User(" + user + ") has not permission to create community!"); - throw new WebApplicationException(Response.Status.UNAUTHORIZED); - } - - org.dspace.content.Community dspaceCommunity = communityService.create(null, context); - writeStats(dspaceCommunity, UsageEvent.Action.CREATE, user_ip, user_agent, xforwardedfor, - headers, request, context); - - communityService.setMetadataSingleValue(context, dspaceCommunity, - MD_NAME, community.getName(), null); - communityService.setMetadataSingleValue(context, dspaceCommunity, - MD_COPYRIGHT_TEXT, community.getCopyrightText(), null); - communityService.setMetadataSingleValue(context, dspaceCommunity, - MD_INTRODUCTORY_TEXT, community.getIntroductoryText(), null); - communityService.setMetadataSingleValue(context, dspaceCommunity, - MD_SHORT_DESCRIPTION, community.getShortDescription(), null); - communityService.setMetadataSingleValue(context, dspaceCommunity, - MD_SIDEBAR_TEXT, community.getSidebarText(), null); - communityService.update(context, dspaceCommunity); - - retCommunity = new Community(dspaceCommunity, servletContext, "", context); - context.complete(); - } catch (SQLException e) { - processException("Could not create new top community, SQLException. Message: " + e, context); - } catch (ContextException e) { - processException("Could not create new top community, ContextException. Message: " + e.getMessage(), - context); - } catch (AuthorizeException e) { - processException("Could not create new top community, AuthorizeException. Message: " + e.getMessage(), - context); - } finally { - processFinally(context); - } - - - log.info("Community at top level has been successfully created. Handle:" + retCommunity.getHandle()); - return retCommunity; - } - - /** - * Create collection in community. - * - * @param communityId Id of community in DSpace. - * @param collection Collection which will be added into community. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the community as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @return Return response 200 if was everything all right. Otherwise 400 - * when id of community was incorrect or 401 if was problem with - * permission to write into collection. - * @throws WebApplicationException It is thrown when was problem with database reading or - * writing. Or problem with authorization to community. Or - * problem with creating context. - */ - @POST - @Path("/{community_id}/collections") - @Consumes( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public Collection addCommunityCollection(@PathParam("community_id") String communityId, Collection collection, - @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, - @Context HttpHeaders headers, @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Adding collection into community(id=" + communityId + ")."); - org.dspace.core.Context context = null; - Collection retCollection = null; - - try { - context = createContext(); - - org.dspace.content.Community dspaceCommunity = findCommunity(context, communityId, - org.dspace.core.Constants.WRITE); - writeStats(dspaceCommunity, UsageEvent.Action.UPDATE, user_ip, user_agent, xforwardedfor, - headers, request, context); - org.dspace.content.Collection dspaceCollection = collectionService.create(context, dspaceCommunity); - collectionService.setMetadataSingleValue(context, dspaceCollection, - MD_LICENSE, collection.getLicense(), null); - // dspaceCollection.setLogo(collection.getLogo()); // TODO Add this option. - collectionService.setMetadataSingleValue(context, dspaceCollection, - MD_NAME, collection.getName(), null); - collectionService.setMetadataSingleValue(context, dspaceCollection, - MD_COPYRIGHT_TEXT, collection.getCopyrightText(), null); - collectionService.setMetadataSingleValue(context, dspaceCollection, - MD_INTRODUCTORY_TEXT, collection.getIntroductoryText(), null); - collectionService.setMetadataSingleValue(context, dspaceCollection, - MD_SHORT_DESCRIPTION, collection.getShortDescription(), null); - collectionService.setMetadataSingleValue(context, dspaceCollection, - MD_SIDEBAR_TEXT, collection.getSidebarText(), null); - collectionService.update(context, dspaceCollection); - communityService.update(context, dspaceCommunity); - retCollection = new Collection(dspaceCollection, servletContext, "", context, 100, 0); - context.complete(); - - } catch (SQLException e) { - processException( - "Could not add collection into community(id=" + communityId + "), SQLException. Message:" + e, - context); - } catch (AuthorizeException e) { - processException( - "Could not add collection into community(id=" + communityId + "), AuthorizeException. Message:" + e, - context); - } catch (ContextException e) { - processException( - "Could not add collection into community(id=" + communityId + "), ContextException. Message:" - + e.getMessage(), context); - } finally { - processFinally(context); - } - - - log.info("Collection was successfully added into community(id=" + communityId + "). Collection handle=" - + retCollection.getHandle()); - return retCollection; - } - - /** - * Create subcommunity in community. - * - * @param communityId Id of community in DSpace, in which will be created - * subcommunity. - * @param community Community which will be added into community. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the community as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @return Return response 200 if was everything all right. Otherwise 400 - * when id of community was incorrect or 401 if was problem with - * permission to write into collection. - * @throws WebApplicationException It is thrown when was problem with database reading or - * writing. Or problem with authorization to community. Or - * problem with creating context. - */ - @POST - @Path("/{community_id}/communities") - @Consumes( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public Community addCommunityCommunity(@PathParam("community_id") String communityId, Community community, - @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, - @Context HttpHeaders headers, @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Add subcommunity into community(id=" + communityId + ")."); - org.dspace.core.Context context = null; - Community retCommunity = null; - - try { - context = createContext(); - org.dspace.content.Community dspaceParentCommunity = findCommunity(context, communityId, - org.dspace.core.Constants.WRITE); - - writeStats(dspaceParentCommunity, UsageEvent.Action.UPDATE, user_ip, user_agent, xforwardedfor, - headers, request, context); - - org.dspace.content.Community dspaceCommunity = communityService - .createSubcommunity(context, dspaceParentCommunity); - communityService.setMetadataSingleValue(context, dspaceCommunity, - MD_NAME, community.getName(), null); - communityService.setMetadataSingleValue(context, dspaceCommunity, - MD_COPYRIGHT_TEXT, community.getCopyrightText(), null); - communityService.setMetadataSingleValue(context, dspaceCommunity, - MD_INTRODUCTORY_TEXT, community.getIntroductoryText(), null); - communityService.setMetadataSingleValue(context, dspaceCommunity, - MD_SHORT_DESCRIPTION, community.getShortDescription(), null); - communityService.setMetadataSingleValue(context, dspaceCommunity, - MD_SIDEBAR_TEXT, community.getSidebarText(), null); - communityService.update(context, dspaceCommunity); - communityService.update(context, dspaceParentCommunity); - - retCommunity = new Community(dspaceCommunity, servletContext, "", context); - context.complete(); - - } catch (SQLException e) { - processException( - "Could not add subcommunity into community(id=" + communityId + "), SQLException. Message:" + e, - context); - } catch (AuthorizeException e) { - processException( - "Could not add subcommunity into community(id=" + communityId + "), AuthorizeException. Message:" - + e, context); - } catch (ContextException e) { - processException( - "Could not add subcommunity into community(id=" + communityId + "), ContextException. Message:" + e, - context); - } finally { - processFinally(context); - } - - - log.info("Subcommunity was successfully added in community(id=" + communityId + ")."); - return retCommunity; - } - - /** - * Update community. Replace all information about community except: id, - * handle and expandle items. - * - * @param communityId Id of community in DSpace. - * @param community Instance of community which will replace actual community in - * DSpace. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the community as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @return Response 200 if was all ok. Otherwise 400 if id was incorrect or - * 401 if logged user has no permission to delete community. - * @throws WebApplicationException Thrown if there was a problem with creating context or problem - * with database reading or writing. Or problem with writing to - * community caused by authorization. - */ - @PUT - @Path("/{community_id}") - @Consumes( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public Response updateCommunity(@PathParam("community_id") String communityId, Community community, - @QueryParam("userIP") String user_ip, @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, @Context HttpHeaders headers, - @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Updating community(id=" + communityId + ")."); - org.dspace.core.Context context = null; - - try { - context = createContext(); - - org.dspace.content.Community dspaceCommunity = findCommunity(context, communityId, - org.dspace.core.Constants.WRITE); - writeStats(dspaceCommunity, UsageEvent.Action.UPDATE, user_ip, user_agent, xforwardedfor, - headers, request, context); - - // dspaceCommunity.setLogo(arg0); // TODO Add this option. - communityService.setMetadataSingleValue(context, dspaceCommunity, - MD_NAME, community.getName(), null); - communityService.setMetadataSingleValue(context, dspaceCommunity, - MD_COPYRIGHT_TEXT, community.getCopyrightText(), null); - communityService.setMetadataSingleValue(context, dspaceCommunity, - MD_INTRODUCTORY_TEXT, community.getIntroductoryText(), null); - communityService.setMetadataSingleValue(context, dspaceCommunity, - MD_SHORT_DESCRIPTION, community.getShortDescription(), null); - communityService.setMetadataSingleValue(context, dspaceCommunity, - MD_SIDEBAR_TEXT, community.getSidebarText(), null); - communityService.update(context, dspaceCommunity); - context.complete(); - - } catch (SQLException e) { - processException("Could not update community(id=" + communityId + "), AuthorizeException. Message:" + e, - context); - } catch (ContextException e) { - processException("Could not update community(id=" + communityId + "), ContextException Message:" + e, - context); - } catch (AuthorizeException e) { - processException("Could not update community(id=" + communityId + "), AuthorizeException Message:" + e, - context); - } finally { - processFinally(context); - } - - log.info("Community(id=" + communityId + ") has been successfully updated."); - return Response.ok().build(); - } - - /** - * Delete community from DSpace. It delete it everything with community! - * - * @param communityId Id of community in DSpace. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the community as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @return Return response code OK(200) if was everything all right. - * Otherwise return NOT_FOUND(404) if was id of community incorrect. - * Or (UNAUTHORIZED)401 if was problem with permission to community. - * @throws WebApplicationException Thrown if there was a problem with creating context or problem - * with database reading or deleting. Or problem with deleting - * community caused by IOException or authorization. - */ - @DELETE - @Path("/{community_id}") - public Response deleteCommunity(@PathParam("community_id") String communityId, @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, - @Context HttpHeaders headers, @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Deleting community(id=" + communityId + ")."); - org.dspace.core.Context context = null; - - try { - context = createContext(); - - org.dspace.content.Community community = findCommunity(context, communityId, - org.dspace.core.Constants.DELETE); - writeStats(community, UsageEvent.Action.DELETE, user_ip, user_agent, xforwardedfor, headers, - request, context); - - communityService.delete(context, community); - communityService.update(context, community); - context.complete(); - - } catch (SQLException e) { - processException("Could not delete community(id=" + communityId + "), SQLException. Message:" + e, context); - } catch (AuthorizeException e) { - processException("Could not delete community(id=" + communityId + "), AuthorizeException. Message:" + e, - context); - } catch (IOException e) { - processException("Could not delete community(id=" + communityId + "), IOException. Message:" + e, context); - } catch (ContextException e) { - processException( - "Could not delete community(id=" + communityId + "), ContextException. Message:" + e.getMessage(), - context); - } finally { - processFinally(context); - } - - - log.info("Community(id=" + communityId + ") was successfully deleted."); - return Response.status(Response.Status.OK).build(); - } - - /** - * Delete collection in community. - * - * @param communityId Id of community in DSpace. - * @param collectionId Id of collection which will be deleted. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the community as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @return Return response code OK(200) if was everything all right. - * Otherwise return NOT_FOUND(404) if was id of community or - * collection incorrect. Or (UNAUTHORIZED)401 if was problem with - * permission to community or collection. - * @throws WebApplicationException Thrown if there was a problem with creating context or problem - * with database reading or deleting. Or problem with deleting - * collection caused by IOException or authorization. - */ - @DELETE - @Path("/{community_id}/collections/{collection_id}") - public Response deleteCommunityCollection(@PathParam("community_id") String communityId, - @PathParam("collection_id") String collectionId, - @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, - @Context HttpHeaders headers, @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Deleting collection(id=" + collectionId + ") in community(id=" + communityId + ")."); - org.dspace.core.Context context = null; - - try { - context = createContext(); - - org.dspace.content.Community community = findCommunity(context, communityId, - org.dspace.core.Constants.WRITE); - org.dspace.content.Collection collection = collectionService.findByIdOrLegacyId(context, collectionId); - - if (collection == null) { - context.abort(); - log.warn("Collection(id=" + collectionId + ") was not found!"); - throw new WebApplicationException(Response.Status.NOT_FOUND); - } else if (!authorizeService - .authorizeActionBoolean(context, collection, org.dspace.core.Constants.REMOVE)) { - context.abort(); - if (context.getCurrentUser() != null) { - log.error( - "User(" + context.getCurrentUser().getEmail() + ") has not permission to delete collection!"); - } else { - log.error("User(anonymous) has not permission to delete collection!"); - } - throw new WebApplicationException(Response.Status.UNAUTHORIZED); - } - - communityService.removeCollection(context, community, collection); - communityService.update(context, community); - collectionService.update(context, collection); - - writeStats(community, UsageEvent.Action.UPDATE, user_ip, user_agent, xforwardedfor, headers, - request, context); - writeStats(collection, UsageEvent.Action.DELETE, user_ip, user_agent, xforwardedfor, headers, - request, context); - - context.complete(); - - } catch (SQLException e) { - processException("Could not delete collection(id=" + collectionId + ") in community(id=" + communityId - + "), SQLException. Message:" + e, context); - } catch (AuthorizeException e) { - processException("Could not delete collection(id=" + collectionId + ") in community(id=" + communityId - + "), AuthorizeException. Message:" + e, context); - } catch (IOException e) { - processException("Could not delete collection(id=" + collectionId + ") in community(id=" + communityId - + "), IOException. Message:" + e, context); - } catch (ContextException e) { - processException("Could not delete collection(id=" + collectionId + ") in community(id=" + communityId - + "), ContextExcpetion. Message:" + e.getMessage(), context); - } finally { - processFinally(context); - } - - - log.info("Collection(id=" + collectionId + ") in community(id=" + communityId + ") was successfully deleted."); - return Response.status(Response.Status.OK).build(); - } - - /** - * Delete subcommunity in community. - * - * @param parentCommunityId Id of community in DSpace. - * @param subcommunityId Id of community which will be deleted. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the community as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @return Return response code OK(200) if was everything all right. - * Otherwise return NOT_FOUND(404) if was id of community or - * subcommunity incorrect. Or (UNAUTHORIZED)401 if was problem with - * permission to community or subcommunity. - * @throws WebApplicationException Thrown if there was a problem with creating context or problem - * with database reading or deleting. Or problem with deleting - * subcommunity caused by IOException or authorization. - */ - @DELETE - @Path("/{community_id}/communities/{community_id2}") - public Response deleteCommunityCommunity(@PathParam("community_id") String parentCommunityId, - @PathParam("community_id2") String subcommunityId, - @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, - @Context HttpHeaders headers, @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Deleting community(id=" + parentCommunityId + ")."); - org.dspace.core.Context context = null; - - try { - context = createContext(); - - org.dspace.content.Community parentCommunity = findCommunity(context, parentCommunityId, - org.dspace.core.Constants.WRITE); - org.dspace.content.Community subcommunity = communityService.findByIdOrLegacyId(context, subcommunityId); - - if (subcommunity == null) { - context.abort(); - log.warn("Subcommunity(id=" + subcommunityId + ") in community(id=" + ") was not found!"); - throw new WebApplicationException(Response.Status.NOT_FOUND); - } else if (!authorizeService - .authorizeActionBoolean(context, subcommunity, org.dspace.core.Constants.REMOVE)) { - context.abort(); - if (context.getCurrentUser() != null) { - log.error( - "User(" + context.getCurrentUser().getEmail() + ") has not permission to delete community!"); - } else { - log.error("User(anonymous) has not permission to delete community!"); - } - throw new WebApplicationException(Response.Status.UNAUTHORIZED); - } - - communityService.removeSubcommunity(context, parentCommunity, subcommunity); - communityService.update(context, parentCommunity); - communityService.update(context, subcommunity); - - writeStats(parentCommunity, UsageEvent.Action.UPDATE, user_ip, user_agent, xforwardedfor, - headers, request, context); - writeStats(subcommunity, UsageEvent.Action.DELETE, user_ip, user_agent, xforwardedfor, headers, - request, context); - - context.complete(); - - } catch (SQLException e) { - processException( - "Could not delete subcommunity(id=" + subcommunityId + ") in community(id=" + parentCommunityId - + "), SQLException. Message:" + e, context); - } catch (AuthorizeException e) { - processException( - "Could not delete subcommunity(id=" + subcommunityId + ") in community(id=" + parentCommunityId - + "), AuthorizeException. Message:" + e, context); - } catch (IOException e) { - processException( - "Could not delete subcommunity(id=" + subcommunityId + ") in community(id=" + parentCommunityId - + "), IOException. Message:" + e, context); - } catch (ContextException e) { - processException( - "Could not delete subcommunity(id=" + subcommunityId + ") in community(id=" + parentCommunityId - + "), ContextException. Message:" + e.getMessage(), context); - } finally { - processFinally(context); - } - - - log.info("Subcommunity(id=" + subcommunityId + ") from community(id=" + parentCommunityId - + ") was successfully deleted."); - return Response.status(Response.Status.OK).build(); - } - - /** - * Find community from DSpace database. It is encapsulation of method - * org.dspace.content.Community.find with checking if item exist and if user - * logged into context has permission to do passed action. - * - * @param context Context of actual logged user. - * @param id Id of community in DSpace. - * @param action Constant from org.dspace.core.Constants. - * @return It returns DSpace collection. - * @throws WebApplicationException Is thrown when item with passed id is not exists and if user - * has no permission to do passed action. - */ - private org.dspace.content.Community findCommunity(org.dspace.core.Context context, String id, int action) - throws WebApplicationException { - org.dspace.content.Community community = null; - try { - community = communityService.findByIdOrLegacyId(context, id); - - if (community == null) { - context.abort(); - log.warn("Community(id=" + id + ") was not found!"); - throw new WebApplicationException(Response.Status.NOT_FOUND); - } else if (!authorizeService.authorizeActionBoolean(context, community, action)) { - context.abort(); - if (context.getCurrentUser() != null) { - log.error("User(" + context.getCurrentUser().getEmail() + ") has not permission to " - + getActionString(action) + " community!"); - } else { - log.error("User(anonymous) has not permission to " + getActionString(action) + " community!"); - } - throw new WebApplicationException(Response.Status.UNAUTHORIZED); - } - - } catch (SQLException e) { - processException("Something get wrong while finding community(id=" + id + "). SQLException, Message:" + e, - context); - } - return community; - } -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/DSpaceRestApplication.java b/dspace-rest/src/main/java/org/dspace/rest/DSpaceRestApplication.java deleted file mode 100644 index baa5c8555b..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/DSpaceRestApplication.java +++ /dev/null @@ -1,19 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.rest; - -import org.glassfish.jersey.jackson.JacksonFeature; -import org.glassfish.jersey.server.ResourceConfig; - -public class DSpaceRestApplication extends ResourceConfig { - - public DSpaceRestApplication() { - register(JacksonFeature.class); - packages("org.dspace.rest"); - } -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/FilteredCollectionsResource.java b/dspace-rest/src/main/java/org/dspace/rest/FilteredCollectionsResource.java deleted file mode 100644 index 133ed50d9c..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/FilteredCollectionsResource.java +++ /dev/null @@ -1,215 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.rest; - -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.List; -import javax.servlet.ServletContext; -import javax.servlet.http.HttpServletRequest; -import javax.ws.rs.DefaultValue; -import javax.ws.rs.GET; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; - -import org.apache.logging.log4j.Logger; -import org.dspace.authorize.factory.AuthorizeServiceFactory; -import org.dspace.authorize.service.AuthorizeService; -import org.dspace.content.factory.ContentServiceFactory; -import org.dspace.content.service.CollectionService; -import org.dspace.rest.common.FilteredCollection; -import org.dspace.rest.exceptions.ContextException; -import org.dspace.services.ConfigurationService; -import org.dspace.services.factory.DSpaceServicesFactory; -import org.dspace.usage.UsageEvent; - -/* - * This class provides the items within a collection evaluated against a set of Item Filters. - * - * @author Terry Brady, Georgetown University - */ -@Path("/filtered-collections") -public class FilteredCollectionsResource extends Resource { - protected AuthorizeService authorizeService = AuthorizeServiceFactory.getInstance().getAuthorizeService(); - protected CollectionService collectionService = ContentServiceFactory.getInstance().getCollectionService(); - protected ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); - private static Logger log = org.apache.logging.log4j.LogManager.getLogger(FilteredCollectionsResource.class); - - /** - * Return array of all collections in DSpace. You can add more properties - * through expand parameter. - * - * @param expand String in which is what you want to add to returned instance - * of collection. Options are: "all", "parentCommunityList", - * "parentCommunity", "topCommunity", "items", "license" and "logo". - * If you want to use multiple options, it must be separated by commas. - * @param limit Limit value for items in list in collection. Default value is - * 100. - * @param offset Offset of start index in list of items of collection. Default - * value is 0. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param filters Comma separated list of Item Filters to use to evaluate against - * the items in a collection - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param servletContext Context of the servlet container. - * @param headers If you want to access the collections as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @return Return array of collection, on which has logged user permission - * to view. - * @throws WebApplicationException It is thrown when was problem with database reading - * (SQLException) or problem with creating - * context(ContextException). - */ - @GET - @Produces( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public org.dspace.rest.common.FilteredCollection[] getCollections(@QueryParam("expand") String expand, - @QueryParam("limit") @DefaultValue("100") - Integer limit, - @QueryParam("offset") @DefaultValue("0") - Integer offset, - @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("filters") @DefaultValue("is_item") - String filters, - @QueryParam("xforwardedfor") String xforwardedfor, - @Context ServletContext servletContext, - @Context HttpHeaders headers, - @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Reading all filtered collections.(offset=" + offset + ",limit=" + limit + ")"); - org.dspace.core.Context context = null; - List collections = new ArrayList(); - - try { - context = createContext(); - - if (!((limit != null) && (limit >= 0) && (offset != null) && (offset >= 0))) { - log.warn("Paging was badly set."); - limit = 100; - offset = 0; - } - - List dspaceCollections = collectionService.findAll(context, limit, offset); - for (org.dspace.content.Collection dspaceCollection : dspaceCollections) { - if (authorizeService - .authorizeActionBoolean(context, dspaceCollection, org.dspace.core.Constants.READ)) { - FilteredCollection collection = new org.dspace.rest.common.FilteredCollection(dspaceCollection, - servletContext, - filters, expand, - context, limit, - offset); - collections.add(collection); - writeStats(dspaceCollection, UsageEvent.Action.VIEW, user_ip, user_agent, - xforwardedfor, headers, request, context); - } - } - context.complete(); - } catch (SQLException e) { - processException("Something went wrong while reading collections from database. Message: " + e, context); - } catch (ContextException e) { - processException("Something went wrong while reading collections, ContextError. Message: " + e.getMessage(), - context); - } finally { - processFinally(context); - } - - log.trace("All collections were successfully read."); - return collections.toArray(new org.dspace.rest.common.FilteredCollection[0]); - } - - /** - * Return instance of collection with passed id. You can add more properties - * through expand parameter. - * - * @param collection_id Id of collection in DSpace. - * @param expand String in which is what you want to add to returned instance - * of collection. Options are: "all", "parentCommunityList", - * "parentCommunity", "topCommunity", "items", "license" and "logo". - * If you want to use multiple options, it must be separated by commas. - * @param limit Limit value for items in list in collection. Default value is - * 100. - * @param offset Offset of start index in list of items of collection. Default - * value is 0. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param filters Comma separated list of Item Filters to use to evaluate against - * the items in a collection - * @param headers If you want to access the collection as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @param servletContext Context of the servlet container. - * @return Return instance of collection. It can also return status code - * NOT_FOUND(404) if id of collection is incorrect or status code - * UNATHORIZED(401) if user has no permission to read collection. - * @throws WebApplicationException It is thrown when was problem with database reading - * (SQLException) or problem with creating - * context(ContextException). It is thrown by NOT_FOUND and - * UNATHORIZED status codes, too. - */ - @GET - @Path("/{collection_id}") - @Produces( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public org.dspace.rest.common.FilteredCollection getCollection(@PathParam("collection_id") String collection_id, - @QueryParam("expand") String expand, - @QueryParam("limit") @DefaultValue("1000") Integer - limit, - @QueryParam("offset") @DefaultValue("0") Integer - offset, - @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, - @QueryParam("filters") @DefaultValue("is_item") - String filters, - @Context HttpHeaders headers, - @Context HttpServletRequest request, - @Context ServletContext servletContext) { - org.dspace.core.Context context = null; - FilteredCollection retColl = new org.dspace.rest.common.FilteredCollection(); - try { - context = createContext(); - - org.dspace.content.Collection collection = collectionService.findByIdOrLegacyId(context, collection_id); - if (authorizeService.authorizeActionBoolean(context, collection, org.dspace.core.Constants.READ)) { - writeStats(collection, UsageEvent.Action.VIEW, user_ip, - user_agent, xforwardedfor, headers, request, context); - retColl = new org.dspace.rest.common.FilteredCollection( - collection, servletContext, filters, expand, context, limit, offset); - } else { - throw new WebApplicationException(Response.Status.UNAUTHORIZED); - } - context.complete(); - } catch (SQLException e) { - processException(e.getMessage(), context); - } catch (ContextException e) { - processException(String.format("Could not read collection %s. %s", collection_id, e.getMessage()), - context); - } finally { - processFinally(context); - } - return retColl; - } -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/FilteredItemsResource.java b/dspace-rest/src/main/java/org/dspace/rest/FilteredItemsResource.java deleted file mode 100644 index 0f4331adc5..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/FilteredItemsResource.java +++ /dev/null @@ -1,217 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.rest; - -import java.io.IOException; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.UUID; -import javax.servlet.ServletContext; -import javax.servlet.http.HttpServletRequest; -import javax.ws.rs.DefaultValue; -import javax.ws.rs.GET; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.MediaType; - -import org.apache.logging.log4j.Logger; -import org.dspace.authorize.AuthorizeException; -import org.dspace.content.Item; -import org.dspace.content.MetadataField; -import org.dspace.content.factory.ContentServiceFactory; -import org.dspace.content.service.CollectionService; -import org.dspace.content.service.ItemService; -import org.dspace.content.service.MetadataFieldService; -import org.dspace.content.service.MetadataSchemaService; -import org.dspace.content.service.SiteService; -import org.dspace.rest.common.ItemFilter; -import org.dspace.rest.common.ItemFilterQuery; -import org.dspace.rest.exceptions.ContextException; -import org.dspace.rest.filter.ItemFilterSet; -import org.dspace.services.ConfigurationService; -import org.dspace.services.factory.DSpaceServicesFactory; -import org.dspace.usage.UsageEvent; - -/* - * This class retrieves items by a constructed metadata query evaluated against a set of Item Filters. - * - * @author Terry Brady, Georgetown University - */ -@Path("/filtered-items") -public class FilteredItemsResource extends Resource { - protected ItemService itemService = ContentServiceFactory.getInstance().getItemService(); - protected MetadataFieldService metadataFieldService = ContentServiceFactory.getInstance().getMetadataFieldService(); - protected MetadataSchemaService metadataSchemaService = ContentServiceFactory.getInstance() - .getMetadataSchemaService(); - protected CollectionService collectionService = ContentServiceFactory.getInstance().getCollectionService(); - protected SiteService siteService = ContentServiceFactory.getInstance().getSiteService(); - protected ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); - - private static Logger log = org.apache.logging.log4j.LogManager.getLogger(FilteredItemsResource.class); - - /** - * Return instance of collection with passed id. You can add more properties - * through expand parameter. - * - * @param expand String in which is what you want to add to returned instance - * of collection. Options are: "all", "parentCommunityList", - * "parentCommunity", "items", "license" and "logo". If you want - * to use multiple options, it must be separated by commas. - * @param limit Limit value for items in list in collection. Default value is - * 100. - * @param offset Offset of start index in list of items of collection. Default - * value is 0. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param filters Comma separated list of Item Filters to use to evaluate against - * the items in a collection - * @param query_field List of metadata fields to evaluate in a metadata query. - * Each list value is used in conjunction with a query_op and query_field. - * @param query_op List of metadata operators to use in a metadata query. - * Each list value is used in conjunction with a query_field and query_field. - * @param query_val List of metadata values to evaluate in a metadata query. - * Each list value is used in conjunction with a query_value and query_op. - * @param collSel List of collections to query. - * @param headers If you want to access the collection as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @param servletContext Context of the servlet container. - * @return Return instance of collection. It can also return status code - * NOT_FOUND(404) if id of collection is incorrect or status code - * UNATHORIZED(401) if user has no permission to read collection. - * @throws WebApplicationException It is thrown when was problem with database reading - * (SQLException) or problem with creating - * context(ContextException). It is thrown by NOT_FOUND and - * UNATHORIZED status codes, too. - */ - @GET - @Produces( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public org.dspace.rest.common.ItemFilter getItemQuery(@QueryParam("expand") String expand, - @QueryParam("limit") @DefaultValue("100") Integer limit, - @QueryParam("offset") @DefaultValue("0") Integer offset, - @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, - @QueryParam("filters") @DefaultValue("is_item,all_filters") - String filters, - @QueryParam("query_field[]") @DefaultValue("dc.title") - List query_field, - @QueryParam("query_op[]") @DefaultValue("exists") - List query_op, - @QueryParam("query_val[]") @DefaultValue("") List - query_val, - @QueryParam("collSel[]") @DefaultValue("") List - collSel, - @Context HttpHeaders headers, - @Context HttpServletRequest request, - @Context ServletContext servletContext) { - org.dspace.core.Context context = null; - ItemFilterSet itemFilterSet = new ItemFilterSet(filters, true); - ItemFilter result = itemFilterSet.getAllFiltersFilter(); - try { - context = createContext(); - - int index = Math.min(query_field.size(), Math.min(query_op.size(), query_val.size())); - List itemFilterQueries = new ArrayList(); - for (int i = 0; i < index; i++) { - itemFilterQueries.add(new ItemFilterQuery(query_field.get(i), query_op.get(i), query_val.get(i))); - } - - String regexClause = configurationService.getProperty("rest.regex-clause"); - if (regexClause == null) { - regexClause = ""; - } - - List uuids = getUuidsFromStrings(collSel); - List> listFieldList = getMetadataFieldsList(context, query_field); - - Iterator childItems = itemService - .findByMetadataQuery(context, listFieldList, query_op, query_val, uuids, regexClause, offset, limit); - - int count = itemFilterSet.processSaveItems(context, servletContext, childItems, true, expand); - writeStats(siteService.findSite(context), UsageEvent.Action.VIEW, user_ip, user_agent, xforwardedfor, - headers, request, context); - result.annotateQuery(query_field, query_op, query_val); - result.setUnfilteredItemCount(count); - context.complete(); - } catch (IOException e) { - processException(e.getMessage(), context); - } catch (SQLException e) { - processException(e.getMessage(), context); - } catch (AuthorizeException e) { - processException(e.getMessage(), context); - } catch (ContextException e) { - processException("Unauthorized filtered item query. " + e.getMessage(), context); - } finally { - processFinally(context); - } - return result; - } - - private List> getMetadataFieldsList(org.dspace.core.Context context, List query_field) - throws SQLException { - List> listFieldList = new ArrayList>(); - for (String s : query_field) { - ArrayList fields = new ArrayList(); - listFieldList.add(fields); - if (s.equals("*")) { - continue; - } - String schema = ""; - String element = ""; - String qualifier = null; - String[] parts = s.split("\\."); - if (parts.length > 0) { - schema = parts[0]; - } - if (parts.length > 1) { - element = parts[1]; - } - if (parts.length > 2) { - qualifier = parts[2]; - } - - if (Item.ANY.equals(qualifier)) { - for (MetadataField mf : metadataFieldService - .findFieldsByElementNameUnqualified(context, schema, element)) { - fields.add(mf); - } - } else { - MetadataField mf = metadataFieldService.findByElement(context, schema, element, qualifier); - if (mf != null) { - fields.add(mf); - } - } - } - return listFieldList; - } - - private List getUuidsFromStrings(List collSel) { - List uuids = new ArrayList(); - for (String s : collSel) { - try { - uuids.add(UUID.fromString(s)); - } catch (IllegalArgumentException e) { - log.warn("Invalid collection UUID: " + s); - } - } - return uuids; - } - -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/FiltersResource.java b/dspace-rest/src/main/java/org/dspace/rest/FiltersResource.java deleted file mode 100644 index bff755f2de..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/FiltersResource.java +++ /dev/null @@ -1,60 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.rest; - -import javax.servlet.http.HttpServletRequest; -import javax.ws.rs.GET; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.MediaType; - -import org.apache.logging.log4j.Logger; -import org.dspace.rest.common.ItemFilter; - -/** - * Class which provides read methods over the metadata registry. - * - * @author Terry Brady, Georgetown University - */ -@Path("/filters") -public class FiltersResource { - private static Logger log = org.apache.logging.log4j.LogManager.getLogger(FiltersResource.class); - - /** - * Return all Use Case Item Filters in DSpace. - * - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the collection as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @return Return array of metadata schemas. - * @throws WebApplicationException It can be caused by creating context or while was problem - * with reading community from database(SQLException). - */ - @GET - @Produces( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public ItemFilter[] getFilters(@QueryParam("userIP") String user_ip, @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, @Context HttpHeaders headers, - @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Reading all Item Filters."); - return ItemFilter.getItemFilters(ItemFilter.ALL, false).toArray(new ItemFilter[0]); - } - -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/HandleResource.java b/dspace-rest/src/main/java/org/dspace/rest/HandleResource.java deleted file mode 100644 index 51436a1c00..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/HandleResource.java +++ /dev/null @@ -1,109 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.rest; - -import java.sql.SQLException; -import javax.ws.rs.GET; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; - -import org.apache.logging.log4j.Logger; -import org.dspace.authorize.factory.AuthorizeServiceFactory; -import org.dspace.authorize.service.AuthorizeService; -import org.dspace.content.factory.ContentServiceFactory; -import org.dspace.content.service.DSpaceObjectService; -import org.dspace.core.Constants; -import org.dspace.handle.factory.HandleServiceFactory; -import org.dspace.handle.service.HandleService; -import org.dspace.rest.common.Collection; -import org.dspace.rest.common.Community; -import org.dspace.rest.common.DSpaceObject; -import org.dspace.rest.common.Item; -import org.dspace.rest.exceptions.ContextException; - -/** - * Created with IntelliJ IDEA. - * User: peterdietz - * Date: 10/7/13 - * Time: 1:54 PM - * To change this template use File | Settings | File Templates. - */ -@Path("/handle") -public class HandleResource extends Resource { - protected HandleService handleService = HandleServiceFactory.getInstance().getHandleService(); - protected AuthorizeService authorizeService = AuthorizeServiceFactory.getInstance().getAuthorizeService(); - - private static Logger log = org.apache.logging.log4j.LogManager.getLogger(HandleResource.class); - - @GET - @Path("/{prefix}/{suffix}") - @Produces( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public org.dspace.rest.common.DSpaceObject getObject(@PathParam("prefix") String prefix, - @PathParam("suffix") String suffix, - @QueryParam("expand") String expand, - @javax.ws.rs.core.Context HttpHeaders headers) { - DSpaceObject dSpaceObject = new DSpaceObject(); - org.dspace.core.Context context = null; - - try { - context = createContext(); - - org.dspace.content.DSpaceObject dso = handleService.resolveToObject(context, prefix + "/" + suffix); - - if (dso == null) { - throw new WebApplicationException(Response.Status.NOT_FOUND); - } - DSpaceObjectService dSpaceObjectService = ContentServiceFactory.getInstance().getDSpaceObjectService(dso); - log.info("DSO Lookup by handle: [" + prefix + "] / [" + suffix + "] got result of: " + dSpaceObjectService - .getTypeText(dso) + "_" + dso.getID()); - - if (authorizeService.authorizeActionBoolean(context, dso, org.dspace.core.Constants.READ)) { - switch (dso.getType()) { - case Constants.COMMUNITY: - dSpaceObject = new Community((org.dspace.content.Community) dso, servletContext, expand, - context); - break; - case Constants.COLLECTION: - dSpaceObject = new Collection((org.dspace.content.Collection) dso, servletContext, expand, - context, null, null); - break; - case Constants.ITEM: - dSpaceObject = new Item((org.dspace.content.Item) dso, servletContext, expand, context); - break; - default: - dSpaceObject = new DSpaceObject(dso, servletContext); - break; - } - } else { - throw new WebApplicationException(Response.Status.UNAUTHORIZED); - } - - context.complete(); - - } catch (SQLException e) { - log.error(e.getMessage()); - throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR); - } catch (ContextException e) { - processException( - "Could not read handle(prefix=" + prefix + "), (suffix=" + suffix + ") ContextException. Message:" + e - .getMessage(), - context); - } finally { - processFinally(context); - } - - return dSpaceObject; - - } -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/HierarchyResource.java b/dspace-rest/src/main/java/org/dspace/rest/HierarchyResource.java deleted file mode 100644 index b2ffc559b0..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/HierarchyResource.java +++ /dev/null @@ -1,140 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.rest; - -import java.io.UnsupportedEncodingException; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.List; -import javax.servlet.http.HttpServletRequest; -import javax.ws.rs.GET; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.MediaType; - -import org.apache.logging.log4j.Logger; -import org.dspace.authorize.factory.AuthorizeServiceFactory; -import org.dspace.authorize.service.AuthorizeService; -import org.dspace.content.Collection; -import org.dspace.content.Community; -import org.dspace.content.Site; -import org.dspace.content.factory.ContentServiceFactory; -import org.dspace.content.service.CommunityService; -import org.dspace.content.service.SiteService; -import org.dspace.rest.common.HierarchyCollection; -import org.dspace.rest.common.HierarchyCommunity; -import org.dspace.rest.common.HierarchySite; -import org.dspace.services.ConfigurationService; -import org.dspace.services.factory.DSpaceServicesFactory; - - -/* - * This class retrieves the community hierarchy in an optimized format. - * - * @author Terry Brady, Georgetown University - */ -@Path("/hierarchy") -@Produces( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) -public class HierarchyResource extends Resource { - private static Logger log = org.apache.logging.log4j.LogManager.getLogger(HierarchyResource.class); - protected SiteService siteService = ContentServiceFactory.getInstance().getSiteService(); - protected CommunityService communityService = ContentServiceFactory.getInstance().getCommunityService(); - protected AuthorizeService authorizeService = AuthorizeServiceFactory.getInstance().getAuthorizeService(); - protected ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); - - /** - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the collection as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @return Return instance of collection. It can also return status code - * NOT_FOUND(404) if id of collection is incorrect or status code - * @throws UnsupportedEncodingException The Character Encoding is not supported. - * @throws WebApplicationException It is thrown when was problem with database reading - * (SQLException) or problem with creating - * context(ContextException). It is thrown by NOT_FOUND and - * UNATHORIZED status codes, too. - */ - @GET - @Produces( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public HierarchySite getHierarchy( - @QueryParam("userAgent") String user_agent, @QueryParam("xforwardedfor") String xforwardedfor, - @Context HttpHeaders headers, @Context HttpServletRequest request) - throws UnsupportedEncodingException, WebApplicationException { - - org.dspace.core.Context context = null; - HierarchySite repo = new HierarchySite(); - - try { - context = createContext(); - - Site site = siteService.findSite(context); - repo.setId(site.getID().toString()); - repo.setName(site.getName()); - repo.setHandle(site.getHandle()); - List dspaceCommunities = communityService.findAllTop(context); - processCommunity(context, repo, dspaceCommunities); - } catch (Exception e) { - processException(e.getMessage(), context); - } finally { - if (context != null) { - try { - context.complete(); - } catch (SQLException e) { - log.error(e.getMessage() + " occurred while trying to close"); - } - } - } - return repo; - } - - - private void processCommunity(org.dspace.core.Context context, HierarchyCommunity parent, - List communities) throws SQLException { - if (communities == null) { - return; - } - if (communities.size() == 0) { - return; - } - List parentComms = new ArrayList(); - parent.setCommunities(parentComms); - for (Community comm : communities) { - if (!authorizeService.authorizeActionBoolean(context, comm, org.dspace.core.Constants.READ)) { - continue; - } - HierarchyCommunity mycomm = new HierarchyCommunity(comm.getID().toString(), comm.getName(), - comm.getHandle()); - parentComms.add(mycomm); - List colls = comm.getCollections(); - if (colls.size() > 0) { - List myColls = new ArrayList(); - mycomm.setCollections(myColls); - for (Collection coll : colls) { - if (!authorizeService.authorizeActionBoolean(context, coll, org.dspace.core.Constants.READ)) { - continue; - } - HierarchyCollection mycoll = new HierarchyCollection(coll.getID().toString(), coll.getName(), - coll.getHandle()); - myColls.add(mycoll); - } - } - processCommunity(context, mycomm, comm.getSubcommunities()); - } - - } -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/ItemsResource.java b/dspace-rest/src/main/java/org/dspace/rest/ItemsResource.java deleted file mode 100644 index 761d4dfc8a..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/ItemsResource.java +++ /dev/null @@ -1,1008 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.rest; - -import java.io.IOException; -import java.io.InputStream; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Date; -import java.util.Iterator; -import java.util.List; -import javax.servlet.http.HttpServletRequest; -import javax.ws.rs.Consumes; -import javax.ws.rs.DELETE; -import javax.ws.rs.DefaultValue; -import javax.ws.rs.GET; -import javax.ws.rs.POST; -import javax.ws.rs.PUT; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.Response.Status; - -import org.apache.logging.log4j.Logger; -import org.dspace.authorize.AuthorizeException; -import org.dspace.authorize.factory.AuthorizeServiceFactory; -import org.dspace.authorize.service.AuthorizeService; -import org.dspace.authorize.service.ResourcePolicyService; -import org.dspace.content.Bundle; -import org.dspace.content.factory.ContentServiceFactory; -import org.dspace.content.service.BitstreamFormatService; -import org.dspace.content.service.BitstreamService; -import org.dspace.content.service.BundleService; -import org.dspace.content.service.CollectionService; -import org.dspace.content.service.ItemService; -import org.dspace.eperson.factory.EPersonServiceFactory; -import org.dspace.eperson.service.GroupService; -import org.dspace.rest.common.Bitstream; -import org.dspace.rest.common.Item; -import org.dspace.rest.common.MetadataEntry; -import org.dspace.rest.exceptions.ContextException; -import org.dspace.usage.UsageEvent; - -/** - * Class which provide all CRUD methods over items. - * - * @author Rostislav Novak (Computing and Information Centre, CTU in Prague) - */ -// Every DSpace class used without namespace is from package org.dspace.rest.common.*. Otherwise namespace is defined. -@SuppressWarnings("deprecation") -@Path("/items") -public class ItemsResource extends Resource { - protected CollectionService collectionService = ContentServiceFactory.getInstance().getCollectionService(); - protected ItemService itemService = ContentServiceFactory.getInstance().getItemService(); - protected AuthorizeService authorizeService = AuthorizeServiceFactory.getInstance().getAuthorizeService(); - protected BitstreamService bitstreamService = ContentServiceFactory.getInstance().getBitstreamService(); - protected BitstreamFormatService bitstreamFormatService = ContentServiceFactory.getInstance() - .getBitstreamFormatService(); - protected BundleService bundleService = ContentServiceFactory.getInstance().getBundleService(); - protected ResourcePolicyService resourcePolicyService = AuthorizeServiceFactory.getInstance() - .getResourcePolicyService(); - protected GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); - - private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(ItemsResource.class); - - /** - * Return item properties without metadata and bitstreams. You can add - * additional properties by parameter expand. - * - * @param itemId Id of item in DSpace. - * @param expand String which define, what additional properties will be in - * returned item. Options are separeted by commas and are: "all", - * "metadata", "parentCollection", "parentCollectionList", - * "parentCommunityList" and "bitstreams". - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the item as the user logged into the context. - * The value of the "rest-dspace-token" header must be set with passed - * token from login method. - * @param request Servlet's HTTP request object. - * @return If user is allowed to read item, it returns item. Otherwise is - * thrown WebApplicationException with response status - * UNAUTHORIZED(401) or NOT_FOUND(404) if was id incorrect. - * @throws WebApplicationException This exception can be throw by NOT_FOUND(bad id of item), - * UNAUTHORIZED, SQLException if wasproblem with reading from - * database and ContextException, if there was problem with - * creating context of DSpace. - */ - @GET - @Path("/{item_id}") - @Produces( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public Item getItem(@PathParam("item_id") String itemId, @QueryParam("expand") String expand, - @QueryParam("userIP") String user_ip, @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, @Context HttpHeaders headers, - @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Reading item(id=" + itemId + ")."); - org.dspace.core.Context context = null; - Item item = null; - - try { - context = createContext(); - org.dspace.content.Item dspaceItem = findItem(context, itemId, org.dspace.core.Constants.READ); - - writeStats(dspaceItem, UsageEvent.Action.VIEW, user_ip, user_agent, xforwardedfor, headers, request, - context); - - item = new Item(dspaceItem, servletContext, expand, context); - context.complete(); - log.trace("Item(id=" + itemId + ") was successfully read."); - - } catch (SQLException e) { - processException("Could not read item(id=" + itemId + "), SQLException. Message: " + e, context); - } catch (ContextException e) { - processException("Could not read item(id=" + itemId + "), ContextException. Message: " + e.getMessage(), - context); - } finally { - processFinally(context); - } - - return item; - } - - /** - * It returns an array of items in DSpace. You can define how many items in - * list will be and from which index will start. Items in list are sorted by - * handle, not by id. - * - * @param expand String which define, what additional properties will be in - * returned item. Options are separeted by commas and are: "all", - * "metadata", "parentCollection", "parentCollectionList", - * "parentCommunityList" and "bitstreams". - * @param limit How many items in array will be. Default value is 100. - * @param offset On which index will array start. Default value is 0. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the item as the user logged into the context. - * The value of the "rest-dspace-token" header must be set with passed - * token from login method. - * @param request Servlet's HTTP request object. - * @return Return array of items, on which has logged user into context - * permission. - * @throws WebApplicationException It can be thrown by SQLException, when was problem with - * reading items from database or ContextException, when was - * problem with creating context of DSpace. - */ - @GET - @Produces( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public Item[] getItems(@QueryParam("expand") String expand, @QueryParam("limit") @DefaultValue("100") Integer limit, - @QueryParam("offset") @DefaultValue("0") Integer offset, - @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, - @Context HttpHeaders headers, @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Reading items.(offset=" + offset + ",limit=" + limit + ")."); - org.dspace.core.Context context = null; - List items = null; - - try { - context = createContext(); - - Iterator dspaceItems = itemService.findAllUnfiltered(context); - items = new ArrayList(); - - if (!((limit != null) && (limit >= 0) && (offset != null) && (offset >= 0))) { - log.warn("Paging was badly set, using default values."); - limit = 100; - offset = 0; - } - - for (int i = 0; (dspaceItems.hasNext()) && (i < (limit + offset)); i++) { - org.dspace.content.Item dspaceItem = dspaceItems.next(); - if (i >= offset) { - if (itemService.isItemListedForUser(context, dspaceItem)) { - items.add(new Item(dspaceItem, servletContext, expand, context)); - writeStats(dspaceItem, UsageEvent.Action.VIEW, user_ip, user_agent, xforwardedfor, - headers, request, context); - } - } - } - context.complete(); - } catch (SQLException e) { - processException("Something went wrong while reading items from database. Message: " + e, context); - } catch (ContextException e) { - processException("Something went wrong while reading items, ContextException. Message: " + e.getMessage(), - context); - } finally { - processFinally(context); - } - - log.trace("Items were successfully read."); - return items.toArray(new Item[0]); - } - - /** - * Returns item metadata in list. - * - * @param itemId Id of item in DSpace. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the item as the user logged into the context. - * The value of the "rest-dspace-token" header must be set with passed - * token from login method. - * @param request Servlet's HTTP request object. - * @return Return list of metadata fields if was everything ok. Otherwise it - * throw WebApplication exception with response code NOT_FOUND(404) - * or UNAUTHORIZED(401). - * @throws WebApplicationException It can be thrown by two exceptions: SQLException if was - * problem wtih reading item from database and ContextException, - * if was problem with creating context of DSpace. And can be - * thrown by NOT_FOUND and UNAUTHORIZED too. - */ - @GET - @Path("/{item_id}/metadata") - @Produces( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public MetadataEntry[] getItemMetadata(@PathParam("item_id") String itemId, @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, - @Context HttpHeaders headers, @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Reading item(id=" + itemId + ") metadata."); - org.dspace.core.Context context = null; - List metadata = null; - - try { - context = createContext(); - org.dspace.content.Item dspaceItem = findItem(context, itemId, org.dspace.core.Constants.READ); - - writeStats(dspaceItem, UsageEvent.Action.VIEW, user_ip, user_agent, xforwardedfor, headers, request, - context); - - metadata = new org.dspace.rest.common.Item(dspaceItem, servletContext, "metadata", context).getMetadata(); - context.complete(); - } catch (SQLException e) { - processException("Could not read item(id=" + itemId + "), SQLException. Message: " + e, context); - } catch (ContextException e) { - processException("Could not read item(id=" + itemId + "), ContextException. Message: " + e.getMessage(), - context); - } finally { - processFinally(context); - } - - log.trace("Item(id=" + itemId + ") metadata were successfully read."); - return metadata.toArray(new MetadataEntry[0]); - } - - /** - * Return array of bitstreams in item. It can be paged. - * - * @param itemId Id of item in DSpace. - * @param limit How many items will be in array. - * @param offset On which index will start array. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the item as the user logged into the context. - * The value of the "rest-dspace-token" header must be set with passed - * token from login method. - * @param request Servlet's HTTP request object. - * @return Return paged array of bitstreams in item. - * @throws WebApplicationException It can be throw by NOT_FOUND, UNAUTHORIZED, SQLException if - * was problem with reading from database and ContextException - * if was problem with creating context of DSpace. - */ - @GET - @Path("/{item_id}/bitstreams") - @Produces( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public Bitstream[] getItemBitstreams(@PathParam("item_id") String itemId, - @QueryParam("limit") @DefaultValue("20") Integer limit, - @QueryParam("offset") @DefaultValue("0") Integer offset, - @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, - @Context HttpHeaders headers, @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Reading item(id=" + itemId + ") bitstreams.(offset=" + offset + ",limit=" + limit + ")"); - org.dspace.core.Context context = null; - List bitstreams = null; - try { - context = createContext(); - org.dspace.content.Item dspaceItem = findItem(context, itemId, org.dspace.core.Constants.READ); - - writeStats(dspaceItem, UsageEvent.Action.VIEW, user_ip, user_agent, xforwardedfor, headers, request, - context); - - List itemBitstreams = new Item(dspaceItem, servletContext, "bitstreams", context) - .getBitstreams(); - - if ((offset + limit) > (itemBitstreams.size() - offset)) { - bitstreams = itemBitstreams.subList(offset, itemBitstreams.size()); - } else { - bitstreams = itemBitstreams.subList(offset, offset + limit); - } - context.complete(); - } catch (SQLException e) { - processException("Could not read item(id=" + itemId + ") bitstreams, SQLExcpetion. Message: " + e, context); - } catch (ContextException e) { - processException( - "Could not read item(id=" + itemId + ") bitstreams, ContextException. Message: " + e.getMessage(), - context); - } finally { - processFinally(context); - } - - log.trace("Item(id=" + itemId + ") bitstreams were successfully read."); - return bitstreams.toArray(new Bitstream[0]); - } - - /** - * Adding metadata fields to item. If metadata key is in item, it will be - * added, NOT REPLACED! - * - * @param itemId Id of item in DSpace. - * @param metadata List of metadata fields, which will be added into item. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the item as the user logged into the context. - * The value of the "rest-dspace-token" header must be set with passed - * token from login method. - * @param request Servlet's HTTP request object. - * @return It returns status code OK(200) if all was ok. UNAUTHORIZED(401) - * if user is not allowed to write to item. NOT_FOUND(404) if id of - * item is incorrect. - * @throws WebApplicationException It is throw by these exceptions: SQLException, if was problem - * with reading from database or writing to database. - * AuthorizeException, if was problem with authorization to item - * fields. ContextException, if was problem with creating - * context of DSpace. - */ - @POST - @Path("/{item_id}/metadata") - @Consumes( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public Response addItemMetadata(@PathParam("item_id") String itemId, - List metadata, - @QueryParam("userIP") String user_ip, @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, @Context HttpHeaders headers, - @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Adding metadata to item(id=" + itemId + ")."); - org.dspace.core.Context context = null; - - try { - context = createContext(); - org.dspace.content.Item dspaceItem = findItem(context, itemId, org.dspace.core.Constants.WRITE); - - writeStats(dspaceItem, UsageEvent.Action.UPDATE, user_ip, user_agent, xforwardedfor, headers, request, - context); - - for (MetadataEntry entry : metadata) { - // TODO Test with Java split - String data[] = mySplit(entry.getKey()); // Done by my split, because of java split was not function. - if ((data.length >= 2) && (data.length <= 3)) { - itemService.addMetadata(context, dspaceItem, data[0], data[1], data[2], entry.getLanguage(), - entry.getValue()); - } - } - context.complete(); - - } catch (SQLException e) { - processException("Could not write metadata to item(id=" + itemId + "), SQLException. Message: " + e, - context); - } catch (ContextException e) { - processException( - "Could not write metadata to item(id=" + itemId + "), ContextException. Message: " + e.getMessage(), - context); - } finally { - processFinally(context); - } - - log.info("Metadata to item(id=" + itemId + ") were successfully added."); - return Response.status(Status.OK).build(); - } - - /** - * Create bitstream in item. - * - * @param name Btstream name to set. - * @param description Btstream description to set. - * @param groupId ResourcePolicy group (allowed to READ). - * @param year ResourcePolicy start date year. - * @param month ResourcePolicy start date month. - * @param day ResourcePolicy start date day. - * @param itemId Id of item in DSpace. - * @param inputStream Data of bitstream in inputStream. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the item as the user logged into the context. - * The value of the "rest-dspace-token" header must be set with passed - * token from login method. - * @param request Servlet's HTTP request object. - * @return Returns bitstream with status code OK(200). If id of item is - * invalid , it returns status code NOT_FOUND(404). If user is not - * allowed to write to item, UNAUTHORIZED(401). - * @throws WebApplicationException It is thrown by these exceptions: SQLException, when was - * problem with reading/writing from/to database. - * AuthorizeException, when was problem with authorization to - * item and add bitstream to item. IOException, when was problem - * with creating file or reading from inpustream. - * ContextException. When was problem with creating context of - * DSpace. - */ - // TODO Add option to add bitstream by URI.(for very big files) - @POST - @Path("/{item_id}/bitstreams") - public Bitstream addItemBitstream(@PathParam("item_id") String itemId, InputStream inputStream, - @QueryParam("name") String name, @QueryParam("description") String description, - @QueryParam("groupId") String groupId, @QueryParam("year") Integer year, - @QueryParam("month") Integer month, - @QueryParam("day") Integer day, @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, @Context HttpHeaders headers, - @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Adding bitstream to item(id=" + itemId + ")."); - org.dspace.core.Context context = null; - Bitstream bitstream = null; - - try { - context = createContext(); - org.dspace.content.Item dspaceItem = findItem(context, itemId, org.dspace.core.Constants.WRITE); - - writeStats(dspaceItem, UsageEvent.Action.UPDATE, user_ip, user_agent, xforwardedfor, headers, request, - context); - - // Is better to add bitstream to ORIGINAL bundle or to item own? - log.trace("Creating bitstream in item."); - org.dspace.content.Bundle bundle = null; - org.dspace.content.Bitstream dspaceBitstream = null; - List bundles = itemService.getBundles(dspaceItem, org.dspace.core.Constants.CONTENT_BUNDLE_NAME); - - if (bundles != null && bundles.size() != 0) { - bundle = bundles.get(0); // There should be only one bundle ORIGINAL. - } - if (bundle == null) { - log.trace("Creating bundle in item."); - dspaceBitstream = itemService.createSingleBitstream(context, inputStream, dspaceItem); - } else { - log.trace("Getting bundle from item."); - dspaceBitstream = bitstreamService.create(context, bundle, inputStream); - } - - dspaceBitstream.setSource(context, "DSpace REST API"); - - // Set bitstream name and description - if (name != null) { - if (BitstreamResource.getMimeType(name) == null) { - dspaceBitstream.setFormat(context, bitstreamFormatService.findUnknown(context)); - } else { - bitstreamService.setFormat(context, dspaceBitstream, bitstreamFormatService - .findByMIMEType(context, BitstreamResource.getMimeType(name))); - } - - dspaceBitstream.setName(context, name); - } - if (description != null) { - dspaceBitstream.setDescription(context, description); - } - - // Create policy for bitstream - if (groupId != null) { - bundles = dspaceBitstream.getBundles(); - for (Bundle dspaceBundle : bundles) { - List bitstreamsPolicies = bundleService - .getBitstreamPolicies(context, dspaceBundle); - - // Remove default bitstream policies - List policiesToRemove = new ArrayList(); - for (org.dspace.authorize.ResourcePolicy policy : bitstreamsPolicies) { - if (policy.getdSpaceObject().getID().equals(dspaceBitstream.getID())) { - policiesToRemove.add(policy); - } - } - for (org.dspace.authorize.ResourcePolicy policy : policiesToRemove) { - bitstreamsPolicies.remove(policy); - } - - org.dspace.authorize.ResourcePolicy dspacePolicy = - resourcePolicyService.create(context, - null, groupService.findByIdOrLegacyId(context, groupId)); - dspacePolicy.setAction(org.dspace.core.Constants.READ); - dspacePolicy.setdSpaceObject(dspaceBitstream); - if ((year != null) || (month != null) || (day != null)) { - Date date = new Date(); - if (year != null) { - date.setYear(year - 1900); - } - if (month != null) { - date.setMonth(month - 1); - } - if (day != null) { - date.setDate(day); - } - date.setHours(0); - date.setMinutes(0); - date.setSeconds(0); - dspacePolicy.setStartDate(date); - } - - resourcePolicyService.update(context, dspacePolicy); - - bitstreamService.updateLastModified(context, dspaceBitstream); - } - } - - dspaceBitstream = bitstreamService.find(context, dspaceBitstream.getID()); - bitstream = new Bitstream(dspaceBitstream, servletContext, "", context); - - context.complete(); - - } catch (SQLException e) { - processException("Could not create bitstream in item(id=" + itemId + "), SQLException. Message: " + e, - context); - } catch (AuthorizeException e) { - processException("Could not create bitstream in item(id=" + itemId + "), AuthorizeException. Message: " + e, - context); - } catch (IOException e) { - processException("Could not create bitstream in item(id=" + itemId + "), IOException Message: " + e, - context); - } catch (ContextException e) { - processException( - "Could not create bitstream in item(id=" + itemId + "), ContextException Message: " + e.getMessage(), - context); - } finally { - processFinally(context); - } - - log.info("Bitstream(id=" + bitstream.getUUID() + ") was successfully added into item(id=" + itemId + ")."); - return bitstream; - } - - /** - * Replace all metadata in item with new passed metadata. - * - * @param itemId Id of item in DSpace. - * @param metadata List of metadata fields, which will replace old metadata in - * item. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the item as the user logged into the context. - * The value of the "rest-dspace-token" header must be set with passed - * token from login method. - * @param request Servlet's HTTP request object. - * @return It returns status code: OK(200). NOT_FOUND(404) if item was not - * found, UNAUTHORIZED(401) if user is not allowed to write to item. - * @throws WebApplicationException It is thrown by: SQLException, when was problem with database - * reading or writting, AuthorizeException when was problem with - * authorization to item and metadata fields. And - * ContextException, when was problem with creating context of - * DSpace. - */ - @PUT - @Path("/{item_id}/metadata") - @Consumes( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public Response updateItemMetadata(@PathParam("item_id") String itemId, MetadataEntry[] metadata, - @QueryParam("userIP") String user_ip, @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, @Context HttpHeaders headers, - @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Updating metadata in item(id=" + itemId + ")."); - org.dspace.core.Context context = null; - - try { - context = createContext(); - org.dspace.content.Item dspaceItem = findItem(context, itemId, org.dspace.core.Constants.WRITE); - - writeStats(dspaceItem, UsageEvent.Action.UPDATE, user_ip, user_agent, xforwardedfor, headers, request, - context); - - log.trace("Deleting original metadata from item."); - for (MetadataEntry entry : metadata) { - String data[] = mySplit(entry.getKey()); - if ((data.length >= 2) && (data.length <= 3)) { - itemService - .clearMetadata(context, dspaceItem, data[0], data[1], data[2], org.dspace.content.Item.ANY); - } - } - - log.trace("Adding new metadata to item."); - for (MetadataEntry entry : metadata) { - String data[] = mySplit(entry.getKey()); - if ((data.length >= 2) && (data.length <= 3)) { - itemService.addMetadata(context, dspaceItem, data[0], data[1], data[2], entry.getLanguage(), - entry.getValue()); - } - } - //Update the item to ensure that all the events get fired. - itemService.update(context, dspaceItem); - - context.complete(); - - } catch (SQLException e) { - processException("Could not update metadata in item(id=" + itemId + "), SQLException. Message: " + e, - context); - } catch (ContextException e) { - processException( - "Could not update metadata in item(id=" + itemId + "), ContextException. Message: " + e.getMessage(), - context); - } catch (AuthorizeException e) { - processException( - "Could not update metadata in item(id=" + itemId + "), AuthorizeException. Message: " + e.getMessage(), - context); - } finally { - processFinally(context); - } - - log.info("Metadata of item(id=" + itemId + ") were successfully updated."); - return Response.status(Status.OK).build(); - } - - /** - * Delete item from DSpace. It delete bitstreams only from item bundle. - * - * @param itemId Id of item which will be deleted. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the item as the user logged into the context. - * The value of the "rest-dspace-token" header must be set with passed - * token from login method. - * @param request Servlet's HTTP request object. - * @return It returns status code: OK(200). NOT_FOUND(404) if item was not - * found, UNAUTHORIZED(401) if user is not allowed to delete item - * metadata. - * @throws WebApplicationException It can be thrown by: SQLException, when was problem with - * database reading. AuthorizeException, when was problem with - * authorization to item.(read and delete) IOException, when was - * problem with deleting bitstream file. ContextException, when - * was problem with creating context of DSpace. - */ - @DELETE - @Path("/{item_id}") - public Response deleteItem(@PathParam("item_id") String itemId, @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, - @Context HttpHeaders headers, @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Deleting item(id=" + itemId + ")."); - org.dspace.core.Context context = null; - - try { - context = createContext(); - org.dspace.content.Item dspaceItem = findItem(context, itemId, org.dspace.core.Constants.DELETE); - - writeStats(dspaceItem, UsageEvent.Action.REMOVE, user_ip, user_agent, xforwardedfor, headers, request, - context); - - log.trace("Deleting item."); - itemService.delete(context, dspaceItem); - context.complete(); - - } catch (SQLException e) { - processException("Could not delete item(id=" + itemId + "), SQLException. Message: " + e, context); - } catch (AuthorizeException e) { - processException("Could not delete item(id=" + itemId + "), AuthorizeException. Message: " + e, context); - throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR); - } catch (IOException e) { - processException("Could not delete item(id=" + itemId + "), IOException. Message: " + e, context); - } catch (ContextException e) { - processException("Could not delete item(id=" + itemId + "), ContextException. Message: " + e.getMessage(), - context); - } finally { - processFinally(context); - } - - log.info("Item(id=" + itemId + ") was successfully deleted."); - return Response.status(Status.OK).build(); - } - - /** - * Delete all item metadata. - * - * @param itemId Id of item in DSpace. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the item as the user logged into the context. - * The value of the "rest-dspace-token" header must be set with passed - * token from login method. - * @param request Servlet's HTTP request object. - * @return It returns status code: OK(200). NOT_FOUND(404) if item was not - * found, UNAUTHORIZED(401) if user is not allowed to delete item - * metadata. - * @throws WebApplicationException Thrown by three exceptions. SQLException, when there was - * a problem reading item from database or editing metadata - * fields. AuthorizeException, when there was a problem with - * authorization to item. And ContextException, when there was a problem - * with creating a DSpace context. - */ - @DELETE - @Path("/{item_id}/metadata") - public Response deleteItemMetadata(@PathParam("item_id") String itemId, @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, - @Context HttpHeaders headers, @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Deleting metadata in item(id=" + itemId + ")."); - org.dspace.core.Context context = null; - - try { - context = createContext(); - org.dspace.content.Item dspaceItem = findItem(context, itemId, org.dspace.core.Constants.WRITE); - - writeStats(dspaceItem, UsageEvent.Action.UPDATE, user_ip, user_agent, xforwardedfor, headers, request, - context); - - log.trace("Deleting metadata."); - // TODO Rewrite without deprecated object. Leave there only generated metadata. - - String valueAccessioned = itemService - .getMetadataFirstValue(dspaceItem, "dc", "date", "accessioned", org.dspace.content.Item.ANY); - String valueAvailable = itemService - .getMetadataFirstValue(dspaceItem, "dc", "date", "available", org.dspace.content.Item.ANY); - String valueURI = itemService - .getMetadataFirstValue(dspaceItem, "dc", "identifier", "uri", org.dspace.content.Item.ANY); - String valueProvenance = itemService - .getMetadataFirstValue(dspaceItem, "dc", "description", "provenance", org.dspace.content.Item.ANY); - - itemService.clearMetadata(context, dspaceItem, org.dspace.content.Item.ANY, org.dspace.content.Item.ANY, - org.dspace.content.Item.ANY, - org.dspace.content.Item.ANY); - - // Add their generated metadata - itemService.addMetadata(context, dspaceItem, "dc", "date", "accessioned", null, valueAccessioned); - itemService.addMetadata(context, dspaceItem, "dc", "date", "available", null, valueAvailable); - itemService.addMetadata(context, dspaceItem, "dc", "identifier", "uri", null, valueURI); - itemService.addMetadata(context, dspaceItem, "dc", "description", "provenance", null, valueProvenance); - - context.complete(); - } catch (SQLException e) { - processException("Could not delete item(id=" + itemId + "), SQLException. Message: " + e, context); - } catch (ContextException e) { - processException("Could not delete item(id=" + itemId + "), ContextException. Message:" + e.getMessage(), - context); - } finally { - processFinally(context); - } - - log.info("Item(id=" + itemId + ") metadata were successfully deleted."); - return Response.status(Status.OK).build(); - } - - /** - * Delete bitstream from item bundle. - * - * @param itemId Id of item in DSpace. - * @param bitstreamId Id of bitstream, which will be deleted from bundle. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the item as the user logged into the context. - * The value of the "rest-dspace-token" header must be set with passed - * token from login method. - * @param request Servlet's HTTP request object. - * @return Return status code OK(200) if is all ok. NOT_FOUND(404) if item - * or bitstream was not found. UNAUTHORIZED(401) if user is not - * allowed to delete bitstream. - * @throws WebApplicationException It is thrown, when: Was problem with edditting database, - * SQLException. Or problem with authorization to item, bundle - * or bitstream, AuthorizeException. When was problem with - * deleting file IOException. Or problem with creating context - * of DSpace, ContextException. - */ - @DELETE - @Path("/{item_id}/bitstreams/{bitstream_id}") - public Response deleteItemBitstream(@PathParam("item_id") String itemId, - @PathParam("bitstream_id") String bitstreamId, - @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, @Context HttpHeaders headers, - @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Deleting bitstream in item(id=" + itemId + ")."); - org.dspace.core.Context context = null; - - try { - context = createContext(); - org.dspace.content.Item item = findItem(context, itemId, org.dspace.core.Constants.WRITE); - - org.dspace.content.Bitstream bitstream = bitstreamService.findByIdOrLegacyId(context, bitstreamId); - if (bitstream == null) { - context.abort(); - log.warn("Bitstream(id=" + bitstreamId + ") was not found."); - return Response.status(Status.NOT_FOUND).build(); - } else if (!authorizeService.authorizeActionBoolean(context, bitstream, org.dspace.core.Constants.DELETE)) { - context.abort(); - log.error("User(" + context.getCurrentUser() - .getEmail() + ") is not allowed to delete bitstream(id=" + bitstreamId + - ")."); - return Response.status(Status.UNAUTHORIZED).build(); - } - - writeStats(item, UsageEvent.Action.UPDATE, user_ip, user_agent, xforwardedfor, headers, request, context); - writeStats(bitstream, UsageEvent.Action.REMOVE, user_ip, user_agent, xforwardedfor, headers, - request, context); - - log.trace("Deleting bitstream..."); - bitstreamService.delete(context, bitstream); - - context.complete(); - - } catch (SQLException e) { - processException("Could not delete bitstream(id=" + bitstreamId + "), SQLException. Message: " + e, - context); - } catch (AuthorizeException e) { - processException("Could not delete bitstream(id=" + bitstreamId + "), AuthorizeException. Message: " + e, - context); - } catch (IOException e) { - processException("Could not delete bitstream(id=" + bitstreamId + "), IOException. Message: " + e, context); - } catch (ContextException e) { - processException( - "Could not delete bitstream(id=" + bitstreamId + "), ContextException. Message:" + e.getMessage(), - context); - } finally { - processFinally(context); - } - - log.info("Bitstream(id=" + bitstreamId + ") from item(id=" + itemId + ") was successfuly deleted ."); - return Response.status(Status.OK).build(); - } - - /** - * Find items by one metadata field. - * - * @param metadataEntry Metadata field to search by. - * @param expand String which define, what additional properties will be in - * returned item. Options are separeted by commas and are: "all", - * "metadata", "parentCollection", "parentCollectionList", - * "parentCommunityList" and "bitstreams". - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the item as the user logged into context, - * header "rest-dspace-token" must be set to token value retrieved - * from the login method. - * @param request Servlet's HTTP request object. - * @return Return array of found items. - * @throws WebApplicationException Can be thrown: SQLException - problem with - * database reading. AuthorizeException - problem with - * authorization to item. IOException - problem with - * reading from metadata field. ContextException - - * problem with creating DSpace context. - */ - @POST - @Path("/find-by-metadata-field") - @Produces( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public Item[] findItemsByMetadataField(MetadataEntry metadataEntry, @QueryParam("expand") String expand, - @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, - @Context HttpHeaders headers, @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Looking for item with metadata(key=" + metadataEntry.getKey() + ",value=" + metadataEntry.getValue() - + ", language=" + metadataEntry.getLanguage() + ")."); - org.dspace.core.Context context = null; - - List items = new ArrayList(); - String[] metadata = mySplit(metadataEntry.getKey()); - - // Must used own style. - if ((metadata.length < 2) || (metadata.length > 3)) { - log.error("Finding failed, bad metadata key."); - throw new WebApplicationException(Response.Status.NOT_FOUND); - } - - try { - context = createContext(); - - Iterator itemIterator = itemService - .findByMetadataField(context, metadataEntry.getSchema(), - metadataEntry.getElement(), metadataEntry.getQualifier(), - metadataEntry.getValue()); - - while (itemIterator.hasNext()) { - org.dspace.content.Item dspaceItem = itemIterator.next(); - //Only return items that are available for the current user - if (itemService.isItemListedForUser(context, dspaceItem)) { - Item item = new Item(dspaceItem, servletContext, expand, context); - writeStats(dspaceItem, UsageEvent.Action.VIEW, user_ip, user_agent, xforwardedfor, headers, - request, context); - items.add(item); - } - } - - context.complete(); - } catch (SQLException e) { - processException("Something went wrong while finding item. SQLException, Message: " + e, context); - } catch (ContextException e) { - processException("Context error:" + e.getMessage(), context); - } catch (AuthorizeException e) { - processException("Authorize error:" + e.getMessage(), context); - } catch (IOException e) { - processException("IO error:" + e.getMessage(), context); - } finally { - processFinally(context); - } - - if (items.size() == 0) { - log.info("Items not found."); - } else { - log.info("Items were found."); - } - - return items.toArray(new Item[0]); - } - - /** - * Find item from DSpace database. It is encapsulation of method - * org.dspace.content.Item.find with checking if item exist and if user - * logged into context has permission to do passed action. - * - * @param context Context of actual logged user. - * @param id Id of item in DSpace. - * @param action Constant from org.dspace.core.Constants. - * @return It returns DSpace item. - * @throws WebApplicationException Is thrown when item with passed id is not exists and if user - * has no permission to do passed action. - */ - private org.dspace.content.Item findItem(org.dspace.core.Context context, String id, int action) - throws WebApplicationException { - org.dspace.content.Item item = null; - try { - item = itemService.findByIdOrLegacyId(context, id); - - if (item == null) { - context.abort(); - log.warn("Item(id=" + id + ") was not found!"); - throw new WebApplicationException(Response.Status.NOT_FOUND); - } else if (!authorizeService.authorizeActionBoolean(context, item, action)) { - context.abort(); - if (context.getCurrentUser() != null) { - log.error("User(" + context.getCurrentUser().getEmail() + ") has not permission to " - + getActionString(action) + " item!"); - } else { - log.error("User(anonymous) has not permission to " + getActionString(action) + " item!"); - } - throw new WebApplicationException(Response.Status.UNAUTHORIZED); - } - - } catch (SQLException e) { - processException("Something get wrong while finding item(id=" + id + "). SQLException, Message: " + e, - context); - } - return item; - } -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/MetadataRegistryResource.java b/dspace-rest/src/main/java/org/dspace/rest/MetadataRegistryResource.java deleted file mode 100644 index 79e655e63d..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/MetadataRegistryResource.java +++ /dev/null @@ -1,738 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.rest; - -import java.io.IOException; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.List; -import javax.servlet.http.HttpServletRequest; -import javax.ws.rs.Consumes; -import javax.ws.rs.DELETE; -import javax.ws.rs.DefaultValue; -import javax.ws.rs.GET; -import javax.ws.rs.POST; -import javax.ws.rs.PUT; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; - -import org.apache.logging.log4j.Logger; -import org.dspace.authorize.AuthorizeException; -import org.dspace.authorize.factory.AuthorizeServiceFactory; -import org.dspace.authorize.service.AuthorizeService; -import org.dspace.content.NonUniqueMetadataException; -import org.dspace.content.factory.ContentServiceFactory; -import org.dspace.content.service.MetadataFieldService; -import org.dspace.content.service.MetadataSchemaService; -import org.dspace.content.service.SiteService; -import org.dspace.rest.common.MetadataField; -import org.dspace.rest.common.MetadataSchema; -import org.dspace.rest.exceptions.ContextException; -import org.dspace.usage.UsageEvent; - -/** - * Class which provides read methods over the metadata registry. - * - * @author Terry Brady, Georgetown University - * - * GET /registries/schema - Return the list of schemas in the registry - * GET /registries/schema/{schema_prefix} - Returns the specified schema - * GET /registries/schema/{schema_prefix}/metadata-fields/{element} - Returns the metadata field within a schema - * with an unqualified element name - * GET /registries/schema/{schema_prefix}/metadata-fields/{element}/{qualifier} - Returns the metadata field - * within a schema with a qualified element name - * POST /registries/schema/ - Add a schema to the schema registry - * POST /registries/schema/{schema_prefix}/metadata-fields - Add a metadata field to the specified schema - * GET /registries/metadata-fields/{field_id} - Return the specified metadata field - * PUT /registries/metadata-fields/{field_id} - Update the specified metadata field - * DELETE /registries/metadata-fields/{field_id} - Delete the specified metadata field from the metadata field registry - * DELETE /registries/schema/{schema_id} - Delete the specified schema from the schema registry - * - * Note: intentionally not providing since there is no date to update other than the namespace - * PUT /registries/schema/{schema_id} - */ -@Path("/registries") -public class MetadataRegistryResource extends Resource { - protected AuthorizeService authorizeService = AuthorizeServiceFactory.getInstance().getAuthorizeService(); - protected MetadataFieldService metadataFieldService = ContentServiceFactory.getInstance().getMetadataFieldService(); - protected MetadataSchemaService metadataSchemaService = ContentServiceFactory.getInstance() - .getMetadataSchemaService(); - protected SiteService siteService = ContentServiceFactory.getInstance().getSiteService(); - private static Logger log = org.apache.logging.log4j.LogManager.getLogger(MetadataRegistryResource.class); - - /** - * Return all metadata registry items in DSpace. - * - * @param expand String in which is what you want to add to returned instance - * of metadata schema. Options are: "all", "fields". Default value "fields". - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the metadata schema as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @return Return array of metadata schemas. - * @throws WebApplicationException It can be caused by creating context or while was problem - * with reading schema from database(SQLException). - */ - @GET - @Path("/schema") - @Produces( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public MetadataSchema[] getSchemas(@QueryParam("expand") @DefaultValue("fields") String expand, - @QueryParam("userIP") String user_ip, @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, @Context HttpHeaders headers, - @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Reading all metadata schemas."); - org.dspace.core.Context context = null; - ArrayList metadataSchemas = null; - - try { - context = createContext(); - - List schemas = metadataSchemaService.findAll(context); - metadataSchemas = new ArrayList(); - for (org.dspace.content.MetadataSchema schema : schemas) { - metadataSchemas.add(new MetadataSchema(schema, expand, context)); - } - - context.complete(); - } catch (SQLException e) { - processException("Could not read metadata schemas, SQLException. Message:" + e, context); - } catch (ContextException e) { - processException("Could not read metadata schemas, ContextException. Message:" + e.getMessage(), context); - } finally { - processFinally(context); - } - - log.trace("All metadata schemas successfully read."); - return metadataSchemas.toArray(new MetadataSchema[0]); - } - - /** - * Returns metadata schema with basic properties. If you want more, use expand - * parameter or method for metadata fields. - * - * @param schemaPrefix Prefix for schema in DSpace. - * @param expand String in which is what you want to add to returned instance - * of metadata schema. Options are: "all", "fields". Default value "fields". - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the metadata schema as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @return Return instance of org.dspace.rest.common.MetadataSchema. - * @throws WebApplicationException Thrown if there was a problem with creating context or problem - * with database reading. Also if id/prefix of schema is incorrect - * or logged user into context has no permission to read. - */ - @GET - @Path("/schema/{schema_prefix}") - @Produces( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public MetadataSchema getSchema(@PathParam("schema_prefix") String schemaPrefix, - @QueryParam("expand") @DefaultValue("fields") String expand, - @QueryParam("userIP") String user_ip, @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, @Context HttpHeaders headers, - @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Reading metadata schemas."); - org.dspace.core.Context context = null; - MetadataSchema metadataSchema = null; - - try { - context = createContext(); - - org.dspace.content.MetadataSchema schema = metadataSchemaService.find(context, schemaPrefix); - metadataSchema = new MetadataSchema(schema, expand, context); - if (schema == null) { - processException(String.format("Schema not found for index %s", schemaPrefix), context); - } - - context.complete(); - } catch (SQLException e) { - processException("Could not read metadata schema, SQLException. Message:" + e, context); - } catch (ContextException e) { - processException("Could not read metadata schema, ContextException. Message:" + e.getMessage(), context); - } finally { - processFinally(context); - } - - log.trace("Metadata schemas successfully read."); - return metadataSchema; - } - - /** - * Returns metadata field with basic properties. - * - * @param schemaPrefix Prefix for schema in DSpace. - * @param element Unqualified element name for field in the metadata registry. - * @param expand String in which is what you want to add to returned instance - * of the metadata field. Options are: "all", "parentSchema". Default value "". - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the community as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @return Return instance of org.dspace.rest.common.MetadataField. - * @throws WebApplicationException Thrown if there was a problem with creating context or problem - * with database reading. Also if id of field is incorrect - * or logged user into context has no permission to read. - */ - @GET - @Path("/schema/{schema_prefix}/metadata-fields/{element}") - @Produces( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public MetadataField getMetadataFieldUnqualified(@PathParam("schema_prefix") String schemaPrefix, - @PathParam("element") String element, - @QueryParam("expand") String expand, - @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, - @Context HttpHeaders headers, @Context HttpServletRequest request) - throws WebApplicationException { - return getMetadataFieldQualified(schemaPrefix, element, "", expand, user_ip, user_agent, xforwardedfor, headers, - request); - } - - /** - * Returns metadata field with basic properties. - * - * @param schemaPrefix Prefix for schema in DSpace. - * @param element Element name for field in the metadata registry. - * @param qualifier Element name qualifier for field in the metadata registry. - * @param expand String in which is what you want to add to returned instance - * of the metadata field. Options are: "all", "parentSchema". Default value "". - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the community as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @return Return instance of org.dspace.rest.common.MetadataField. - * @throws WebApplicationException Thrown if there was a problem with creating context or problem - * with database reading. Also if id of field is incorrect - * or logged user into context has no permission to read. - */ - @GET - @Path("/schema/{schema_prefix}/metadata-fields/{element}/{qualifier}") - @Produces( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public MetadataField getMetadataFieldQualified(@PathParam("schema_prefix") String schemaPrefix, - @PathParam("element") String element, - @PathParam("qualifier") @DefaultValue("") String qualifier, - @QueryParam("expand") String expand, - @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, - @Context HttpHeaders headers, @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Reading metadata field."); - org.dspace.core.Context context = null; - MetadataField metadataField = null; - - try { - context = createContext(); - - org.dspace.content.MetadataSchema schema = metadataSchemaService.find(context, schemaPrefix); - - if (schema == null) { - log.error(String.format("Schema not found for prefix %s", schemaPrefix)); - throw new WebApplicationException(Response.Status.NOT_FOUND); - } - - org.dspace.content.MetadataField field = metadataFieldService - .findByElement(context, schema, element, qualifier); - if (field == null) { - log.error(String.format("Field %s.%s.%s not found", schemaPrefix, element, qualifier)); - throw new WebApplicationException(Response.Status.NOT_FOUND); - } - metadataField = new MetadataField(schema, field, expand, context); - - context.complete(); - } catch (SQLException e) { - processException("Could not read metadata field, SQLException. Message:" + e, context); - } catch (ContextException e) { - processException("Could not read metadata field, ContextException. Message:" + e.getMessage(), context); - } finally { - processFinally(context); - } - - log.trace("Metadata field successfully read."); - return metadataField; - } - - /** - * Returns metadata field with basic properties. - * - * @param fieldId Id of metadata field in DSpace. - * @param expand String in which is what you want to add to returned instance - * of the metadata field. Options are: "all", "parentSchema". Default value "parentSchema". - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the community as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @return Return instance of org.dspace.rest.common.MetadataField. - * @throws WebApplicationException Thrown if there was a problem with creating context or problem - * with database reading. Also if id of field is incorrect - * or logged user into context has no permission to read. - */ - @GET - @Path("/metadata-fields/{field_id}") - @Produces( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public MetadataField getMetadataField(@PathParam("field_id") Integer fieldId, - @QueryParam("expand") @DefaultValue("parentSchema") String expand, - @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, - @Context HttpHeaders headers, @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Reading metadata field."); - org.dspace.core.Context context = null; - MetadataField metadataField = null; - - try { - context = createContext(); - - org.dspace.content.MetadataField field = metadataFieldService.find(context, fieldId); - if (field == null) { - log.error(String.format("Metadata Field %d not found", fieldId)); - throw new WebApplicationException(Response.Status.NOT_FOUND); - } - org.dspace.content.MetadataSchema schema = field.getMetadataSchema(); - if (schema == null) { - log.error(String.format("Parent Schema not found for Metadata Field %d not found", fieldId)); - throw new WebApplicationException(Response.Status.NOT_FOUND); - } - metadataField = new MetadataField(schema, field, expand, context); - - context.complete(); - } catch (SQLException e) { - processException("Could not read metadata field, SQLException. Message:" + e, context); - } catch (ContextException e) { - processException("Could not read metadata field, ContextException. Message:" + e.getMessage(), context); - } finally { - processFinally(context); - } - - log.trace("Metadata field successfully read."); - return metadataField; - } - - /** - * Create schema in the schema registry. Creating a schema is restricted to admin users. - * - * @param schema Schema that will be added to the metadata registry. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the schema as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @return Return response 200 if was everything all right. Otherwise 400 - * when id of community was incorrect or 401 if was problem with - * permission to write into collection. - * Returns the schema (schemaId), if was all ok. - * @throws WebApplicationException It can be thrown by SQLException, AuthorizeException and - * ContextException. - */ - @POST - @Path("/schema") - @Consumes( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public MetadataSchema createSchema(MetadataSchema schema, @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, - @Context HttpHeaders headers, @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Creating a schema."); - org.dspace.core.Context context = null; - MetadataSchema retSchema = null; - - try { - context = createContext(); - - if (!authorizeService.isAdmin(context)) { - context.abort(); - String user = "anonymous"; - if (context.getCurrentUser() != null) { - user = context.getCurrentUser().getEmail(); - } - log.error("User(" + user + ") does not have permission to create a metadata schema!"); - throw new WebApplicationException(Response.Status.UNAUTHORIZED); - } - - log.debug(String.format("Admin user creating schema with namespace %s and prefix %s", schema.getNamespace(), - schema.getPrefix())); - - org.dspace.content.MetadataSchema dspaceSchema = metadataSchemaService - .create(context, schema.getPrefix(), schema.getNamespace()); - log.debug("Creating return object."); - retSchema = new MetadataSchema(dspaceSchema, "", context); - - writeStats(siteService.findSite(context), UsageEvent.Action.CREATE, user_ip, user_agent, xforwardedfor, - headers, request, context); - - context.complete(); - log.info("Schema created" + retSchema.getPrefix()); - - } catch (SQLException e) { - processException("Could not create new metadata schema, SQLException. Message: " + e, context); - } catch (ContextException e) { - processException("Could not create new metadata schema, ContextException. Message: " + e.getMessage(), - context); - } catch (AuthorizeException e) { - processException("Could not create new metadata schema, AuthorizeException. Message: " + e.getMessage(), - context); - } catch (NonUniqueMetadataException e) { - processException( - "Could not create new metadata schema, NonUniqueMetadataException. Message: " + e.getMessage(), - context); - } catch (Exception e) { - processException("Could not create new metadata schema, Exception. Class: " + e.getClass(), context); - } finally { - processFinally(context); - } - - return retSchema; - } - - - /** - * Create a new metadata field within a schema. - * Creating a metadata field is restricted to admin users. - * - * @param schemaPrefix Prefix for schema in DSpace. - * @param field Field that will be added to the metadata registry for a schema. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the schema as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @return Return response 200 if was everything all right. Otherwise 400 - * when id of community was incorrect or 401 if was problem with - * permission to write into collection. - * Returns the field (with fieldId), if was all ok. - * @throws WebApplicationException It can be thrown by SQLException, AuthorizeException and - * ContextException. - */ - @POST - @Path("/schema/{schema_prefix}/metadata-fields") - @Consumes( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public MetadataField createMetadataField(@PathParam("schema_prefix") String schemaPrefix, - MetadataField field, @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, - @Context HttpHeaders headers, @Context HttpServletRequest request) - throws WebApplicationException { - - log.info(String.format("Creating metadataField within schema %s.", schemaPrefix)); - org.dspace.core.Context context = null; - MetadataField retField = null; - - try { - context = createContext(); - - if (!authorizeService.isAdmin(context)) { - context.abort(); - String user = "anonymous"; - if (context.getCurrentUser() != null) { - user = context.getCurrentUser().getEmail(); - } - log.error("User(" + user + ") does not have permission to create a metadata field!"); - throw new WebApplicationException(Response.Status.UNAUTHORIZED); - } - - org.dspace.content.MetadataSchema schema = metadataSchemaService.find(context, schemaPrefix); - if (schema == null) { - log.error(String.format("Schema not found for prefix %s", schemaPrefix)); - throw new WebApplicationException(Response.Status.NOT_FOUND); - } - org.dspace.content.MetadataField dspaceField = metadataFieldService - .create(context, schema, field.getElement(), field.getQualifier(), field.getDescription()); - writeStats(siteService.findSite(context), UsageEvent.Action.CREATE, user_ip, user_agent, xforwardedfor, - headers, request, context); - - retField = new MetadataField(schema, dspaceField, "", context); - context.complete(); - log.info("Metadata field created within schema" + retField.getName()); - } catch (SQLException e) { - processException("Could not create new metadata field, SQLException. Message: " + e, context); - } catch (ContextException e) { - processException("Could not create new metadata field, ContextException. Message: " + e.getMessage(), - context); - } catch (AuthorizeException e) { - processException("Could not create new metadata field, AuthorizeException. Message: " + e.getMessage(), - context); - } catch (NonUniqueMetadataException e) { - processException( - "Could not create new metadata field, NonUniqueMetadataException. Message: " + e.getMessage(), context); - } catch (Exception e) { - processException("Could not create new metadata field, Exception. Message: " + e.getMessage(), context); - } finally { - processFinally(context); - } - - return retField; - } - - //@PUT - //@Path("/schema/{schema_prefix}") - //Assumption - there are no meaningful fields to update for a schema - - /** - * Update metadata field. Replace all information about community except the id and the containing schema. - * - * @param fieldId Id of the field in the DSpace metdata registry. - * @param field Instance of the metadata field which will replace actual metadata field in - * DSpace. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the metadata field as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @return Response 200 if was all ok. Otherwise 400 if was id incorrect or - * 401 if logged user has no permission to update the metadata field. - * @throws WebApplicationException Thrown if there was a problem with creating context or problem - * with database reading or writing. Or problem with writing to - * community caused by authorization. - */ - @PUT - @Path("/metadata-fields/{field_id}") - @Consumes( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public Response updateMetadataField(@PathParam("field_id") Integer fieldId, MetadataField field, - @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, @Context HttpHeaders headers, - @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Updating metadata field(id=" + fieldId + ")."); - org.dspace.core.Context context = null; - - try { - context = createContext(); - - org.dspace.content.MetadataField dspaceField = metadataFieldService.find(context, fieldId); - if (field == null) { - log.error(String.format("Metadata Field %d not found", fieldId)); - throw new WebApplicationException(Response.Status.NOT_FOUND); - } - - writeStats(siteService.findSite(context), UsageEvent.Action.UPDATE, user_ip, user_agent, xforwardedfor, - headers, request, context); - - dspaceField.setElement(field.getElement()); - dspaceField.setQualifier(field.getQualifier()); - dspaceField.setScopeNote(field.getDescription()); - metadataFieldService.update(context, dspaceField); - - context.complete(); - - } catch (SQLException e) { - processException("Could not update metadata field(id=" + fieldId + "), AuthorizeException. Message:" + e, - context); - } catch (ContextException e) { - processException("Could not update metadata field(id=" + fieldId + "), ContextException Message:" + e, - context); - } catch (AuthorizeException e) { - processException("Could not update metadata field(id=" + fieldId + "), AuthorizeException. Message:" + e, - context); - } catch (NonUniqueMetadataException e) { - processException( - "Could not update metadata field(id=" + fieldId + "), NonUniqueMetadataException. Message:" + e, - context); - } catch (IOException e) { - processException("Could not update metadata field(id=" + fieldId + "), IOException. Message:" + e, context); - } finally { - processFinally(context); - } - - log.info("Metadata Field(id=" + fieldId + ") has been successfully updated."); - return Response.ok().build(); - } - - /** - * Delete metadata field from the DSpace metadata registry - * - * @param fieldId Id of the metadata field in DSpace. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the metadata field as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @return Return response code OK(200) if was everything all right. - * Otherwise return NOT_FOUND(404) if was id of metadata field is incorrect. - * Or (UNAUTHORIZED)401 if was problem with permission to metadata field. - * @throws WebApplicationException Thrown if there was a problem with creating context or problem - * with database reading or deleting. Or problem with deleting - * metadata field caused by IOException or authorization. - */ - @DELETE - @Path("/metadata-fields/{field_id}") - public Response deleteMetadataField(@PathParam("field_id") Integer fieldId, @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, - @Context HttpHeaders headers, @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Deleting metadata field(id=" + fieldId + ")."); - org.dspace.core.Context context = null; - - try { - context = createContext(); - - org.dspace.content.MetadataField dspaceField = metadataFieldService.find(context, fieldId); - if (dspaceField == null) { - log.error(String.format("Metadata Field %d not found", fieldId)); - throw new WebApplicationException(Response.Status.NOT_FOUND); - } - writeStats(siteService.findSite(context), UsageEvent.Action.DELETE, user_ip, user_agent, xforwardedfor, - headers, - request, context); - - metadataFieldService.delete(context, dspaceField); - context.complete(); - - } catch (SQLException e) { - processException("Could not delete metadata field(id=" + fieldId + "), SQLException. Message:" + e, - context); - } catch (AuthorizeException e) { - processException("Could not delete metadata field(id=" + fieldId + "), AuthorizeException. Message:" + e, - context); - } catch (ContextException e) { - processException( - "Could not delete metadata field(id=" + fieldId + "), ContextException. Message:" + e.getMessage(), - context); - } finally { - processFinally(context); - } - - - log.info("Metadata field(id=" + fieldId + ") was successfully deleted."); - return Response.status(Response.Status.OK).build(); - } - - /** - * Delete metadata schema from the DSpace metadata registry - * - * @param schemaId Id of the metadata schema in DSpace. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the metadata schema as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @return Return response code OK(200) if was everything all right. - * Otherwise return NOT_FOUND(404) if was id of metadata schema is incorrect. - * Or (UNAUTHORIZED)401 if was problem with permission to metadata schema. - * @throws WebApplicationException Thrown if there was a problem with creating context or problem - * with database reading or deleting. Or problem with deleting - * metadata schema caused by IOException or authorization. - */ - @DELETE - @Path("/schema/{schema_id}") - public Response deleteSchema(@PathParam("schema_id") Integer schemaId, @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, - @Context HttpHeaders headers, @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Deleting metadata schema(id=" + schemaId + ")."); - org.dspace.core.Context context = null; - - try { - context = createContext(); - - org.dspace.content.MetadataSchema dspaceSchema = metadataSchemaService.find(context, schemaId); - if (dspaceSchema == null) { - log.error(String.format("Metadata Schema %d not found", schemaId)); - throw new WebApplicationException(Response.Status.NOT_FOUND); - } - writeStats(siteService.findSite(context), UsageEvent.Action.DELETE, user_ip, user_agent, xforwardedfor, - headers, - request, context); - - metadataSchemaService.delete(context, dspaceSchema); - context.complete(); - - } catch (SQLException e) { - processException("Could not delete metadata schema(id=" + schemaId + "), SQLException. Message:" + e, - context); - } catch (AuthorizeException e) { - processException("Could not delete metadata schema(id=" + schemaId + "), AuthorizeException. Message:" + e, - context); - } catch (ContextException e) { - processException( - "Could not delete metadata schema(id=" + schemaId + "), ContextException. Message:" + e.getMessage(), - context); - } finally { - processFinally(context); - } - - - log.info("Metadata schema(id=" + schemaId + ") was successfully deleted."); - return Response.status(Response.Status.OK).build(); - } - -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/Resource.java b/dspace-rest/src/main/java/org/dspace/rest/Resource.java deleted file mode 100644 index 7a7624fef0..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/Resource.java +++ /dev/null @@ -1,212 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.rest; - -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Collection; -import javax.servlet.ServletContext; -import javax.servlet.http.HttpServletRequest; -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.Response; - -import org.apache.logging.log4j.Logger; -import org.dspace.content.DSpaceObject; -import org.dspace.core.Context; -import org.dspace.eperson.factory.EPersonServiceFactory; -import org.dspace.rest.exceptions.ContextException; -import org.dspace.services.factory.DSpaceServicesFactory; -import org.dspace.usage.UsageEvent; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.context.SecurityContextHolder; - -/** - * Superclass of all resource classes in REST API. It has methods for creating - * context, write statistics, processsing exceptions, splitting a key of - * metadata, string representation of action and method for getting the logged - * in user from the token in request header. - * - * @author Rostislav Novak (Computing and Information Centre, CTU in Prague) - */ -public class Resource { - - @javax.ws.rs.core.Context - public ServletContext servletContext; - - private static Logger log = org.apache.logging.log4j.LogManager.getLogger(Resource.class); - - private static final boolean writeStatistics; - - static { - writeStatistics = DSpaceServicesFactory.getInstance().getConfigurationService() - .getBooleanProperty("rest.stats", false); - } - - /** - * Create context to work with DSpace database. It can create context - * with or without a logged in user (retrieved from SecurityContextHolder). Throws - * WebApplicationException caused by: SQLException if there was a problem - * with reading from database. Throws AuthorizeException if there was - * a problem with authorization to read from the database. Throws Exception - * if there was a problem creating context. - * - * @return Newly created context with the logged in user unless the specified user was null. - * If user is null, create the context without a logged in user. - * @throws ContextException Thrown in case of a problem creating context. Can be caused by - * SQLException error in creating context or finding the user to - * log in. Can be caused by AuthorizeException if there was a - * problem authorizing the found user. - * @throws SQLException An exception that provides information on a database access error or other errors. - */ - protected static org.dspace.core.Context createContext() throws ContextException, SQLException { - org.dspace.core.Context context = new org.dspace.core.Context(); - //context.getDBConnection().setAutoCommit(false); // Disable autocommit. - - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - if (authentication != null) { - Collection specialGroups = (Collection) authentication - .getAuthorities(); - for (SimpleGrantedAuthority grantedAuthority : specialGroups) { - context.setSpecialGroup(EPersonServiceFactory.getInstance().getGroupService() - .findByName(context, grantedAuthority.getAuthority()) - .getID()); - } - context.setCurrentUser( - EPersonServiceFactory.getInstance().getEPersonService().findByEmail(context, authentication.getName())); - } - - return context; - } - - /** - * Records a statistics event about an object used via REST API. - * - * @param dspaceObject DSpace object on which a request was performed. - * @param action Action that was performed. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the item as the user logged into the - * context. The header "rest-dspace-token" with the token passed - * from the login method must be set. - * @param request Servlet's HTTP request object. - * @param context Context which must be aborted. - */ - protected void writeStats(DSpaceObject dspaceObject, UsageEvent.Action action, - String user_ip, String user_agent, String xforwardedfor, HttpHeaders headers, - HttpServletRequest request, Context context) { - if (!writeStatistics) { - return; - } - - if ((user_ip == null) || (user_ip.length() == 0)) { - DSpaceServicesFactory.getInstance().getEventService() - .fireEvent(new UsageEvent(action, request, context, dspaceObject)); - } else { - DSpaceServicesFactory.getInstance().getEventService().fireEvent( - new UsageEvent(action, user_ip, user_agent, xforwardedfor, context, dspaceObject)); - } - - log.debug("fired event"); - } - - /** - * Process exception, print message to logger error stream and abort DSpace - * context. - * - * @param message Message, which will be printed to error stream. - * @param context Context which must be aborted. - * @throws WebApplicationException This exception is throw for user of REST api. - */ - protected static void processException(String message, org.dspace.core.Context context) - throws WebApplicationException { - if ((context != null) && (context.isValid())) { - context.abort(); - } - log.error(message); - throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR); - } - - /** - * Process finally statement. It will print message to logger error stream - * and abort DSpace context, if was not properly ended. - * - * @param context Context which must be aborted. - * @throws WebApplicationException This exception is thrown for user of REST API. - */ - protected void processFinally(org.dspace.core.Context context) throws WebApplicationException { - if ((context != null) && (context.isValid())) { - context.abort(); - log.error("Something get wrong. Aborting context in finally statement."); - throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR); - } - } - - /** - * Split string with regex ".". - * - * @param key String which will be splitted. - * @return String array filed with separated string. - */ - protected String[] mySplit(String key) { - ArrayList list = new ArrayList(); - int prev = 0; - for (int i = 0; i < key.length(); i++) { - if (key.charAt(i) == '.') { - list.add(key.substring(prev, i)); - prev = i + 1; - } else if (i + 1 == key.length()) { - list.add(key.substring(prev, i + 1)); - } - } - - if (list.size() == 2) { - list.add(null); - } - - return list.toArray(new String[0]); - } - - /** - * Return string representation of values - * org.dspace.core.Constants.{READ,WRITE,DELETE}. - * - * @param action Constant from org.dspace.core.Constants.* - * @return String representation. read or write or delete. - */ - protected String getActionString(int action) { - String actionStr; - switch (action) { - case org.dspace.core.Constants.READ: - actionStr = "read"; - break; - case org.dspace.core.Constants.WRITE: - actionStr = "write"; - break; - case org.dspace.core.Constants.DELETE: - actionStr = "delete"; - break; - case org.dspace.core.Constants.REMOVE: - actionStr = "remove"; - break; - case org.dspace.core.Constants.ADD: - actionStr = "add"; - break; - default: - actionStr = "(?action?)"; - break; - } - return actionStr; - } - -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/RestIndex.java b/dspace-rest/src/main/java/org/dspace/rest/RestIndex.java deleted file mode 100644 index 26b1150229..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/RestIndex.java +++ /dev/null @@ -1,301 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.rest; - -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.sql.SQLException; -import java.util.Iterator; -import javax.servlet.ServletContext; -import javax.ws.rs.Consumes; -import javax.ws.rs.GET; -import javax.ws.rs.POST; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; - -import org.apache.commons.lang3.StringUtils; -import org.apache.logging.log4j.Logger; -import org.dspace.authenticate.AuthenticationMethod; -import org.dspace.authenticate.ShibAuthentication; -import org.dspace.authenticate.factory.AuthenticateServiceFactory; -import org.dspace.authenticate.service.AuthenticationService; -import org.dspace.eperson.EPerson; -import org.dspace.eperson.factory.EPersonServiceFactory; -import org.dspace.eperson.service.EPersonService; -import org.dspace.rest.common.Status; -import org.dspace.rest.exceptions.ContextException; -import org.dspace.utils.DSpace; - -/** - * Root of RESTful api. It provides login and logout. Also have method for - * printing every method which is provides by RESTful api. - * - * @author Rostislav Novak (Computing and Information Centre, CTU in Prague) - */ -@Path("/") -public class RestIndex { - protected EPersonService epersonService = EPersonServiceFactory.getInstance().getEPersonService(); - private static Logger log = org.apache.logging.log4j.LogManager.getLogger(RestIndex.class); - - /** - * Return html page with information about REST api. It contains methods all - * methods provide by REST api. - * - * @param servletContext Context of the servlet container. - * @return HTML page which has information about all methods of REST API. - */ - @GET - @Produces(MediaType.TEXT_HTML) - public String sayHtmlHello(@Context ServletContext servletContext) { - // TODO Better graphics, add arguments to all methods. (limit, offset, item and so on) - return "DSpace REST - index" + - "" - + "

    DSpace REST API (Deprecated)

    " + - "This REST API is deprecated and will be removed in v8." + - " Please use the new Server API webapp instead.
    " + - "Server path: " + servletContext.getContextPath() + - "

    Index

    " + - "
      " + - "
    • GET / - Return this page.
    • " + - "
    • GET /test - Return the string \"REST api is running\" for testing purposes.
    • " + - "
    • POST /login - Method for logging into the DSpace RESTful API. You must post the parameters \"email\"" + - " and \"password\". Example: \"email=test@dspace&password=pass\". Returns a JSESSIONID cookie which can " + - "be used for future authenticated requests.
    • " + - "
    • POST /logout - Method for logging out of the DSpace RESTful API. The request must include the " + - "\"rest-dspace-token\" token
    • header." + - "
    " + - "

    Communities

    " + - "
      " + - "
    • GET /communities - Return an array of all communities in DSpace.
    • " + - "
    • GET /communities/top-communities - Returns an array of all top-leve communities in DSpace.
    • " + - "
    • GET /communities/{communityId} - Returns a community with the specified ID.
    • " + - "
    • GET /communities/{communityId}/collections - Returns an array of collections of the specified " + - "community.
    • " + - "
    • GET /communities/{communityId}/communities - Returns an array of subcommunities of the specified " + - "community.
    • " + - "
    • POST /communities - Create a new top-level community. You must post a community.
    • " + - "
    • POST /communities/{communityId}/collections - Create a new collection in the specified community. " + - "You must post a collection.
    • " + - "
    • POST /communities/{communityId}/communities - Create a new subcommunity in the specified community. " + - "You must post a community.
    • " + - "
    • PUT /communities/{communityId} - Update the specified community.
    • " + - "
    • DELETE /communities/{communityId} - Delete the specified community.
    • " + - "
    • DELETE /communities/{communityId}/collections/{collectionId} - Delete the specified collection in " + - "the specified community.
    • " + - "
    • DELETE /communities/{communityId}/communities/{communityId2} - Delete the specified subcommunity " + - "(communityId2) in the specified community (communityId).
    • " + - "
    " + - "

    Collections

    " + - "
      " + - "
    • GET /collections - Return all DSpace collections in array.
    • " + - "
    • GET /collections/{collectionId} - Return a collection with the specified ID.
    • " + - "
    • GET /collections/{collectionId}/items - Return all items of the specified collection.
    • " + - "
    • POST /collections/{collectionId}/items - Create an item in the specified collection. You must post " + - "an item.
    • " + - "
    • POST /collections/find-collection - Find a collection by name.
    • " + - "
    • PUT /collections/{collectionId}
    • - Update the specified collection. You must post a collection." + - "
    • DELETE /collections/{collectionId} - Delete the specified collection from DSpace.
    • " + - "
    • DELETE /collections/{collectionId}/items/{itemId} - Delete the specified item (itemId) in the " + - "specified collection (collectionId).
    • " + - "
    " + - "

    Items

    " + - "
      " + - "
    • GET /items - Return a list of items.
    • " + - "
    • GET /items/{item id} - Return the specified item.
    • " + - "
    • GET /items/{item id}/metadata - Return metadata of the specified item.
    • " + - "
    • GET /items/{item id}/bitstreams - Return bitstreams of the specified item.
    • " + - "
    • POST /items/find-by-metadata-field - Find items by the specified metadata value.
    • " + - "
    • POST /items/{item id}/metadata - Add metadata to the specified item.
    • " + - "
    • POST /items/{item id}/bitstreams - Add a bitstream to the specified item.
    • " + - "
    • PUT /items/{item id}/metadata - Update metadata in the specified item.
    • " + - "
    • DELETE /items/{item id} - Delete the specified item.
    • " + - "
    • DELETE /items/{item id}/metadata - Clear metadata of the specified item.
    • " + - "
    • DELETE /items/{item id}/bitstreams/{bitstream id} - Delete the specified bitstream of the specified " + - "item.
    • " + - "
    " + - "

    Bitstreams

    " + - "
      " + - "
    • GET /bitstreams - Return all bitstreams in DSpace.
    • " + - "
    • GET /bitstreams/{bitstream id} - Return the specified bitstream.
    • " + - "
    • GET /bitstreams/{bitstream id}/policy - Return policies of the specified bitstream.
    • " + - "
    • GET /bitstreams/{bitstream id}/retrieve - Return the contents of the specified bitstream.
    • " + - "
    • POST /bitstreams/{bitstream id}/policy - Add a policy to the specified bitstream.
    • " + - "
    • PUT /bitstreams/{bitstream id}/data - Update the contents of the specified bitstream.
    • " + - "
    • PUT /bitstreams/{bitstream id} - Update metadata of the specified bitstream.
    • " + - "
    • DELETE /bitstreams/{bitstream id} - Delete the specified bitstream from DSpace.
    • " + - "
    • DELETE /bitstreams/{bitstream id}/policy/{policy_id} - Delete the specified bitstream policy.
    • " + - "
    " + - "

    Hierarchy

    " + - "
      " + - "
    • GET /hierarchy - Return hierarchy of communities and collections in tree form. Each object is " + - "minimally populated (name, handle, id) for efficient retrieval.
    • " + - "
    " + - "

    Metadata and Schema Registry

    " + - "
      " + - "
    • GET /registries/schema - Return the list of metadata schemas in the registry
    • " + - "
    • GET /registries/schema/{schema_prefix} - Returns the specified metadata schema
    • " + - "
    • GET /registries/schema/{schema_prefix}/metadata-fields/{element} - Returns the metadata field within" + - " a schema with an unqualified element name
    • " + - "
    • GET /registries/schema/{schema_prefix}/metadata-fields/{element}/{qualifier} - Returns the metadata " + - "field within a schema with a qualified element name
    • " + - "
    • POST /registries/schema/ - Add a schema to the schema registry
    • " + - "
    • POST /registries/schema/{schema_prefix}/metadata-fields - Add a metadata field to the specified " + - "schema
    • " + - "
    • GET /registries/metadata-fields/{field_id} - Return the specified metadata field
    • " + - "
    • PUT /registries/metadata-fields/{field_id} - Update the specified metadata field
    • " + - "
    • DELETE /registries/metadata-fields/{field_id} - Delete the specified metadata field from the " + - "metadata field registry
    • " + - "
    • DELETE /registries/schema/{schema_id} - Delete the specified schema from the schema registry
    • " + - "
    " + - "

    Query/Reporting Tools

    " + - "
      " + - "
    • GET /reports - Return a list of report tools built on the rest api
    • " + - "
    • GET /reports/{nickname} - Return a redirect to a specific report
    • " + - "
    • GET /filters - Return a list of use case filters available for quality control reporting
    • " + - "
    • GET /filtered-collections - Return collections and item counts based on pre-defined filters
    • " + - "
    • GET /filtered-collections/{collection_id} - Return items and item counts for a collection based on " + - "pre-defined filters
    • " + - "
    • GET /filtered-items - Retrieve a set of items based on a metadata query and a set of filters
    • " + - "
    " + - " "; - } - - /** - * Method only for testing whether the REST API is running. - * - * @return String "REST api is running." - */ - @GET - @Path("/test") - public String test() { - return "REST api is running."; - } - - /** - * Method to login a user into REST API. - * - * @return Returns response code OK and a token. Otherwise returns response - * code FORBIDDEN(403). - */ - @POST - @Path("/login") - @Consumes( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public Response login() { - //If you can get here, you are authenticated, the actual login is handled by spring security - return Response.ok().build(); - } - - @GET - @Path("/shibboleth-login") - @Consumes( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public Response shibbolethLogin() { - //If you can get here, you are authenticated, the actual login is handled by spring security - return Response.ok().build(); - } - - @GET - @Path("/login-shibboleth") - @Consumes( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public Response shibbolethLoginEndPoint() { - org.dspace.core.Context context = null; - try { - context = Resource.createContext(); - AuthenticationService authenticationService = AuthenticateServiceFactory.getInstance() - .getAuthenticationService(); - Iterator authenticationMethodIterator = authenticationService - .authenticationMethodIterator(); - while (authenticationMethodIterator.hasNext()) { - AuthenticationMethod authenticationMethod = authenticationMethodIterator.next(); - if (authenticationMethod instanceof ShibAuthentication) { - //TODO: Perhaps look for a better way of handling this ? - org.dspace.services.model.Request currentRequest = new DSpace().getRequestService() - .getCurrentRequest(); - String loginPageURL = authenticationMethod - .loginPageURL(context, currentRequest.getHttpServletRequest(), - currentRequest.getHttpServletResponse()); - if (StringUtils.isNotBlank(loginPageURL)) { - currentRequest.getHttpServletResponse().sendRedirect(loginPageURL); - } - } - } - context.abort(); - } catch (ContextException | SQLException | IOException e) { - Resource.processException("Shibboleth endpoint error: " + e.getMessage(), context); - } finally { - if (context != null && context.isValid()) { - context.abort(); - } - - } - return Response.ok().build(); - } - - /** - * Method to logout a user from DSpace REST API. Removes the token and user from - * TokenHolder. - * - * @param headers Request header which contains the header named - * "rest-dspace-token" containing the token as value. - * @return Return response OK, otherwise BAD_REQUEST, if there was a problem with - * logout or the token is incorrect. - */ - @POST - @Path("/logout") - @Consumes( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public Response logout(@Context HttpHeaders headers) { - //If you can get here, you are logged out, this actual logout is handled by spring security - return Response.ok().build(); - } - - /** - * Method to check current status of the service and logged in user. - * - * okay: true | false - * authenticated: true | false - * epersonEMAIL: user@example.com - * epersonNAME: John Doe - * - * @param headers Request header which contains the header named - * "rest-dspace-token" containing the token as value. - * @return status the Status object with information about REST API - * @throws UnsupportedEncodingException The Character Encoding is not supported. - */ - @GET - @Path("/status") - @Consumes( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public Status status(@Context HttpHeaders headers) - throws UnsupportedEncodingException { - org.dspace.core.Context context = null; - - try { - context = Resource.createContext(); - EPerson ePerson = context.getCurrentUser(); - - if (ePerson != null) { - //DB EPerson needed since token won't have full info, need context - EPerson dbEPerson = epersonService.findByEmail(context, ePerson.getEmail()); - - Status status = new Status(dbEPerson.getEmail(), dbEPerson.getFullName()); - return status; - } - } catch (ContextException e) { - Resource.processException("Status context error: " + e.getMessage(), context); - } catch (SQLException e) { - Resource.processException("Status eperson db lookup error: " + e.getMessage(), context); - } finally { - context.abort(); - } - - //fallback status, unauth - return new Status(); - } -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/RestReports.java b/dspace-rest/src/main/java/org/dspace/rest/RestReports.java deleted file mode 100644 index 4af556b6f8..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/RestReports.java +++ /dev/null @@ -1,86 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.rest; - -import java.net.URI; -import java.util.ArrayList; -import java.util.List; -import javax.ws.rs.GET; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.UriInfo; - -import org.apache.logging.log4j.Logger; -import org.dspace.rest.common.Report; -import org.dspace.services.ConfigurationService; -import org.dspace.services.factory.DSpaceServicesFactory; - - -/** - * Root of RESTful api. It provides login and logout. Also have method for - * printing every method which is provides by RESTful api. - * - * @author Terry Brady, Georgetown University - */ -@Path("/reports") -public class RestReports { - private static Logger log = org.apache.logging.log4j.LogManager.getLogger(RestReports.class); - - protected ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); - public static final String REST_RPT_URL = "rest.report-url."; - - /** - * Return html page with information about REST api. It contains methods all - * methods provide by REST api. - * - * @return HTML page which has information about all methods of REST api. - */ - @GET - @Produces(MediaType.APPLICATION_XML) - public Report[] reportIndex() - throws WebApplicationException { - ArrayList reports = new ArrayList(); - List propNames = configurationService.getPropertyKeys("rest"); - for (String propName : propNames) { - if (propName.startsWith(REST_RPT_URL)) { - String nickname = propName.substring(REST_RPT_URL.length()); - String url = configurationService.getProperty(propName); - reports.add(new Report(nickname, url)); - } - } - return reports.toArray(new Report[0]); - } - - @Path("/{report_nickname}") - @GET - public Response customReport(@PathParam("report_nickname") String report_nickname, @Context UriInfo uriInfo) - throws WebApplicationException { - URI uri = null; - if (!report_nickname.isEmpty()) { - log.info(String.format("Seeking report %s", report_nickname)); - String url = configurationService.getProperty(REST_RPT_URL + report_nickname); - - log.info(String.format("URL for report %s found: [%s]", report_nickname, url)); - if (!url.isEmpty()) { - uri = uriInfo.getBaseUriBuilder().path(url).build(""); - log.info(String.format("URI for report %s", uri)); - } - } - - if (uri != null) { - return Response.temporaryRedirect(uri).build(); - } - - return Response.noContent().build(); - } -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/authentication/DSpaceAuthenticationProvider.java b/dspace-rest/src/main/java/org/dspace/rest/authentication/DSpaceAuthenticationProvider.java deleted file mode 100644 index eac4c40111..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/authentication/DSpaceAuthenticationProvider.java +++ /dev/null @@ -1,129 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.rest.authentication; - -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.List; -import javax.servlet.http.HttpServletRequest; - -import org.apache.commons.lang3.StringUtils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.dspace.authenticate.AuthenticationMethod; -import org.dspace.authenticate.factory.AuthenticateServiceFactory; -import org.dspace.authenticate.service.AuthenticationService; -import org.dspace.core.Context; -import org.dspace.core.LogHelper; -import org.dspace.eperson.EPerson; -import org.dspace.eperson.Group; -import org.dspace.utils.DSpace; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.authentication.BadCredentialsException; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.core.authority.SimpleGrantedAuthority; - -/** - * The core authentication & authorization provider, this provider is called when logging in & will process - * - * @author Roeland Dillen (roeland at atmire dot com) - * @author kevinvandevelde at atmire.com - * - * FIXME This provider handles both the authorization as well as the authentication, - * due to the way that the DSpace authentication is implemented there is currently no other way to do this. - */ -public class DSpaceAuthenticationProvider implements AuthenticationProvider { - - private static final Logger log = LogManager.getLogger(); - - protected AuthenticationService authenticationService = AuthenticateServiceFactory.getInstance() - .getAuthenticationService(); - - @Override - public Authentication authenticate(Authentication authentication) throws AuthenticationException { - Context context = null; - - try { - context = new Context(); - String name = authentication.getName(); - String password = authentication.getCredentials().toString(); - HttpServletRequest httpServletRequest = new DSpace().getRequestService().getCurrentRequest() - .getHttpServletRequest(); - List grantedAuthorities = new ArrayList<>(); - - - int implicitStatus = authenticationService - .authenticateImplicit(context, null, null, null, httpServletRequest); - - if (implicitStatus == AuthenticationMethod.SUCCESS) { - log.info(LogHelper.getHeader(context, "login", "type=implicit")); - addSpecialGroupsToGrantedAuthorityList(context, httpServletRequest, grantedAuthorities); - return createAuthenticationToken(password, context, grantedAuthorities); - - } else { - int authenticateResult = authenticationService - .authenticate(context, name, password, null, httpServletRequest); - if (AuthenticationMethod.SUCCESS == authenticateResult) { - addSpecialGroupsToGrantedAuthorityList(context, httpServletRequest, grantedAuthorities); - - log.info(LogHelper.getHeader(context, "login", "type=explicit")); - - return createAuthenticationToken(password, context, grantedAuthorities); - - } else { - log.info(LogHelper.getHeader(context, "failed_login", - "email=" + name + ", result=" + authenticateResult)); - throw new BadCredentialsException("Login failed"); - } - } - } catch (BadCredentialsException e) { - throw e; - } catch (Exception e) { - log.error("Error while authenticating in the rest api", e); - } finally { - if (context != null && context.isValid()) { - try { - context.complete(); - } catch (SQLException e) { - log.error(e.getMessage() + " occurred while trying to close", e); - } - } - } - - return null; - } - - protected void addSpecialGroupsToGrantedAuthorityList(Context context, HttpServletRequest httpServletRequest, - List grantedAuthorities) - throws SQLException { - List groups = authenticationService.getSpecialGroups(context, httpServletRequest); - for (Group group : groups) { - grantedAuthorities.add(new SimpleGrantedAuthority(group.getName())); - } - } - - private Authentication createAuthenticationToken(final String password, final Context context, - final List grantedAuthorities) { - EPerson ePerson = context.getCurrentUser(); - if (ePerson != null && StringUtils.isNotBlank(ePerson.getEmail())) { - return new UsernamePasswordAuthenticationToken(ePerson.getEmail(), password, grantedAuthorities); - - } else { - log.info(LogHelper.getHeader(context, "failed_login", - "No eperson with an non-blank e-mail address found")); - throw new BadCredentialsException("Login failed"); - } - } - - @Override - public boolean supports(Class authentication) { - return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication)); - } -} \ No newline at end of file diff --git a/dspace-rest/src/main/java/org/dspace/rest/authentication/NoRedirectAuthenticationLoginSuccessHandler.java b/dspace-rest/src/main/java/org/dspace/rest/authentication/NoRedirectAuthenticationLoginSuccessHandler.java deleted file mode 100644 index af146f27b7..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/authentication/NoRedirectAuthenticationLoginSuccessHandler.java +++ /dev/null @@ -1,41 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.rest.authentication; - -import java.io.IOException; -import javax.annotation.PostConstruct; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.springframework.security.web.RedirectStrategy; -import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; - -/** - * @author kevinvandevelde at atmire.com - * - * Spring redirects to the home page after a successfull login. This success handles ensures that this is NOT the case. - */ -public class NoRedirectAuthenticationLoginSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { - - @PostConstruct - public void afterPropertiesSet() { - setRedirectStrategy(new NoRedirectStrategy()); - } - - protected class NoRedirectStrategy implements RedirectStrategy { - - @Override - public void sendRedirect(HttpServletRequest request, - HttpServletResponse response, String url) throws IOException { - // no redirect - - } - - } - -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/authentication/NoRedirectAuthenticationLogoutSuccessHandler.java b/dspace-rest/src/main/java/org/dspace/rest/authentication/NoRedirectAuthenticationLogoutSuccessHandler.java deleted file mode 100644 index db28f2e388..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/authentication/NoRedirectAuthenticationLogoutSuccessHandler.java +++ /dev/null @@ -1,39 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.rest.authentication; - -import java.io.IOException; -import javax.annotation.PostConstruct; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.springframework.security.web.RedirectStrategy; -import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler; - -/** - * @author kevinvandevelde at atmire.com - * - * Spring redirects to the home page after a successfull logout. This success handles ensures that this is NOT the case. - */ -public class NoRedirectAuthenticationLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler { - @PostConstruct - public void afterPropertiesSet() { - setRedirectStrategy(new NoRedirectStrategy()); - } - - protected class NoRedirectStrategy implements RedirectStrategy { - - @Override - public void sendRedirect(HttpServletRequest request, - HttpServletResponse response, String url) throws IOException { - // no redirect - - } - - } -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/common/Bitstream.java b/dspace-rest/src/main/java/org/dspace/rest/common/Bitstream.java deleted file mode 100644 index 7eb198990e..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/common/Bitstream.java +++ /dev/null @@ -1,199 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.rest.common; - -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import javax.servlet.ServletContext; -import javax.xml.bind.annotation.XmlRootElement; - -import org.apache.logging.log4j.Logger; -import org.dspace.content.Bundle; -import org.dspace.content.factory.ContentServiceFactory; -import org.dspace.content.service.BitstreamService; -import org.dspace.content.service.BundleService; -import org.dspace.core.Constants; -import org.dspace.core.Context; -import org.dspace.utils.DSpace; - -/** - * Created with IntelliJ IDEA. - * User: peterdietz - * Date: 9/21/13 - * Time: 12:54 AM - * To change this template use File | Settings | File Templates. - */ -@XmlRootElement(name = "bitstream") -public class Bitstream extends DSpaceObject { - protected BitstreamService bitstreamService = ContentServiceFactory.getInstance().getBitstreamService(); - protected BundleService bundleService = ContentServiceFactory.getInstance().getBundleService(); - - Logger log = org.apache.logging.log4j.LogManager.getLogger(Bitstream.class); - - private String bundleName; - private String description; - private String format; - private String mimeType; - private Long sizeBytes; - private DSpaceObject parentObject; - private String retrieveLink; - private CheckSum checkSum; - private Integer sequenceId; - - private ResourcePolicy[] policies = null; - - public Bitstream() { - - } - - public Bitstream(org.dspace.content.Bitstream bitstream, ServletContext servletContext, String expand, - Context context) - throws SQLException { - super(bitstream, servletContext); - setup(bitstream, servletContext, expand, context); - } - - public void setup(org.dspace.content.Bitstream bitstream, ServletContext servletContext, String expand, - Context context) - throws SQLException { - List expandFields = new ArrayList(); - if (expand != null) { - expandFields = Arrays.asList(expand.split(",")); - } - - //A logo bitstream might not have a bundle... - if (bitstream.getBundles() != null && !bitstream.getBundles().isEmpty()) { - if (bitstreamService.getParentObject(context, bitstream).getType() == Constants.ITEM) { - bundleName = bitstream.getBundles().get(0).getName(); - } - } - - description = bitstream.getDescription(); - format = bitstreamService.getFormatDescription(context, bitstream); - sizeBytes = bitstream.getSizeBytes(); - String path = new DSpace().getRequestService().getCurrentRequest().getHttpServletRequest().getContextPath(); - retrieveLink = path + "/bitstreams/" + bitstream.getID() + "/retrieve"; - mimeType = bitstreamService.getFormat(context, bitstream).getMIMEType(); - sequenceId = bitstream.getSequenceID(); - CheckSum checkSum = new CheckSum(); - checkSum.setCheckSumAlgorith(bitstream.getChecksumAlgorithm()); - checkSum.setValue(bitstream.getChecksum()); - this.setCheckSum(checkSum); - - if (expandFields.contains("parent") || expandFields.contains("all")) { - parentObject = new DSpaceObject(bitstreamService.getParentObject(context, bitstream), servletContext); - } else { - this.addExpand("parent"); - } - - if (expandFields.contains("policies") || expandFields.contains("all")) { - // Find policies without context. - List tempPolicies = new ArrayList(); - List bundles = bitstream.getBundles(); - for (Bundle bundle : bundles) { - List bitstreamsPolicies = bundleService - .getBitstreamPolicies(context, bundle); - for (org.dspace.authorize.ResourcePolicy policy : bitstreamsPolicies) { - if (policy.getdSpaceObject().equals(bitstream)) { - tempPolicies.add(new ResourcePolicy(policy)); - } - } - } - - policies = tempPolicies.toArray(new ResourcePolicy[0]); - } else { - this.addExpand("policies"); - } - - if (!expandFields.contains("all")) { - this.addExpand("all"); - } - } - - public Integer getSequenceId() { - return sequenceId; - } - - public void setSequenceId(Integer sequenceId) { - this.sequenceId = sequenceId; - } - - public String getBundleName() { - return bundleName; - } - - public void setBundleName(String bundleName) { - this.bundleName = bundleName; - } - - public void setDescription(String description) { - this.description = description; - } - - public void setFormat(String format) { - this.format = format; - } - - public void setMimeType(String mimeType) { - this.mimeType = mimeType; - } - - public void setSizeBytes(Long sizeBytes) { - this.sizeBytes = sizeBytes; - } - - public void setParentObject(DSpaceObject parentObject) { - this.parentObject = parentObject; - } - - public void setRetrieveLink(String retrieveLink) { - this.retrieveLink = retrieveLink; - } - - public String getDescription() { - return description; - } - - public String getFormat() { - return format; - } - - public String getMimeType() { - return mimeType; - } - - public Long getSizeBytes() { - return sizeBytes; - } - - public String getRetrieveLink() { - return retrieveLink; - } - - public DSpaceObject getParentObject() { - return parentObject; - } - - public CheckSum getCheckSum() { - return checkSum; - } - - public void setCheckSum(CheckSum checkSum) { - this.checkSum = checkSum; - } - - public ResourcePolicy[] getPolicies() { - return policies; - } - - public void setPolicies(ResourcePolicy[] policies) { - this.policies = policies; - } -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/common/CheckSum.java b/dspace-rest/src/main/java/org/dspace/rest/common/CheckSum.java deleted file mode 100644 index 2db36ae9a0..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/common/CheckSum.java +++ /dev/null @@ -1,40 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ - -package org.dspace.rest.common; - -import javax.xml.bind.annotation.XmlAttribute; -import javax.xml.bind.annotation.XmlType; -import javax.xml.bind.annotation.XmlValue; - -@XmlType -public class CheckSum { - String checkSumAlgorithm; - String value; - - public CheckSum() { - } - - @XmlAttribute(name = "checkSumAlgorithm") - public String getCheckSumAlgorith() { - return checkSumAlgorithm; - } - - public void setCheckSumAlgorith(String checkSumAlgorith) { - this.checkSumAlgorithm = checkSumAlgorith; - } - - @XmlValue - public String getValue() { - return value; - } - - public void setValue(String value) { - this.value = value; - } -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/common/Collection.java b/dspace-rest/src/main/java/org/dspace/rest/common/Collection.java deleted file mode 100644 index be6e698b4d..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/common/Collection.java +++ /dev/null @@ -1,225 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.rest.common; - -import static org.dspace.content.service.DSpaceObjectService.MD_COPYRIGHT_TEXT; -import static org.dspace.content.service.DSpaceObjectService.MD_INTRODUCTORY_TEXT; -import static org.dspace.content.service.DSpaceObjectService.MD_SHORT_DESCRIPTION; -import static org.dspace.content.service.DSpaceObjectService.MD_SIDEBAR_TEXT; - -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Iterator; -import java.util.List; -import javax.servlet.ServletContext; -import javax.ws.rs.WebApplicationException; -import javax.xml.bind.annotation.XmlRootElement; - -import org.apache.logging.log4j.Logger; -import org.dspace.content.factory.ContentServiceFactory; -import org.dspace.content.service.CollectionService; -import org.dspace.content.service.CommunityService; -import org.dspace.content.service.ItemService; -import org.dspace.core.Context; - -/** - * Created with IntelliJ IDEA. - * User: peterdietz - * Date: 5/22/13 - * Time: 9:41 AM - */ -@XmlRootElement(name = "collection") -public class Collection extends DSpaceObject { - protected CommunityService communityService = ContentServiceFactory.getInstance().getCommunityService(); - protected CollectionService collectionService = ContentServiceFactory.getInstance().getCollectionService(); - protected ItemService itemService = ContentServiceFactory.getInstance().getItemService(); - - Logger log = org.apache.logging.log4j.LogManager.getLogger(Collection.class); - - //Relationships - private Bitstream logo; - private Community parentCommunity; - private List parentCommunityList = new ArrayList<>(); - - private List items = new ArrayList<>(); - - //Collection-Metadata - private String license; - private String copyrightText; - private String introductoryText; - private String shortDescription; - private String sidebarText; - - //Calculated - private Integer numberItems; - - public Collection() { - } - - public Collection(org.dspace.content.Collection collection, ServletContext servletContext, String expand, - Context context, Integer limit, Integer offset) - throws SQLException, WebApplicationException { - super(collection, servletContext); - setup(collection, servletContext, expand, context, limit, offset); - } - - private void setup(org.dspace.content.Collection collection, ServletContext servletContext, String expand, - Context context, Integer limit, Integer offset) - throws SQLException { - List expandFields = new ArrayList<>(); - if (expand != null) { - expandFields = Arrays.asList(expand.split(",")); - } - - this.setCopyrightText(collectionService.getMetadataFirstValue(collection, - MD_COPYRIGHT_TEXT, org.dspace.content.Item.ANY)); - this.setIntroductoryText(collectionService.getMetadataFirstValue(collection, - MD_INTRODUCTORY_TEXT, org.dspace.content.Item.ANY)); - this.setShortDescription(collectionService.getMetadataFirstValue(collection, - MD_SHORT_DESCRIPTION, org.dspace.content.Item.ANY)); - this.setSidebarText(collectionService.getMetadataFirstValue(collection, - MD_SIDEBAR_TEXT, org.dspace.content.Item.ANY)); - - if (expandFields.contains("parentCommunityList") || expandFields.contains("all")) { - List parentCommunities = communityService.getAllParents(context, collection); - for (org.dspace.content.Community parentCommunity : parentCommunities) { - this.addParentCommunityList(new Community(parentCommunity, servletContext, null, context)); - } - } else { - this.addExpand("parentCommunityList"); - } - - if (expandFields.contains("parentCommunity") | expandFields.contains("all")) { - org.dspace.content.Community parentCommunity = - (org.dspace.content.Community) collectionService - .getParentObject(context, collection); - this.setParentCommunity(new Community( - parentCommunity, servletContext, null, context)); - } else { - this.addExpand("parentCommunity"); - } - - //TODO: Item paging. limit, offset/page - if (expandFields.contains("items") || expandFields.contains("all")) { - Iterator childItems = - itemService.findByCollection(context, collection, limit, offset); - - items = new ArrayList<>(); - while (childItems.hasNext()) { - org.dspace.content.Item item = childItems.next(); - - if (itemService.isItemListedForUser(context, item)) { - items.add(new Item(item, servletContext, null, context)); - } - } - } else { - this.addExpand("items"); - } - - if (expandFields.contains("license") || expandFields.contains("all")) { - setLicense(collectionService.getLicense(collection)); - } else { - this.addExpand("license"); - } - - if (expandFields.contains("logo") || expandFields.contains("all")) { - if (collection.getLogo() != null) { - this.logo = new Bitstream(collection.getLogo(), servletContext, null, context); - } - } else { - this.addExpand("logo"); - } - - if (!expandFields.contains("all")) { - this.addExpand("all"); - } - - this.setNumberItems(itemService.countItems(context, collection)); - } - - public Bitstream getLogo() { - return logo; - } - - public Integer getNumberItems() { - return numberItems; - } - - public void setNumberItems(Integer numberItems) { - this.numberItems = numberItems; - } - - public Community getParentCommunity() { - return parentCommunity; - } - - public void setParentCommunity(Community parentCommunity) { - this.parentCommunity = parentCommunity; - } - - public List getItems() { - return items; - } - - public void setItems(List items) { - this.items = items; - } - - public void setParentCommunityList(List parentCommunityList) { - this.parentCommunityList = parentCommunityList; - } - - public List getParentCommunityList() { - return parentCommunityList; - } - - public void addParentCommunityList(Community parentCommunity) { - this.parentCommunityList.add(parentCommunity); - } - - public String getLicense() { - return license; - } - - public void setLicense(String license) { - this.license = license; - } - - public String getCopyrightText() { - return copyrightText; - } - - public void setCopyrightText(String copyrightText) { - this.copyrightText = copyrightText; - } - - public String getIntroductoryText() { - return introductoryText; - } - - public void setIntroductoryText(String introductoryText) { - this.introductoryText = introductoryText; - } - - public String getShortDescription() { - return shortDescription; - } - - public void setShortDescription(String shortDescription) { - this.shortDescription = shortDescription; - } - - public String getSidebarText() { - return sidebarText; - } - - public void setSidebarText(String sidebarText) { - this.sidebarText = sidebarText; - } -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/common/Community.java b/dspace-rest/src/main/java/org/dspace/rest/common/Community.java deleted file mode 100644 index e6e4716eab..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/common/Community.java +++ /dev/null @@ -1,217 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.rest.common; - -import static org.dspace.content.service.DSpaceObjectService.MD_COPYRIGHT_TEXT; -import static org.dspace.content.service.DSpaceObjectService.MD_INTRODUCTORY_TEXT; -import static org.dspace.content.service.DSpaceObjectService.MD_SHORT_DESCRIPTION; -import static org.dspace.content.service.DSpaceObjectService.MD_SIDEBAR_TEXT; - -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import javax.servlet.ServletContext; -import javax.ws.rs.WebApplicationException; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlRootElement; - -import org.apache.logging.log4j.Logger; -import org.dspace.authorize.factory.AuthorizeServiceFactory; -import org.dspace.authorize.service.AuthorizeService; -import org.dspace.content.factory.ContentServiceFactory; -import org.dspace.content.service.CommunityService; -import org.dspace.content.service.ItemService; -import org.dspace.core.Context; - -/** - * Created with IntelliJ IDEA. - * User: peterdietz - * Date: 5/22/13 - * Time: 9:41 AM - * To change this template use File | Settings | File Templates. - */ -@XmlRootElement(name = "community") -public class Community extends DSpaceObject { - protected CommunityService communityService = ContentServiceFactory.getInstance().getCommunityService(); - protected ItemService itemService = ContentServiceFactory.getInstance().getItemService(); - protected AuthorizeService authorizeService = AuthorizeServiceFactory.getInstance().getAuthorizeService(); - - private static Logger log = org.apache.logging.log4j.LogManager.getLogger(Community.class); - - //Exandable relationships - private Bitstream logo; - - private Community parentCommunity; - - private String copyrightText; - private String introductoryText; - private String shortDescription; - private String sidebarText; - private Integer countItems; - - private List subcommunities = new ArrayList<>(); - - private List collections = new ArrayList<>(); - - public Community() { - } - - public Community(org.dspace.content.Community community, ServletContext servletContext, String expand, - Context context) - throws SQLException, WebApplicationException { - super(community, servletContext); - setup(community, servletContext, expand, context); - } - - private void setup(org.dspace.content.Community community, ServletContext servletContext, String expand, - Context context) - throws SQLException { - List expandFields = new ArrayList<>(); - if (expand != null) { - expandFields = Arrays.asList(expand.split(",")); - } - - this.setCopyrightText(communityService.getMetadataFirstValue(community, - MD_COPYRIGHT_TEXT, org.dspace.content.Item.ANY)); - this.setIntroductoryText(communityService.getMetadataFirstValue(community, - MD_INTRODUCTORY_TEXT, org.dspace.content.Item.ANY)); - this.setShortDescription(communityService.getMetadataFirstValue(community, - MD_SHORT_DESCRIPTION, org.dspace.content.Item.ANY)); - this.setSidebarText(communityService.getMetadataFirstValue(community, - MD_SIDEBAR_TEXT, org.dspace.content.Item.ANY)); - this.setCountItems(itemService.countItems(context, community)); - - if (expandFields.contains("parentCommunity") || expandFields.contains("all")) { - org.dspace.content.Community parentCommunity = (org.dspace.content.Community) communityService - .getParentObject(context, community); - if (parentCommunity != null) { - setParentCommunity(new Community(parentCommunity, servletContext, null, context)); - } - } else { - this.addExpand("parentCommunity"); - } - - if (expandFields.contains("collections") || expandFields.contains("all")) { - List collections = community.getCollections(); - List restCollections = new ArrayList<>(); - - for (org.dspace.content.Collection collection : collections) { - if (authorizeService.authorizeActionBoolean(context, collection, org.dspace.core.Constants.READ)) { - restCollections.add(new Collection(collection, servletContext, null, context, null, null)); - } else { - log.info("Omitted restricted collection: " + collection.getID() + " _ " + collection.getName()); - } - } - setCollections(restCollections); - } else { - this.addExpand("collections"); - } - - if (expandFields.contains("subCommunities") || expandFields.contains("all")) { - List communities = community.getSubcommunities(); - subcommunities = new ArrayList<>(); - for (org.dspace.content.Community subCommunity : communities) { - if (authorizeService.authorizeActionBoolean(context, subCommunity, org.dspace.core.Constants.READ)) { - subcommunities.add(new Community(subCommunity, servletContext, null, context)); - } else { - log.info( - "Omitted restricted subCommunity: " + subCommunity.getID() + " _ " + subCommunity.getName()); - } - } - } else { - this.addExpand("subCommunities"); - } - - if (expandFields.contains("logo") || expandFields.contains("all")) { - if (community.getLogo() != null) { - logo = new Bitstream(community.getLogo(), servletContext, null, context); - } - } else { - this.addExpand("logo"); - } - - if (!expandFields.contains("all")) { - this.addExpand("all"); - } - } - - public List getCollections() { - return collections; - } - - public void setCollections(List collections) { - this.collections = collections; - } - - public Integer getCountItems() { - return countItems; - } - - public void setCountItems(Integer countItems) { - this.countItems = countItems; - } - - public String getSidebarText() { - return sidebarText; - } - - public void setSidebarText(String sidebarText) { - this.sidebarText = sidebarText; - } - - public String getShortDescription() { - return shortDescription; - } - - public void setShortDescription(String shortDescription) { - this.shortDescription = shortDescription; - } - - public String getIntroductoryText() { - return introductoryText; - } - - public void setIntroductoryText(String introductoryText) { - this.introductoryText = introductoryText; - } - - public String getCopyrightText() { - return copyrightText; - } - - public void setCopyrightText(String copyrightText) { - this.copyrightText = copyrightText; - } - - public Community getParentCommunity() { - return parentCommunity; - } - - public void setParentCommunity(Community parentCommunity) { - this.parentCommunity = parentCommunity; - } - - public Bitstream getLogo() { - return logo; - } - - public void setLogo(Bitstream logo) { - this.logo = logo; - } - - // Renamed because of xml annotation exception with this attribute and getSubCommunities. - @XmlElement(name = "subcommunities", required = true) - public List getSubcommunities() { - return subcommunities; - } - - public void setSubcommunities(List subcommunities) { - this.subcommunities = subcommunities; - } -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/common/DSpaceObject.java b/dspace-rest/src/main/java/org/dspace/rest/common/DSpaceObject.java deleted file mode 100644 index 08df254336..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/common/DSpaceObject.java +++ /dev/null @@ -1,107 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.rest.common; - -import java.util.ArrayList; -import java.util.List; -import javax.servlet.ServletContext; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlRootElement; - -import org.atteo.evo.inflector.English; -import org.dspace.content.factory.ContentServiceFactory; -import org.dspace.content.service.DSpaceObjectService; - -/** - * Created with IntelliJ IDEA. - * User: peterdietz - * Date: 10/7/13 - * Time: 12:11 PM - * To change this template use File | Settings | File Templates. - */ -@XmlRootElement(name = "dspaceobject") -public class DSpaceObject { - - private String uuid; - - private String name; - private String handle; - private String type; - - @XmlElement(name = "link", required = true) - private String link; - - @XmlElement(required = true) - private ArrayList expand = new ArrayList(); - - public DSpaceObject() { - - } - - public DSpaceObject(org.dspace.content.DSpaceObject dso, ServletContext servletContext) { - setUUID(dso.getID().toString()); - setName(dso.getName()); - setHandle(dso.getHandle()); - DSpaceObjectService dspaceObjectService = ContentServiceFactory.getInstance().getDSpaceObjectService(dso); - setType(dspaceObjectService.getTypeText(dso).toLowerCase()); - link = createLink(servletContext); - } - - public String getName() { - return this.name; - } - - public void setName(String name) { - this.name = name; - } - - public String getHandle() { - return handle; - } - - public void setHandle(String handle) { - this.handle = handle; - } - - public String getLink() { - return link; - } - - public String getType() { - return this.type; - } - - public void setType(String type) { - this.type = type; - } - - - public List getExpand() { - return expand; - } - - public void setExpand(ArrayList expand) { - this.expand = expand; - } - - public void addExpand(String expandableAttribute) { - this.expand.add(expandableAttribute); - } - - public String getUUID() { - return uuid; - } - - public void setUUID(String uuid) { - this.uuid = uuid; - } - - private String createLink(ServletContext context) { - return context.getContextPath() + "/" + English.plural(getType()) + "/" + getUUID(); - } -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/common/FilteredCollection.java b/dspace-rest/src/main/java/org/dspace/rest/common/FilteredCollection.java deleted file mode 100644 index c7ff0ef9b3..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/common/FilteredCollection.java +++ /dev/null @@ -1,191 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.rest.common; - -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Iterator; -import java.util.List; -import javax.servlet.ServletContext; -import javax.ws.rs.WebApplicationException; -import javax.xml.bind.annotation.XmlRootElement; - -import org.apache.logging.log4j.Logger; -import org.dspace.content.factory.ContentServiceFactory; -import org.dspace.content.service.CollectionService; -import org.dspace.content.service.CommunityService; -import org.dspace.content.service.ItemService; -import org.dspace.core.Context; -import org.dspace.rest.filter.ItemFilterSet; - -/** - * Retrieve items within a collection that match a specific set of Item Filters of interest - * - * @author Terry Brady, Georgetown University - */ -@XmlRootElement(name = "filtered-collection") -public class FilteredCollection extends DSpaceObject { - protected CommunityService communityService = ContentServiceFactory.getInstance().getCommunityService(); - protected CollectionService collectionService = ContentServiceFactory.getInstance().getCollectionService(); - protected ItemService itemService = ContentServiceFactory.getInstance().getItemService(); - Logger log = org.apache.logging.log4j.LogManager.getLogger(FilteredCollection.class); - - //Relationships - private Community parentCommunity; - private Community topCommunity; - private List parentCommunityList = new ArrayList(); - - private List items = new ArrayList(); - - private List itemFilters = new ArrayList(); - - //Calculated - private Integer numberItems; - private Integer numberItemsProcessed; - - public FilteredCollection() { - } - - /** - * Evaluate a collection against of set of Item Filters - * - * @param collection DSpace Collection to evaluate - * @param servletContext Context of the servlet container. - * @param filters String representing a list of filters - * @param expand String in which is what you want to add to returned instance - * of collection. Options are: "all", "parentCommunityList", - * "parentCommunity", "items", "license" and "logo". If you want - * to use multiple options, it must be separated by commas. - * @param context The relevant DSpace Context. - * @param limit Limit value for items in list in collection. Default value is 100. - * @param offset Offset of start index in list of items of collection. Default - * value is 0. - * @throws SQLException An exception that provides information on a database access error or other - * errors. - * @throws WebApplicationException Runtime exception for applications. - */ - public FilteredCollection(org.dspace.content.Collection collection, ServletContext servletContext, String filters, - String expand, Context context, Integer limit, Integer offset) - throws SQLException, WebApplicationException { - super(collection, servletContext); - setup(collection, servletContext, expand, context, limit, offset, filters); - } - - private void setup(org.dspace.content.Collection collection, ServletContext servletContext, String expand, - Context context, Integer limit, Integer offset, String filters) throws SQLException { - List expandFields = new ArrayList(); - if (expand != null) { - expandFields = Arrays.asList(expand.split(",")); - } - - if (expandFields.contains("parentCommunityList") || expandFields.contains("all")) { - List parentCommunities = communityService.getAllParents(context, collection); - List parentCommunityList = new ArrayList(); - for (org.dspace.content.Community parentCommunity : parentCommunities) { - parentCommunityList.add(new Community(parentCommunity, servletContext, null, context)); - } - this.setParentCommunityList(parentCommunityList); - } else { - this.addExpand("parentCommunityList"); - } - - if (expandFields.contains("parentCommunity") | expandFields.contains("all")) { - org.dspace.content.Community parentCommunity = collection.getCommunities().get(0); - this.setParentCommunity(new Community(parentCommunity, servletContext, null, context)); - } else { - this.addExpand("parentCommunity"); - } - - if (expandFields.contains("topCommunity") | expandFields.contains("all")) { - List parentCommunities = communityService.getAllParents(context, collection); - if (parentCommunities.size() > 0) { - org.dspace.content.Community topCommunity = parentCommunities.get(parentCommunities.size() - 1); - this.setTopCommunity(new Community(topCommunity, servletContext, null, context)); - } - } else { - this.addExpand("topCommunity"); - } - - - boolean reportItems = expandFields.contains("items") || expandFields.contains("all"); - ItemFilterSet itemFilterSet = new ItemFilterSet(filters, reportItems); - this.setItemFilters(itemFilterSet.getItemFilters()); - - this.setNumberItemsProcessed(0); - if (itemFilters.size() > 0) { - Iterator childItems = itemService - .findAllByCollection(context, collection, limit, offset); - int numProc = itemFilterSet - .processSaveItems(context, servletContext, childItems, items, reportItems, expand); - this.setNumberItemsProcessed(numProc); - } - - if (!expandFields.contains("all")) { - this.addExpand("all"); - } - this.setNumberItems(itemService.countAllItems(context, collection)); - } - - public Integer getNumberItems() { - return numberItems; - } - - public void setNumberItems(Integer numberItems) { - this.numberItems = numberItems; - } - - public Integer getNumberItemsProcessed() { - return numberItemsProcessed; - } - - public void setNumberItemsProcessed(Integer numberItemsProcessed) { - this.numberItemsProcessed = numberItemsProcessed; - } - - public Community getParentCommunity() { - return parentCommunity; - } - - public void setParentCommunity(Community parentCommunity) { - this.parentCommunity = parentCommunity; - } - - public Community getTopCommunity() { - return topCommunity; - } - - public void setTopCommunity(Community topCommunity) { - this.topCommunity = topCommunity; - } - - - public List getItems() { - return items; - } - - public void setItems(List items) { - this.items = items; - } - - public void setParentCommunityList(List parentCommunityList) { - this.parentCommunityList = parentCommunityList; - } - - public List getParentCommunityList() { - return parentCommunityList; - } - - public List getItemFilters() { - return itemFilters; - } - - public void setItemFilters(List itemFilters) { - this.itemFilters = itemFilters; - } -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/common/HierarchyCollection.java b/dspace-rest/src/main/java/org/dspace/rest/common/HierarchyCollection.java deleted file mode 100644 index 6c40faf62b..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/common/HierarchyCollection.java +++ /dev/null @@ -1,24 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.rest.common; - -import javax.xml.bind.annotation.XmlRootElement; - -/** - * Used to handle/determine status of REST API. - * Mainly to know your authentication status - */ -@XmlRootElement(name = "collection") -public class HierarchyCollection extends HierarchyObject { - public HierarchyCollection() { - } - - public HierarchyCollection(String id, String name, String handle) { - super(id, name, handle); - } -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/common/HierarchyCommunity.java b/dspace-rest/src/main/java/org/dspace/rest/common/HierarchyCommunity.java deleted file mode 100644 index 3618608e3e..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/common/HierarchyCommunity.java +++ /dev/null @@ -1,44 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.rest.common; - -import java.util.ArrayList; -import java.util.List; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlRootElement; - -@XmlRootElement(name = "community") -public class HierarchyCommunity extends HierarchyObject { - private List communities = new ArrayList(); - private List collections = new ArrayList(); - - public HierarchyCommunity() { - } - - public HierarchyCommunity(String id, String name, String handle) { - super(id, name, handle); - } - - @XmlElement(name = "community") - public List getCommunities() { - return communities; - } - - public void setCommunities(List communities) { - this.communities = communities; - } - - @XmlElement(name = "collection") - public List getCollections() { - return collections; - } - - public void setCollections(List collections) { - this.collections = collections; - } -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/common/HierarchyObject.java b/dspace-rest/src/main/java/org/dspace/rest/common/HierarchyObject.java deleted file mode 100644 index 0074eeea6a..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/common/HierarchyObject.java +++ /dev/null @@ -1,51 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.rest.common; - -import javax.xml.bind.annotation.XmlRootElement; - -@XmlRootElement(name = "object") -public class HierarchyObject { - //id may be a numeric id or a uuid depending on the version of DSpace - private String id; - private String name; - private String handle; - - public HierarchyObject() { - } - - public HierarchyObject(String id, String name, String handle) { - setId(id); - setName(name); - setHandle(handle); - } - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getHandle() { - return handle; - } - - public void setHandle(String handle) { - this.handle = handle; - } -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/common/HierarchySite.java b/dspace-rest/src/main/java/org/dspace/rest/common/HierarchySite.java deleted file mode 100644 index 5eb2cc523c..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/common/HierarchySite.java +++ /dev/null @@ -1,24 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.rest.common; - -import javax.xml.bind.annotation.XmlRootElement; - -/** - * Used to handle/determine status of REST API. - * Mainly to know your authentication status - */ -@XmlRootElement(name = "site") -public class HierarchySite extends HierarchyCommunity { - public HierarchySite() { - } - - public HierarchySite(String id, String name, String handle) { - super(id, name, handle); - } -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/common/Item.java b/dspace-rest/src/main/java/org/dspace/rest/common/Item.java deleted file mode 100644 index 3794153b7d..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/common/Item.java +++ /dev/null @@ -1,219 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.rest.common; - -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import javax.servlet.ServletContext; -import javax.ws.rs.WebApplicationException; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlRootElement; - -import org.apache.logging.log4j.Logger; -import org.dspace.app.util.factory.UtilServiceFactory; -import org.dspace.app.util.service.MetadataExposureService; -import org.dspace.authorize.factory.AuthorizeServiceFactory; -import org.dspace.authorize.service.AuthorizeService; -import org.dspace.content.Bundle; -import org.dspace.content.MetadataField; -import org.dspace.content.MetadataValue; -import org.dspace.content.factory.ContentServiceFactory; -import org.dspace.content.service.ItemService; -import org.dspace.core.Context; - -/** - * Created with IntelliJ IDEA. - * User: peterdietz - * Date: 9/19/13 - * Time: 4:50 PM - * To change this template use File | Settings | File Templates. - */ -@SuppressWarnings("deprecation") -@XmlRootElement(name = "item") -public class Item extends DSpaceObject { - protected ItemService itemService = ContentServiceFactory.getInstance().getItemService(); - protected MetadataExposureService metadataExposureService = UtilServiceFactory.getInstance() - .getMetadataExposureService(); - protected AuthorizeService authorizeService = AuthorizeServiceFactory.getInstance().getAuthorizeService(); - - Logger log = org.apache.logging.log4j.LogManager.getLogger(Item.class); - - String isArchived; - String isWithdrawn; - String lastModified; - - Collection parentCollection; - List parentCollectionList; - List parentCommunityList; - List metadata; - List bitstreams; - - public Item() { - } - - public Item(org.dspace.content.Item item, ServletContext servletContext, String expand, Context context) - throws SQLException, WebApplicationException { - super(item, servletContext); - setup(item, servletContext, expand, context); - } - - private void setup(org.dspace.content.Item item, ServletContext servletContext, String expand, Context context) - throws SQLException { - List expandFields = new ArrayList(); - if (expand != null) { - expandFields = Arrays.asList(expand.split(",")); - } - - if (expandFields.contains("metadata") || expandFields.contains("all")) { - metadata = new ArrayList(); - List metadataValues = itemService.getMetadata( - item, org.dspace.content.Item.ANY, org.dspace.content.Item.ANY, - org.dspace.content.Item.ANY, org.dspace.content.Item.ANY); - - for (MetadataValue metadataValue : metadataValues) { - MetadataField metadataField = metadataValue.getMetadataField(); - if (!metadataExposureService.isHidden(context, - metadataField.getMetadataSchema().getName(), - metadataField.getElement(), - metadataField.getQualifier())) { - metadata.add(new MetadataEntry(metadataField.toString('.'), - metadataValue.getValue(), metadataValue.getLanguage())); - } - } - } else { - this.addExpand("metadata"); - } - - this.setArchived(Boolean.toString(item.isArchived())); - this.setWithdrawn(Boolean.toString(item.isWithdrawn())); - this.setLastModified(item.getLastModified().toString()); - - if (expandFields.contains("parentCollection") || expandFields.contains("all")) { - if (item.getOwningCollection() != null) { - this.parentCollection = new Collection(item.getOwningCollection(), - servletContext, null, context, null, null); - } else { - this.addExpand("parentCollection"); - } - } else { - this.addExpand("parentCollection"); - } - - if (expandFields.contains("parentCollectionList") || expandFields.contains("all")) { - this.parentCollectionList = new ArrayList(); - List collections = item.getCollections(); - for (org.dspace.content.Collection collection : collections) { - this.parentCollectionList.add(new Collection(collection, - servletContext, null, context, null, null)); - } - } else { - this.addExpand("parentCollectionList"); - } - - if (expandFields.contains("parentCommunityList") || expandFields.contains("all")) { - this.parentCommunityList = new ArrayList(); - List communities = itemService.getCommunities(context, item); - - for (org.dspace.content.Community community : communities) { - this.parentCommunityList.add(new Community(community, servletContext, null, context)); - } - } else { - this.addExpand("parentCommunityList"); - } - - //TODO: paging - offset, limit - if (expandFields.contains("bitstreams") || expandFields.contains("all")) { - bitstreams = new ArrayList(); - - List bundles = item.getBundles(); - for (Bundle bundle : bundles) { - - List itemBitstreams = bundle.getBitstreams(); - for (org.dspace.content.Bitstream itemBitstream : itemBitstreams) { - if (authorizeService - .authorizeActionBoolean(context, itemBitstream, org.dspace.core.Constants.READ)) { - bitstreams.add(new Bitstream(itemBitstream, servletContext, null, context)); - } - } - } - } else { - this.addExpand("bitstreams"); - } - - if (!expandFields.contains("all")) { - this.addExpand("all"); - } - } - - public String getArchived() { - return isArchived; - } - - public void setArchived(String archived) { - isArchived = archived; - } - - public String getWithdrawn() { - return isWithdrawn; - } - - public void setWithdrawn(String withdrawn) { - isWithdrawn = withdrawn; - } - - public String getLastModified() { - return lastModified; - } - - public void setLastModified(String lastModified) { - this.lastModified = lastModified; - } - - public Collection getParentCollection() { - return parentCollection; - } - - public List getParentCollectionList() { - return parentCollectionList; - } - - public List getMetadata() { - return metadata; - } - - public List getBitstreams() { - return bitstreams; - } - - public List getParentCommunityList() { - return parentCommunityList; - } - - public void setParentCollection(Collection parentCollection) { - this.parentCollection = parentCollection; - } - - public void setParentCollectionList(List parentCollectionList) { - this.parentCollectionList = parentCollectionList; - } - - public void setParentCommunityList(List parentCommunityList) { - this.parentCommunityList = parentCommunityList; - } - - @XmlElement(required = true) - public void setMetadata(List metadata) { - this.metadata = metadata; - } - - public void setBitstreams(List bitstreams) { - this.bitstreams = bitstreams; - } -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/common/ItemFilter.java b/dspace-rest/src/main/java/org/dspace/rest/common/ItemFilter.java deleted file mode 100644 index bc5bd13134..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/common/ItemFilter.java +++ /dev/null @@ -1,274 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.rest.common; - -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.LinkedHashMap; -import java.util.List; -import javax.ws.rs.WebApplicationException; -import javax.xml.bind.annotation.XmlAttribute; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlRootElement; - -import org.apache.logging.log4j.Logger; -import org.dspace.core.Context; -import org.dspace.core.factory.CoreServiceFactory; -import org.dspace.rest.filter.ItemFilterDefs; -import org.dspace.rest.filter.ItemFilterList; -import org.dspace.rest.filter.ItemFilterTest; - - -/** - * Use Case Item Filters that match a specific set of criteria. - * - * @author Terry Brady, Georgetown University - */ -@XmlRootElement(name = "item-filter") -public class ItemFilter { - static Logger log = org.apache.logging.log4j.LogManager.getLogger(ItemFilter.class); - - private ItemFilterTest itemFilterTest = null; - private String filterName = ""; - private String title; - private String description; - private String category; - private String queryAnnotation; - private List items = new ArrayList(); - private List itemFilterQueries = new ArrayList(); - private List metadata = new ArrayList(); - private Integer itemCount; - private Integer unfilteredItemCount; - private boolean saveItems = false; - - public ItemFilter() { - } - - public static final String ALL_FILTERS = "all_filters"; - public static final String ALL = "all"; - - public static List getItemFilters(String filters, boolean saveItems) { - LinkedHashMap availableTests = new LinkedHashMap(); - for (ItemFilterList plugobj : - (ItemFilterList[]) CoreServiceFactory.getInstance() - .getPluginService().getPluginSequence(ItemFilterList.class)) { - for (ItemFilterTest defFilter : plugobj.getFilters()) { - availableTests.put(defFilter.getName(), defFilter); - } - } - List itemFilters = new ArrayList(); - ItemFilter allFilters = new ItemFilter(ItemFilter.ALL_FILTERS, "Matches all specified filters", - "This filter includes all items that matched ALL specified filters", - ItemFilterDefs.CAT_ITEM, saveItems); - - if (filters.equals(ALL)) { - for (ItemFilterTest itemFilterDef : availableTests.values()) { - itemFilters.add(new ItemFilter(itemFilterDef, saveItems)); - } - itemFilters.add(allFilters); - } else { - for (String filter : Arrays.asList(filters.split(","))) { - if (filter.equals(ItemFilter.ALL_FILTERS)) { - continue; - } - - ItemFilterTest itemFilterDef; - itemFilterDef = availableTests.get(filter); - if (itemFilterDef == null) { - continue; - } - itemFilters.add(new ItemFilter(itemFilterDef, saveItems)); - } - itemFilters.add(allFilters); - } - return itemFilters; - } - - public static ItemFilter getAllFiltersFilter(List itemFilters) { - for (ItemFilter itemFilter : itemFilters) { - if (itemFilter.getFilterName().equals(ALL_FILTERS)) { - itemFilter.initCount(); - return itemFilter; - } - } - return null; - } - - public ItemFilter(ItemFilterTest itemFilterTest, boolean saveItems) - throws WebApplicationException { - this.itemFilterTest = itemFilterTest; - this.saveItems = saveItems; - setup(itemFilterTest.getName(), itemFilterTest.getTitle(), - itemFilterTest.getDescription(), itemFilterTest.getCategory()); - } - - public ItemFilter(String name, String title, String description, String category, boolean saveItems) - throws WebApplicationException { - this.saveItems = saveItems; - setup(name, title, description, category); - } - - private void setup(String name, String title, String description, String category) { - this.setFilterName(name); - this.setTitle(title); - this.setDescription(description); - this.setCategory(category); - } - - private void initCount() { - if (itemCount == null) { - itemCount = 0; - } - if (unfilteredItemCount == null) { - unfilteredItemCount = 0; - } - } - - public boolean hasItemTest() { - return itemFilterTest != null; - } - - public void addItem(org.dspace.rest.common.Item restItem) { - initCount(); - if (saveItems) { - items.add(restItem); - } - itemCount++; - } - - public boolean testItem(Context context, org.dspace.content.Item item, org.dspace.rest.common.Item restItem) { - initCount(); - if (itemFilterTest == null) { - return false; - } - if (itemFilterTest.testItem(context, item)) { - addItem(restItem); - return true; - } - return false; - } - - @XmlAttribute(name = "filter-name") - public String getFilterName() { - return filterName; - } - - public void setFilterName(String name) { - this.filterName = name; - } - - @XmlAttribute(name = "title") - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } - - @XmlAttribute(name = "category") - public String getCategory() { - return category; - } - - public void setCategory(String category) { - this.category = category; - } - - @XmlAttribute(name = "description") - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - - @XmlAttribute(name = "query-annotation") - public String getQueryAnnotation() { - return queryAnnotation; - } - - public void annotateQuery(List query_field, List query_op, List query_val) - throws SQLException { - int index = Math.min(query_field.size(), Math.min(query_op.size(), query_val.size())); - StringBuilder sb = new StringBuilder(); - - for (int i = 0; i < index; i++) { - if (!sb.toString().isEmpty()) { - sb.append(" and "); - } - sb.append("("); - sb.append(query_field.get(i)); - sb.append(" "); - sb.append(query_op.get(i)); - sb.append(" "); - sb.append(query_val.get(i)); - sb.append(")"); - } - setQueryAnnotation(sb.toString()); - } - - public void setQueryAnnotation(String queryAnnotation) { - this.queryAnnotation = queryAnnotation; - } - - @XmlAttribute(name = "item-count") - public Integer getItemCount() { - return itemCount; - } - - public void setItemCount(Integer itemCount) { - this.itemCount = itemCount; - } - - @XmlAttribute(name = "unfiltered-item-count") - public Integer getUnfilteredItemCount() { - return unfilteredItemCount; - } - - public void setUnfilteredItemCount(Integer unfilteredItemCount) { - this.unfilteredItemCount = unfilteredItemCount; - } - - public List getItems() { - return items; - } - - public void setItems(List items) { - this.items = items; - } - - public List getItemFilterQueries() { - return itemFilterQueries; - } - - public void setItemFilterQueries(List itemFilterQueries) { - this.itemFilterQueries = itemFilterQueries; - } - - public void initMetadataList(List show_fields) { - if (show_fields != null) { - List returnFields = new ArrayList(); - for (String field : show_fields) { - returnFields.add(new MetadataEntry(field, null, null)); - } - setMetadata(returnFields); - } - } - - public List getMetadata() { - return metadata; - } - - @XmlElement(required = true) - public void setMetadata(List metadata) { - this.metadata = metadata; - } -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/common/ItemFilterQuery.java b/dspace-rest/src/main/java/org/dspace/rest/common/ItemFilterQuery.java deleted file mode 100644 index 6f56e2b44c..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/common/ItemFilterQuery.java +++ /dev/null @@ -1,77 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.rest.common; - -import javax.ws.rs.WebApplicationException; -import javax.xml.bind.annotation.XmlAttribute; -import javax.xml.bind.annotation.XmlRootElement; - -import org.apache.logging.log4j.Logger; - -/** - * Metadata Query for DSpace Items using the REST API - * - * @author Terry Brady, Georgetown University - */ -@XmlRootElement(name = "item-filter-query") -public class ItemFilterQuery { - Logger log = org.apache.logging.log4j.LogManager.getLogger(ItemFilterQuery.class); - - private String field = ""; - private String operation = ""; - private String value = ""; - - public ItemFilterQuery() { - } - - /** - * Construct a metadata query for DSpace items - * - * @param field Name of the metadata field to query - * @param operation Operation to perform on a metadata field - * @param value Query value. - * @throws WebApplicationException Runtime exception for applications. - */ - public ItemFilterQuery(String field, String operation, String value) throws WebApplicationException { - setup(field, operation, value); - } - - private void setup(String field, String operation, String value) { - this.setField(field); - this.setOperation(operation); - this.setValue(value); - } - - @XmlAttribute(name = "field") - public String getField() { - return field; - } - - public void setField(String field) { - this.field = field; - } - - @XmlAttribute(name = "operation") - public String getOperation() { - return operation; - } - - public void setOperation(String operation) { - this.operation = operation; - } - - @XmlAttribute(name = "value") - public String getValue() { - return value; - } - - public void setValue(String value) { - this.value = value; - } - -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/common/MetadataEntry.java b/dspace-rest/src/main/java/org/dspace/rest/common/MetadataEntry.java deleted file mode 100644 index 27f31cec9c..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/common/MetadataEntry.java +++ /dev/null @@ -1,77 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.rest.common; - -import java.util.regex.Pattern; -import javax.xml.bind.annotation.XmlRootElement; - -/** - * @author peterdietz, Rostislav Novak (Computing and Information Centre, CTU in - * Prague) - */ -@XmlRootElement(name = "metadataentry") -public class MetadataEntry { - String key; - - String value; - - String language; - - public MetadataEntry() { - } - - public MetadataEntry(String key, String value, String language) { - this.key = key; - this.value = value; - this.language = language; - } - - public String getValue() { - return value; - } - - public void setValue(String value) { - this.value = value; - } - - public String getKey() { - return key; - } - - public void setKey(String key) { - this.key = key; - } - - public String getLanguage() { - return language; - } - - public void setLanguage(String language) { - this.language = language; - } - - public String getSchema() { - String[] fieldPieces = key.split(Pattern.quote(".")); - return fieldPieces[0]; - } - - public String getElement() { - String[] fieldPieces = key.split(Pattern.quote(".")); - return fieldPieces[1]; - } - - public String getQualifier() { - String[] fieldPieces = key.split(Pattern.quote(".")); - if (fieldPieces.length == 3) { - return fieldPieces[2]; - } else { - return null; - } - } - -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/common/MetadataField.java b/dspace-rest/src/main/java/org/dspace/rest/common/MetadataField.java deleted file mode 100644 index 3688b5b8ca..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/common/MetadataField.java +++ /dev/null @@ -1,132 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.rest.common; - -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import javax.ws.rs.WebApplicationException; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlRootElement; - -import org.dspace.core.Context; - -/** - * Metadata field representation - * - * @author Terry Brady, Georgetown University. - */ -@XmlRootElement(name = "field") -public class MetadataField { - private int fieldId; - private String name; - private String element; - private String qualifier; - private String description; - - private MetadataSchema parentSchema; - - @XmlElement(required = true) - private ArrayList expand = new ArrayList(); - - public MetadataField() { - } - - public MetadataField(org.dspace.content.MetadataSchema schema, org.dspace.content.MetadataField field, - String expand, Context context) throws SQLException, WebApplicationException { - setup(schema, field, expand, context); - } - - private void setup(org.dspace.content.MetadataSchema schema, org.dspace.content.MetadataField field, String expand, - Context context) throws SQLException { - List expandFields = new ArrayList(); - if (expand != null) { - expandFields = Arrays.asList(expand.split(",")); - } - StringBuilder sb = new StringBuilder(); - sb.append(schema.getName()); - sb.append("."); - sb.append(field.getElement()); - if (field.getQualifier() != null) { - sb.append("."); - sb.append(field.getQualifier()); - } - - this.setName(sb.toString()); - this.setFieldId(field.getID()); - this.setElement(field.getElement()); - this.setQualifier(field.getQualifier()); - this.setDescription(field.getScopeNote()); - - if (expandFields.contains("parentSchema") || expandFields.contains("all")) { - this.addExpand("parentSchema"); - parentSchema = new MetadataSchema(schema, "", context); - } - } - - public void setParentSchema(MetadataSchema schema) { - this.parentSchema = schema; - } - - public MetadataSchema getParentSchema() { - return this.parentSchema; - } - - public void setFieldId(int fieldId) { - this.fieldId = fieldId; - } - - public void setName(String name) { - this.name = name; - } - - public void setElement(String element) { - this.element = element; - } - - public void setQualifier(String qualifier) { - this.qualifier = qualifier; - } - - public void setDescription(String description) { - this.description = description; - } - - public int getFieldId() { - return fieldId; - } - - public String getName() { - return name; - } - - public String getQualifier() { - return qualifier; - } - - public String getElement() { - return element; - } - - public String getDescription() { - return description; - } - - public List getExpand() { - return expand; - } - - public void setExpand(ArrayList expand) { - this.expand = expand; - } - - public void addExpand(String expandableAttribute) { - this.expand.add(expandableAttribute); - } -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/common/MetadataSchema.java b/dspace-rest/src/main/java/org/dspace/rest/common/MetadataSchema.java deleted file mode 100644 index 4b1e29fea2..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/common/MetadataSchema.java +++ /dev/null @@ -1,105 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.rest.common; - -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import javax.ws.rs.WebApplicationException; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlRootElement; - -import org.dspace.content.factory.ContentServiceFactory; -import org.dspace.content.service.MetadataFieldService; -import org.dspace.core.Context; - -/** - * Metadata schema representation - * - * @author Terry Brady, Georgetown University. - */ -@XmlRootElement(name = "schema") -public class MetadataSchema { - protected MetadataFieldService metadataFieldService = ContentServiceFactory.getInstance().getMetadataFieldService(); - - private int schemaID; - private String prefix; - private String namespace; - - @XmlElement(required = true) - private ArrayList expand = new ArrayList(); - - @XmlElement(name = "fields", required = true) - private List fields = new ArrayList(); - - public MetadataSchema() { - } - - public MetadataSchema(org.dspace.content.MetadataSchema schema, String expand, Context context) - throws SQLException, WebApplicationException { - setup(schema, expand, context); - } - - private void setup(org.dspace.content.MetadataSchema schema, String expand, Context context) throws SQLException { - List expandFields = new ArrayList(); - if (expand != null) { - expandFields = Arrays.asList(expand.split(",")); - } - this.setSchemaID(schema.getID()); - this.setPrefix(schema.getName()); - this.setNamespace(schema.getNamespace()); - if (expandFields.contains("fields") || expandFields.contains("all")) { - List fields = metadataFieldService.findAllInSchema(context, schema); - this.addExpand("fields"); - for (org.dspace.content.MetadataField field : fields) { - this.fields.add(new MetadataField(schema, field, "", context)); - } - } - } - - public void setPrefix(String prefix) { - this.prefix = prefix; - } - - public void setNamespace(String namespace) { - this.namespace = namespace; - } - - public String getPrefix() { - return prefix; - } - - public String getNamespace() { - return namespace; - } - - public int getSchemaID() { - return this.schemaID; - } - - public void setSchemaID(int schemaID) { - this.schemaID = schemaID; - } - - public List getMetadataFields() { - return fields; - } - - public List getExpand() { - return expand; - } - - public void setExpand(ArrayList expand) { - this.expand = expand; - } - - public void addExpand(String expandableAttribute) { - this.expand.add(expandableAttribute); - } -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/common/Report.java b/dspace-rest/src/main/java/org/dspace/rest/common/Report.java deleted file mode 100644 index dcaf7d269e..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/common/Report.java +++ /dev/null @@ -1,47 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.rest.common; - -import javax.xml.bind.annotation.XmlRootElement; - -/** - * Used to handle/determine status of REST API. - * Mainly to know your authentication status - */ -@XmlRootElement(name = "report") -public class Report { - private String nickname; - private String url; - - public Report() { - setNickname("na"); - setUrl(""); - } - - - public Report(String nickname, String url) { - setNickname(nickname); - setUrl(url); - } - - public String getUrl() { - return this.url; - } - - public String getNickname() { - return this.nickname; - } - - public void setUrl(String url) { - this.url = url; - } - - public void setNickname(String nickname) { - this.nickname = nickname; - } -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/common/ResourcePolicy.java b/dspace-rest/src/main/java/org/dspace/rest/common/ResourcePolicy.java deleted file mode 100644 index 366bd5fc3a..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/common/ResourcePolicy.java +++ /dev/null @@ -1,195 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.rest.common; - -import java.util.Date; -import javax.xml.bind.annotation.XmlRootElement; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import org.dspace.eperson.EPerson; -import org.dspace.eperson.Group; - -@XmlRootElement(name = "resourcepolicy") -public class ResourcePolicy { - - public enum Action { - READ, WRITE, DELETE; - } - - private Integer id; - private Action action; - private String epersonId; //UUID - private String groupId; //UUID - private String resourceId; //UUID - private String resourceType; - private String rpDescription; - private String rpName; - private String rpType; - private Date startDate; - private Date endDate; - - public ResourcePolicy() { - } - - public ResourcePolicy(org.dspace.authorize.ResourcePolicy dspacePolicy) { - this.id = dspacePolicy.getID(); - - switch (dspacePolicy.getAction()) { - case org.dspace.core.Constants.READ: - this.action = Action.READ; - break; - case org.dspace.core.Constants.WRITE: - this.action = Action.WRITE; - break; - case org.dspace.core.Constants.DELETE: - this.action = Action.DELETE; - break; - default: - break; - } - - EPerson ePerson = dspacePolicy.getEPerson(); - if (ePerson != null) { - this.epersonId = ePerson.getID().toString(); - } - - Group group = dspacePolicy.getGroup(); - if (group != null) { - this.groupId = group.getID().toString(); - } - - this.resourceId = dspacePolicy.getdSpaceObject().getID().toString(); - this.rpDescription = dspacePolicy.getRpDescription(); - this.rpName = dspacePolicy.getRpName(); - this.rpType = dspacePolicy.getRpType(); - this.startDate = dspacePolicy.getStartDate(); - this.endDate = dspacePolicy.getEndDate(); - switch (dspacePolicy.getdSpaceObject().getType()) { - case org.dspace.core.Constants.BITSTREAM: - this.resourceType = "bitstream"; - break; - case org.dspace.core.Constants.ITEM: - this.resourceType = "item"; - break; - case org.dspace.core.Constants.COLLECTION: - this.resourceType = "collection"; - break; - case org.dspace.core.Constants.COMMUNITY: - this.resourceType = "community"; - break; - case org.dspace.core.Constants.BUNDLE: - this.resourceType = "bundle"; - break; - default: - this.resourceType = ""; - break; - } - } - - public Integer getId() { - return id; - } - - public void setId(Integer id) { - this.id = id; - } - - public Action getAction() { - return action; - } - - @JsonIgnore - public int getActionInt() { - switch (action) { - case READ: - return org.dspace.core.Constants.READ; - case WRITE: - return org.dspace.core.Constants.WRITE; - case DELETE: - return org.dspace.core.Constants.DELETE; - default: - return org.dspace.core.Constants.READ; - } - } - - public void setAction(Action action) { - this.action = action; - } - - public String getEpersonId() { - return epersonId; - } - - public void setEpersonId(String epersonId) { - this.epersonId = epersonId; - } - - public String getGroupId() { - return groupId; - } - - public void setGroupId(String groupId) { - this.groupId = groupId; - } - - public String getResourceId() { - return resourceId; - } - - public void setResourceId(String resourceId) { - this.resourceId = resourceId; - } - - public String getResourceType() { - return resourceType; - } - - public void setResourceType(String resourceType) { - this.resourceType = resourceType; - } - - public String getRpDescription() { - return rpDescription; - } - - public void setRpDescription(String rpDescription) { - this.rpDescription = rpDescription; - } - - public String getRpName() { - return rpName; - } - - public void setRpName(String rpName) { - this.rpName = rpName; - } - - public String getRpType() { - return rpType; - } - - public void setRpType(String rpType) { - this.rpType = rpType; - } - - public Date getStartDate() { - return startDate; - } - - public void setStartDate(Date startDate) { - this.startDate = startDate; - } - - public Date getEndDate() { - return endDate; - } - - public void setEndDate(Date endDate) { - this.endDate = endDate; - } -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/common/Status.java b/dspace-rest/src/main/java/org/dspace/rest/common/Status.java deleted file mode 100644 index cdbb8210b9..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/common/Status.java +++ /dev/null @@ -1,111 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.rest.common; - -import javax.xml.bind.annotation.XmlRootElement; - -import com.fasterxml.jackson.annotation.JsonProperty; -import org.dspace.app.util.Util; -import org.dspace.eperson.EPerson; - -/** - * Determine status of REST API - is it running, accessible and without errors?. - * Find out API version (DSpace major version) and DSpace source version. - * Find out your authentication status. - */ -@XmlRootElement(name = "status") -public class Status { - private boolean okay; - private boolean authenticated; - private String email; - private String fullname; - private String sourceVersion; - private String apiVersion; - - public Status() { - setOkay(true); - - setSourceVersion(Util.getSourceVersion()); - String[] version = Util.getSourceVersion().split("\\."); - setApiVersion(version[0]); // major version - - setAuthenticated(false); - } - - public Status(String email, String fullname) { - setOkay(true); - setAuthenticated(true); - setEmail(email); - setFullname(fullname); - } - - public Status(EPerson eperson) { - setOkay(true); - if (eperson != null) { - setAuthenticated(true); - setEmail(eperson.getEmail()); - setFullname(eperson.getFullName()); - } else { - setAuthenticated(false); - } - } - - @JsonProperty("okay") - public boolean isOkay() { - return this.okay; - } - - public void setOkay(boolean okay) { - this.okay = okay; - } - - @JsonProperty("authenticated") - public boolean isAuthenticated() { - return authenticated; - } - - public void setAuthenticated(boolean authenticated) { - this.authenticated = authenticated; - } - - @JsonProperty("email") - public String getEmail() { - return email; - } - - public void setEmail(String email) { - this.email = email; - } - - @JsonProperty("fullname") - public String getFullname() { - return fullname; - } - - public void setFullname(String fullname) { - this.fullname = fullname; - } - - @JsonProperty("sourceVersion") - public String getSourceVersion() { - return this.sourceVersion; - } - - public void setSourceVersion(String sourceVersion) { - this.sourceVersion = sourceVersion; - } - - @JsonProperty("apiVersion") - public String getApiVersion() { - return this.apiVersion; - } - - public void setApiVersion(String apiVersion) { - this.apiVersion = apiVersion; - } -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/exceptions/ContextException.java b/dspace-rest/src/main/java/org/dspace/rest/exceptions/ContextException.java deleted file mode 100644 index 817b662f73..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/exceptions/ContextException.java +++ /dev/null @@ -1,31 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.rest.exceptions; - -/** - * Simple exception which only encapsulate classic exception. This exception is - * only for exceptions caused by creating context. - * - * @author Rostislav Novak (Computing and Information Centre, CTU in Prague) - */ -public class ContextException extends Exception { - - private static final long serialVersionUID = 1L; - - Exception causedBy; - - public ContextException(String message, Exception causedBy) { - super(message); - this.causedBy = causedBy; - } - - public Exception getCausedBy() { - return causedBy; - } - -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/filter/ItemFilterDefs.java b/dspace-rest/src/main/java/org/dspace/rest/filter/ItemFilterDefs.java deleted file mode 100644 index 0712ec546d..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/filter/ItemFilterDefs.java +++ /dev/null @@ -1,159 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.rest.filter; - -import org.dspace.content.Item; -import org.dspace.core.Context; - -/** - * Define the set of use cases for filtering items of interest through the REST API. - * - * @author Terry Brady, Georgetown University - */ - -public class ItemFilterDefs implements ItemFilterList { - public static final String CAT_ITEM = "Item Property Filters"; - public static final String CAT_BASIC = "Basic Bitstream Filters"; - public static final String CAT_MIME = "Bitstream Filters by MIME Type"; - - public static final String[] MIMES_PDF = {"application/pdf"}; - public static final String[] MIMES_JPG = {"image/jpeg"}; - - - private enum EnumItemFilterDefs implements ItemFilterTest { - is_item("Is Item - always true", null, CAT_ITEM) { - public boolean testItem(Context context, Item item) { - return true; - } - }, - is_withdrawn("Withdrawn Items", null, CAT_ITEM) { - public boolean testItem(Context context, Item item) { - return item.isWithdrawn(); - } - }, - is_not_withdrawn("Available Items - Not Withdrawn", null, CAT_ITEM) { - public boolean testItem(Context context, Item item) { - return !item.isWithdrawn(); - } - }, - is_discoverable("Discoverable Items - Not Private", null, CAT_ITEM) { - public boolean testItem(Context context, Item item) { - return item.isDiscoverable(); - } - }, - is_not_discoverable("Not Discoverable - Private Item", null, CAT_ITEM) { - public boolean testItem(Context context, Item item) { - return !item.isDiscoverable(); - } - }, - has_multiple_originals("Item has Multiple Original Bitstreams", null, CAT_BASIC) { - public boolean testItem(Context context, Item item) { - return ItemFilterUtil.countOriginalBitstream(item) > 1; - } - }, - has_no_originals("Item has No Original Bitstreams", null, CAT_BASIC) { - public boolean testItem(Context context, Item item) { - return ItemFilterUtil.countOriginalBitstream(item) == 0; - } - }, - has_one_original("Item has One Original Bitstream", null, CAT_BASIC) { - public boolean testItem(Context context, Item item) { - return ItemFilterUtil.countOriginalBitstream(item) == 1; - } - }, - has_doc_original("Item has a Doc Original Bitstream (PDF, Office, Text, HTML, XML, etc)", null, CAT_MIME) { - public boolean testItem(Context context, Item item) { - return ItemFilterUtil - .countOriginalBitstreamMime(context, item, ItemFilterUtil.getDocumentMimeTypes()) > 0; - } - }, - has_image_original("Item has an Image Original Bitstream", null, CAT_MIME) { - public boolean testItem(Context context, Item item) { - return ItemFilterUtil.countOriginalBitstreamMimeStartsWith(context, item, "image") > 0; - } - }, - has_unsupp_type("Has Other Bitstream Types (not Doc or Image)", null, ItemFilterDefs.CAT_MIME) { - public boolean testItem(Context context, Item item) { - int bitCount = ItemFilterUtil.countOriginalBitstream(item); - if (bitCount == 0) { - return false; - } - int docCount = ItemFilterUtil - .countOriginalBitstreamMime(context, item, ItemFilterUtil.getDocumentMimeTypes()); - int imgCount = ItemFilterUtil.countOriginalBitstreamMimeStartsWith(context, item, "image"); - return (bitCount - docCount - imgCount) > 0; - } - }, - has_mixed_original("Item has multiple types of Original Bitstreams (Doc, Image, Other)", null, CAT_MIME) { - public boolean testItem(Context context, Item item) { - int countBit = ItemFilterUtil.countOriginalBitstream(item); - if (countBit <= 1) { - return false; - } - int countDoc = ItemFilterUtil - .countOriginalBitstreamMime(context, item, ItemFilterUtil.getDocumentMimeTypes()); - if (countDoc > 0) { - return countDoc != countBit; - } - int countImg = ItemFilterUtil.countOriginalBitstreamMimeStartsWith(context, item, "image"); - if (countImg > 0) { - return countImg != countBit; - } - return false; - } - }, - has_pdf_original("Item has a PDF Original Bitstream", null, CAT_MIME) { - public boolean testItem(Context context, Item item) { - return ItemFilterUtil.countOriginalBitstreamMime(context, item, MIMES_PDF) > 0; - } - }, - has_jpg_original("Item has JPG Original Bitstream", null, CAT_MIME) { - public boolean testItem(Context context, Item item) { - return ItemFilterUtil.countOriginalBitstreamMime(context, item, MIMES_JPG) > 0; - } - },; - - private String title = null; - private String description = null; - - private EnumItemFilterDefs(String title, String description, String category) { - this.title = title; - this.description = description; - this.category = category; - } - - private EnumItemFilterDefs() { - this(null, null, null); - } - - public String getName() { - return name(); - } - - public String getTitle() { - return title; - } - - public String getDescription() { - return description; - } - - private String category = null; - - public String getCategory() { - return category; - } - } - - public ItemFilterDefs() { - } - - public ItemFilterTest[] getFilters() { - return EnumItemFilterDefs.values(); - } -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/filter/ItemFilterDefsMeta.java b/dspace-rest/src/main/java/org/dspace/rest/filter/ItemFilterDefsMeta.java deleted file mode 100644 index 96a866357d..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/filter/ItemFilterDefsMeta.java +++ /dev/null @@ -1,177 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.rest.filter; - -import java.util.regex.Pattern; - -import org.apache.logging.log4j.Logger; -import org.dspace.content.Item; -import org.dspace.content.factory.ContentServiceFactory; -import org.dspace.content.service.ItemService; -import org.dspace.core.Context; -import org.dspace.services.factory.DSpaceServicesFactory; - -/** - * Define the set of use cases for filtering items of interest through the REST API. - * - * @author Terry Brady, Georgetown University - */ - -public class ItemFilterDefsMeta implements ItemFilterList { - protected static ItemService itemService = ContentServiceFactory.getInstance().getItemService(); - static Logger log = org.apache.logging.log4j.LogManager.getLogger(ItemFilterDefsMeta.class); - - public static final String CAT_META_GEN = "General Metadata Filters"; - public static final String CAT_META_SPEC = "Specific Metadata Filters"; - public static final String CAT_MOD = "Recently Modified"; - - private enum EnumItemFilterDefs implements ItemFilterTest { - has_no_title("Has no dc.title", null, CAT_META_SPEC) { - public boolean testItem(Context context, Item item) { - return itemService.getMetadataByMetadataString(item, "dc.title").size() == 0; - } - }, - has_no_uri("Has no dc.identifier.uri", null, CAT_META_SPEC) { - public boolean testItem(Context context, Item item) { - return itemService.getMetadataByMetadataString(item, "dc.identifier.uri").size() == 0; - } - }, - has_mult_uri("Has multiple dc.identifier.uri", null, CAT_META_SPEC) { - public boolean testItem(Context context, Item item) { - return itemService.getMetadataByMetadataString(item, "dc.identifier.uri").size() > 1; - } - }, - has_compound_subject("Has compound subject", null, CAT_META_SPEC) { - public boolean testItem(Context context, Item item) { - String regex = DSpaceServicesFactory.getInstance().getConfigurationService() - .getProperty("rest.report-regex-compound-subject"); - return ItemFilterUtil.hasMetadataMatch(item, "dc.subject.*", Pattern.compile(regex)); - } - }, - has_compound_author("Has compound author", null, CAT_META_SPEC) { - public boolean testItem(Context context, Item item) { - String regex = DSpaceServicesFactory.getInstance().getConfigurationService() - .getProperty("rest.report-regex-compound-author"); - return ItemFilterUtil - .hasMetadataMatch(item, "dc.creator,dc.contributor.author", Pattern.compile(regex)); - } - }, - has_empty_metadata("Has empty metadata", null, CAT_META_GEN) { - public boolean testItem(Context context, Item item) { - return ItemFilterUtil.hasMetadataMatch(item, "*", Pattern.compile("^\\s*$")); - } - }, - has_unbreaking_metadata("Has unbreaking metadata", null, CAT_META_GEN) { - public boolean testItem(Context context, Item item) { - String regex = DSpaceServicesFactory.getInstance().getConfigurationService() - .getProperty("rest.report-regex-unbreaking"); - return ItemFilterUtil.hasMetadataMatch(item, "*", Pattern.compile(regex)); - } - }, - has_long_metadata("Has long metadata field", null, CAT_META_GEN) { - public boolean testItem(Context context, Item item) { - String regex = DSpaceServicesFactory.getInstance().getConfigurationService() - .getProperty("rest.report-regex-long"); - return ItemFilterUtil.hasMetadataMatch(item, "*", Pattern.compile(regex)); - } - }, - has_xml_entity("Has XML entity in metadata", null, CAT_META_GEN) { - public boolean testItem(Context context, Item item) { - String regex = DSpaceServicesFactory.getInstance().getConfigurationService() - .getProperty("rest.report-regex-xml-entity"); - return ItemFilterUtil.hasMetadataMatch(item, "*", Pattern.compile(regex)); - } - }, - has_non_ascii("Has non-ascii in metadata", null, CAT_META_GEN) { - public boolean testItem(Context context, Item item) { - String regex = DSpaceServicesFactory.getInstance().getConfigurationService() - .getProperty("rest.report-regex-non-ascii"); - return ItemFilterUtil.hasMetadataMatch(item, "*", Pattern.compile(regex)); - } - }, - has_desc_url("Has url in description", null, CAT_META_SPEC) { - public boolean testItem(Context context, Item item) { - String regex = DSpaceServicesFactory.getInstance().getConfigurationService() - .getProperty("rest.report-regex-url"); - return ItemFilterUtil.hasMetadataMatch(item, "dc.description.*", Pattern.compile(regex)); - } - }, - has_fulltext_provenance("Has fulltext in provenance", null, CAT_META_SPEC) { - public boolean testItem(Context context, Item item) { - String regex = DSpaceServicesFactory.getInstance().getConfigurationService() - .getProperty("rest.report-regex-fulltext"); - return ItemFilterUtil.hasMetadataMatch(item, "dc.description.provenance", Pattern.compile(regex)); - } - }, - no_fulltext_provenance("Doesn't have fulltext in provenance", null, CAT_META_SPEC) { - public boolean testItem(Context context, Item item) { - String regex = DSpaceServicesFactory.getInstance().getConfigurationService() - .getProperty("rest.report-regex-fulltext"); - return !ItemFilterUtil.hasMetadataMatch(item, "dc.description.provenance", Pattern.compile(regex)); - } - }, - mod_last_day("Modified in last 1 day", null, CAT_MOD) { - public boolean testItem(Context context, Item item) { - return ItemFilterUtil.recentlyModified(item, 1); - } - }, - mod_last_7_days("Modified in last 7 days", null, CAT_MOD) { - public boolean testItem(Context context, Item item) { - return ItemFilterUtil.recentlyModified(item, 7); - } - }, - mod_last_30_days("Modified in last 30 days", null, CAT_MOD) { - public boolean testItem(Context context, Item item) { - return ItemFilterUtil.recentlyModified(item, 30); - } - }, - mod_last_90_days("Modified in last 60 days", null, CAT_MOD) { - public boolean testItem(Context context, Item item) { - return ItemFilterUtil.recentlyModified(item, 60); - } - },; - - private String title = null; - private String description = null; - - private EnumItemFilterDefs(String title, String description, String category) { - this.title = title; - this.description = description; - this.category = category; - } - - private EnumItemFilterDefs() { - this(null, null, null); - } - - public String getName() { - return name(); - } - - public String getTitle() { - return title; - } - - public String getDescription() { - return description; - } - - private String category = null; - - public String getCategory() { - return category; - } - } - - public ItemFilterDefsMeta() { - } - - public ItemFilterTest[] getFilters() { - return EnumItemFilterDefs.values(); - } -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/filter/ItemFilterDefsMisc.java b/dspace-rest/src/main/java/org/dspace/rest/filter/ItemFilterDefsMisc.java deleted file mode 100644 index 5b5cc4b12d..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/filter/ItemFilterDefsMisc.java +++ /dev/null @@ -1,206 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.rest.filter; - -import java.util.List; - -import org.dspace.content.Item; -import org.dspace.core.Context; -import org.dspace.rest.filter.ItemFilterUtil.BundleName; -import org.dspace.services.factory.DSpaceServicesFactory; - -/** - * Define the set of use cases for filtering items of interest through the REST API. - * - * @author Terry Brady, Georgetown University - */ - -public class ItemFilterDefsMisc implements ItemFilterList { - public static final String CAT_MISC = "Bitstream Bundle Filters"; - public static final String CAT_MIME_SUPP = "Supported MIME Type Filters"; - - private enum EnumItemFilterDefs implements ItemFilterTest { - has_only_supp_image_type("Item Image Bitstreams are Supported", null, CAT_MIME_SUPP) { - public boolean testItem(Context context, Item item) { - int imageCount = ItemFilterUtil.countOriginalBitstreamMimeStartsWith(context, item, "image/"); - if (imageCount == 0) { - return false; - } - int suppImageCount = ItemFilterUtil - .countOriginalBitstreamMime(context, item, ItemFilterUtil.getSupportedImageMimeTypes()); - return (imageCount == suppImageCount); - } - }, - has_unsupp_image_type("Item has Image Bitstream that is Unsupported", null, CAT_MIME_SUPP) { - public boolean testItem(Context context, Item item) { - int imageCount = ItemFilterUtil.countOriginalBitstreamMimeStartsWith(context, item, "image/"); - if (imageCount == 0) { - return false; - } - int suppImageCount = ItemFilterUtil - .countOriginalBitstreamMime(context, item, ItemFilterUtil.getSupportedImageMimeTypes()); - return (imageCount - suppImageCount) > 0; - } - }, - has_only_supp_doc_type("Item Document Bitstreams are Supported", null, CAT_MIME_SUPP) { - public boolean testItem(Context context, Item item) { - int docCount = ItemFilterUtil - .countOriginalBitstreamMime(context, item, ItemFilterUtil.getDocumentMimeTypes()); - if (docCount == 0) { - return false; - } - int suppDocCount = ItemFilterUtil - .countOriginalBitstreamMime(context, item, ItemFilterUtil.getSupportedDocumentMimeTypes()); - return docCount == suppDocCount; - } - }, - has_unsupp_doc_type("Item has Document Bitstream that is Unsupported", null, CAT_MIME_SUPP) { - public boolean testItem(Context context, Item item) { - int docCount = ItemFilterUtil - .countOriginalBitstreamMime(context, item, ItemFilterUtil.getDocumentMimeTypes()); - if (docCount == 0) { - return false; - } - int suppDocCount = ItemFilterUtil - .countOriginalBitstreamMime(context, item, ItemFilterUtil.getSupportedDocumentMimeTypes()); - return (docCount - suppDocCount) > 0; - } - }, - has_small_pdf("Has unusually small PDF", null, ItemFilterDefs.CAT_MIME) { - public boolean testItem(Context context, Item item) { - return ItemFilterUtil - .countBitstreamSmallerThanMinSize(context, BundleName.ORIGINAL, item, ItemFilterDefs.MIMES_PDF, - "rest.report-pdf-min-size") > 0; - } - }, - has_large_pdf("Has unusually large PDF", null, ItemFilterDefs.CAT_MIME) { - public boolean testItem(Context context, Item item) { - return ItemFilterUtil - .countBitstreamLargerThanMaxSize(context, BundleName.ORIGINAL, item, ItemFilterDefs.MIMES_PDF, - "rest.report-pdf-max-size") > 0; - } - }, - has_unsupported_bundle("Has bitstream in an unsuppored bundle", null, CAT_MISC) { - public boolean testItem(Context context, Item item) { - String[] bundleList = DSpaceServicesFactory.getInstance().getConfigurationService() - .getArrayProperty("rest.report-supp-bundles"); - return ItemFilterUtil.hasUnsupportedBundle(item, bundleList); - } - }, - has_small_thumbnail("Has unusually small thumbnail", null, CAT_MISC) { - public boolean testItem(Context context, Item item) { - return ItemFilterUtil - .countBitstreamSmallerThanMinSize(context, BundleName.THUMBNAIL, item, ItemFilterDefs.MIMES_JPG, - "rest.report-thumbnail-min-size") > 0; - } - }, - has_doc_without_text("Has document bitstream without TEXT item", null, ItemFilterDefs.CAT_MIME) { - public boolean testItem(Context context, Item item) { - int countDoc = ItemFilterUtil - .countOriginalBitstreamMime(context, item, ItemFilterUtil.getDocumentMimeTypes()); - if (countDoc == 0) { - return false; - } - int countText = ItemFilterUtil.countBitstream(BundleName.TEXT, item); - return countDoc > countText; - } - }, - has_original_without_thumbnail("Has original bitstream without thumbnail", null, CAT_MISC) { - public boolean testItem(Context context, Item item) { - int countBit = ItemFilterUtil.countOriginalBitstream(item); - if (countBit == 0) { - return false; - } - int countThumb = ItemFilterUtil.countBitstream(BundleName.THUMBNAIL, item); - return countBit > countThumb; - } - }, - has_invalid_thumbnail_name("Has invalid thumbnail name (assumes one thumbnail for each original)", null, - CAT_MISC) { - public boolean testItem(Context context, Item item) { - List originalNames = ItemFilterUtil.getBitstreamNames(BundleName.ORIGINAL, item); - List thumbNames = ItemFilterUtil.getBitstreamNames(BundleName.THUMBNAIL, item); - if (thumbNames.size() != originalNames.size()) { - return false; - } - for (String name : originalNames) { - if (!thumbNames.contains(name + ".jpg")) { - return true; - } - } - return false; - } - }, - has_non_generated_thumb("Has non generated thumbnail", null, CAT_MISC) { - public boolean testItem(Context context, Item item) { - String[] generatedThumbDesc = DSpaceServicesFactory.getInstance().getConfigurationService() - .getArrayProperty("rest.report-gen-thumbnail-desc"); - int countThumb = ItemFilterUtil.countBitstream(BundleName.THUMBNAIL, item); - if (countThumb == 0) { - return false; - } - int countGen = ItemFilterUtil.countBitstreamByDesc(BundleName.THUMBNAIL, item, generatedThumbDesc); - return (countThumb > countGen); - } - }, - no_license("Doesn't have a license", null, CAT_MISC) { - public boolean testItem(Context context, Item item) { - return ItemFilterUtil.countBitstream(BundleName.LICENSE, item) == 0; - } - }, - has_license_documentation("Has documentation in the license bundle", null, CAT_MISC) { - public boolean testItem(Context context, Item item) { - List names = ItemFilterUtil.getBitstreamNames(BundleName.LICENSE, item); - for (String name : names) { - if (!name.equals("license.txt")) { - return true; - } - } - return false; - } - },; - - private String title = null; - private String description = null; - - private EnumItemFilterDefs(String title, String description, String category) { - this.title = title; - this.description = description; - this.category = category; - } - - private EnumItemFilterDefs() { - this(null, null, null); - } - - public String getName() { - return name(); - } - - public String getTitle() { - return title; - } - - public String getDescription() { - return description; - } - - private String category = null; - - public String getCategory() { - return category; - } - } - - public ItemFilterDefsMisc() { - } - - public ItemFilterTest[] getFilters() { - return EnumItemFilterDefs.values(); - } -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/filter/ItemFilterDefsPerm.java b/dspace-rest/src/main/java/org/dspace/rest/filter/ItemFilterDefsPerm.java deleted file mode 100644 index 9e80f31196..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/filter/ItemFilterDefsPerm.java +++ /dev/null @@ -1,138 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.rest.filter; - -import java.sql.SQLException; - -import org.apache.logging.log4j.Logger; -import org.dspace.authorize.factory.AuthorizeServiceFactory; -import org.dspace.authorize.service.AuthorizeService; -import org.dspace.content.Bitstream; -import org.dspace.content.Bundle; -import org.dspace.content.Item; -import org.dspace.core.Context; -import org.dspace.rest.filter.ItemFilterUtil.BundleName; - -/** - * Define the set of use cases for filtering items of interest through the REST API. - * - * @author Terry Brady, Georgetown University - */ -public class ItemFilterDefsPerm implements ItemFilterList { - protected static AuthorizeService authorizeService = AuthorizeServiceFactory.getInstance().getAuthorizeService(); - public static final String CAT_PERM = "Perimission Filters"; - private static Logger log = org.apache.logging.log4j.LogManager.getLogger(ItemFilterDefsPerm.class); - - public ItemFilterDefsPerm() { - } - - public enum EnumItemFilterPermissionDefs implements ItemFilterTest { - has_restricted_original("Item has Restricted Original Bitstream", - "Item has at least one original bitstream that is not accessible to Anonymous user", - CAT_PERM) { - public boolean testItem(Context context, Item item) { - try { - for (Bundle bundle : item.getBundles()) { - if (!bundle.getName().equals(BundleName.ORIGINAL.name())) { - continue; - } - for (Bitstream bit : bundle.getBitstreams()) { - if (!authorizeService - .authorizeActionBoolean(getAnonContext(), bit, org.dspace.core.Constants.READ)) { - return true; - } - } - } - } catch (SQLException e) { - ItemFilterDefsPerm.log.warn("SQL Exception testing original bitstream access " + e.getMessage(), e); - } - return false; - } - }, - has_restricted_thumbnail("Item has Restricted Thumbnail", - "Item has at least one thumbnail that is not accessible to Anonymous user", CAT_PERM) { - public boolean testItem(Context context, Item item) { - try { - for (Bundle bundle : item.getBundles()) { - if (!bundle.getName().equals(BundleName.THUMBNAIL.name())) { - continue; - } - for (Bitstream bit : bundle.getBitstreams()) { - if (!authorizeService - .authorizeActionBoolean(getAnonContext(), bit, org.dspace.core.Constants.READ)) { - return true; - } - } - } - } catch (SQLException e) { - ItemFilterDefsPerm.log - .warn("SQL Exception testing thumbnail bitstream access " + e.getMessage(), e); - } - return false; - } - }, - has_restricted_metadata("Item has Restricted Metadata", - "Item has metadata that is not accessible to Anonymous user", CAT_PERM) { - public boolean testItem(Context context, Item item) { - try { - return !authorizeService - .authorizeActionBoolean(getAnonContext(), item, org.dspace.core.Constants.READ); - } catch (SQLException e) { - ItemFilterDefsPerm.log.warn("SQL Exception testing item metadata access " + e.getMessage(), e); - return false; - } - } - },; - - private static Context anonContext; - - private static Context getAnonContext() { - if (anonContext == null) { - anonContext = new Context(); - } - return anonContext; - } - - - private String title = null; - private String description = null; - - private EnumItemFilterPermissionDefs(String title, String description, String category) { - this.title = title; - this.description = description; - this.category = category; - } - - private EnumItemFilterPermissionDefs() { - this(null, null, null); - } - - public String getName() { - return name(); - } - - public String getTitle() { - return title; - } - - public String getDescription() { - return description; - } - - private String category = null; - - public String getCategory() { - return category; - } - } - - @Override - public ItemFilterTest[] getFilters() { - return EnumItemFilterPermissionDefs.values(); - } -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/filter/ItemFilterSet.java b/dspace-rest/src/main/java/org/dspace/rest/filter/ItemFilterSet.java deleted file mode 100644 index f70bc9664d..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/filter/ItemFilterSet.java +++ /dev/null @@ -1,143 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.rest.filter; - -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import javax.servlet.ServletContext; -import javax.ws.rs.WebApplicationException; - -import org.apache.logging.log4j.Logger; -import org.dspace.authorize.factory.AuthorizeServiceFactory; -import org.dspace.authorize.service.AuthorizeService; -import org.dspace.core.Context; -import org.dspace.rest.common.Item; -import org.dspace.rest.common.ItemFilter; - -/** - * The set of Item Filter Use Cases to apply to a collection of items. - * - * @author Terry Brady, Georgetown University - */ -public class ItemFilterSet { - protected AuthorizeService authorizeService = AuthorizeServiceFactory.getInstance().getAuthorizeService(); - static Logger log = org.apache.logging.log4j.LogManager.getLogger(ItemFilterSet.class); - - private List itemFilters; - private ItemFilter allFiltersFilter; - - /** - * Construct a set of Item Filters identified by a list string. - * - * @param filterList Comma separated list of filter names to include. - * Use {@link org.dspace.rest.common.ItemFilter#ALL} to retrieve all filters. - * @param reportItems If true, return item details. If false, return only counts of items. - */ - public ItemFilterSet(String filterList, boolean reportItems) { - log.debug(String.format("Create ItemFilterSet: %s", filterList)); - itemFilters = ItemFilter.getItemFilters(filterList, reportItems); - allFiltersFilter = ItemFilter.getAllFiltersFilter(itemFilters); - } - - /** - * Get the special filter that represents the intersection of all items in the Item Filter Set. - * - * @return the special Item Filter that contains items that satisfied every other Item Filter in the Item Filter Set - */ - public ItemFilter getAllFiltersFilter() { - return allFiltersFilter; - } - - /** - * Evaluate an item against the use cases in the Item Filter Set. - * - * If an item satisfies all items in the Item Filter Set, it should also ve added to the special all items filter. - * - * @param context Active DSpace Context - * @param item DSpace Object to evaluate - * @param restItem REST representation of the DSpace Object being evaluated - */ - public void testItem(Context context, org.dspace.content.Item item, Item restItem) { - boolean bAllTrue = true; - for (ItemFilter itemFilter : itemFilters) { - if (itemFilter.hasItemTest()) { - bAllTrue &= itemFilter.testItem(context, item, restItem); - } - } - if (bAllTrue && allFiltersFilter != null) { - allFiltersFilter.addItem(restItem); - } - } - - /** - * Get all of the Item Filters initialized into the Item Filter Set - * - * @return a list of Item Filters initialized into the Item Filter Set - */ - public List getItemFilters() { - return itemFilters; - } - - /** - * Evaluate a set of Items against the Item Filters in the Item Filter Set - * Current DSpace Context - * - * @param context Current DSpace Context - * @param servletContext Context of the servlet container. - * @param childItems Collection of Items to Evaluate - * @param save If true, save the details of each item that is evaluated - * @param expand List of item details to include in the results - * @return The number of items evaluated - * @throws WebApplicationException Runtime exception for applications. - * @throws SQLException An exception that provides information on a database access error or other - * errors. - */ - public int processSaveItems(Context context, ServletContext servletContext, - Iterator childItems, boolean save, String expand) - throws WebApplicationException, SQLException { - return processSaveItems(context, servletContext, childItems, new ArrayList(), save, expand); - } - - /** - * Evaluate a set of Items against the Item Filters in the Item Filter Set - * - * @param context Current DSpace Context - * @param servletContext Context of the servlet container. - * @param childItems Collection of Items to Evaluate - * @param items List of items to contain saved results - * @param save If true, save the details of each item that is evaluated - * @param expand List of item details to include in the results - * @return The number of items evaluated - * @throws WebApplicationException Runtime exception for applications. - * @throws SQLException An exception that provides information on a database access error or other - * errors. - */ - public int processSaveItems(Context context, ServletContext servletContext, - Iterator childItems, List items, boolean save, - String expand) throws WebApplicationException, SQLException { - int count = 0; - while (childItems.hasNext()) { - count++; - org.dspace.content.Item item = childItems.next(); - log.debug(item.getHandle() + " evaluate."); - if (authorizeService.authorizeActionBoolean(context, item, org.dspace.core.Constants.READ)) { - Item restItem = new Item(item, servletContext, expand, context); - if (save) { - items.add(restItem); - } - testItem(context, item, restItem); - } else { - log.debug(item.getHandle() + " not authorized - not included in result set."); - } - } - return count; - } - -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/filter/ItemFilterTest.java b/dspace-rest/src/main/java/org/dspace/rest/filter/ItemFilterTest.java deleted file mode 100644 index 4ef2998e16..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/filter/ItemFilterTest.java +++ /dev/null @@ -1,29 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.rest.filter; - -import org.dspace.content.Item; -import org.dspace.core.Context; - -/** - * Item Filter Use Case Interface. - * Items will be evaluated against a set of filters. - * - * @author Terry Brady, Georgetown University - */ -public interface ItemFilterTest { - public String getName(); - - public String getTitle(); - - public String getDescription(); - - public String getCategory(); - - public boolean testItem(Context context, Item i); -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/filter/ItemFilterUtil.java b/dspace-rest/src/main/java/org/dspace/rest/filter/ItemFilterUtil.java deleted file mode 100644 index ddb75f0db8..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/filter/ItemFilterUtil.java +++ /dev/null @@ -1,278 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.rest.filter; - -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.List; -import java.util.regex.Pattern; - -import com.ibm.icu.util.Calendar; -import org.apache.logging.log4j.Logger; -import org.dspace.content.Bitstream; -import org.dspace.content.Bundle; -import org.dspace.content.Item; -import org.dspace.content.MetadataValue; -import org.dspace.content.factory.ContentServiceFactory; -import org.dspace.content.service.ItemService; -import org.dspace.core.Context; -import org.dspace.services.factory.DSpaceServicesFactory; - -public class ItemFilterUtil { - protected static ItemService itemService = ContentServiceFactory.getInstance().getItemService(); - static Logger log = org.apache.logging.log4j.LogManager.getLogger(ItemFilterUtil.class); - - public enum BundleName { ORIGINAL, TEXT, LICENSE, THUMBNAIL } - - /** - * Default constructor - */ - private ItemFilterUtil() { } - - static String[] getDocumentMimeTypes() { - return DSpaceServicesFactory.getInstance().getConfigurationService() - .getArrayProperty("rest.report-mime-document"); - } - - static String[] getSupportedDocumentMimeTypes() { - return DSpaceServicesFactory.getInstance().getConfigurationService() - .getArrayProperty("rest.report-mime-document-supported"); - } - - static String[] getSupportedImageMimeTypes() { - return DSpaceServicesFactory.getInstance().getConfigurationService() - .getArrayProperty("rest.report-mime-document-image"); - } - - static int countOriginalBitstream(Item item) { - return countBitstream(BundleName.ORIGINAL, item); - } - - static int countBitstream(BundleName bundleName, Item item) { - int count = 0; - for (Bundle bundle : item.getBundles()) { - if (!bundle.getName().equals(bundleName.name())) { - continue; - } - count += bundle.getBitstreams().size(); - } - - return count; - } - - static List getBitstreamNames(BundleName bundleName, Item item) { - ArrayList names = new ArrayList(); - for (Bundle bundle : item.getBundles()) { - if (!bundle.getName().equals(bundleName.name())) { - continue; - } - for (Bitstream bit : bundle.getBitstreams()) { - names.add(bit.getName()); - } - } - return names; - } - - - static int countOriginalBitstreamMime(Context context, Item item, String[] mimeList) { - return countBitstreamMime(context, BundleName.ORIGINAL, item, mimeList); - } - - static int countBitstreamMime(Context context, BundleName bundleName, Item item, String[] mimeList) { - int count = 0; - for (Bundle bundle : item.getBundles()) { - if (!bundle.getName().equals(bundleName.name())) { - continue; - } - for (Bitstream bit : bundle.getBitstreams()) { - for (String mime : mimeList) { - try { - if (bit.getFormat(context).getMIMEType().equals(mime.trim())) { - count++; - } - } catch (SQLException e) { - log.error("Get format error for bitstream " + bit.getName()); - } - } - } - } - return count; - } - - static int countBitstreamByDesc(BundleName bundleName, Item item, String[] descList) { - int count = 0; - for (Bundle bundle : item.getBundles()) { - if (!bundle.getName().equals(bundleName.name())) { - continue; - } - for (Bitstream bit : bundle.getBitstreams()) { - for (String desc : descList) { - String bitDesc = bit.getDescription(); - if (bitDesc == null) { - continue; - } - if (bitDesc.equals(desc.trim())) { - count++; - } - } - } - } - return count; - } - - static int countBitstreamSmallerThanMinSize(Context context, BundleName bundleName, Item item, String[] mimeList, - String prop) { - long size = DSpaceServicesFactory.getInstance().getConfigurationService().getLongProperty(prop); - int count = 0; - try { - for (Bundle bundle : item.getBundles()) { - if (!bundle.getName().equals(bundleName.name())) { - continue; - } - for (Bitstream bit : bundle.getBitstreams()) { - for (String mime : mimeList) { - if (bit.getFormat(context).getMIMEType().equals(mime.trim())) { - if (bit.getSizeBytes() < size) { - count++; - } - } - } - } - } - } catch (SQLException e) { - // ignore - } - return count; - } - - static int countBitstreamLargerThanMaxSize(Context context, BundleName bundleName, Item item, String[] mimeList, - String prop) { - long size = DSpaceServicesFactory.getInstance().getConfigurationService().getLongProperty(prop); - int count = 0; - try { - for (Bundle bundle : item.getBundles()) { - if (!bundle.getName().equals(bundleName.name())) { - continue; - } - for (Bitstream bit : bundle.getBitstreams()) { - for (String mime : mimeList) { - if (bit.getFormat(context).getMIMEType().equals(mime.trim())) { - if (bit.getSizeBytes() > size) { - count++; - } - } - } - } - } - } catch (SQLException e) { - // ignore - } - return count; - } - - static int countOriginalBitstreamMimeStartsWith(Context context, Item item, String prefix) { - return countBitstreamMimeStartsWith(context, BundleName.ORIGINAL, item, prefix); - } - - static int countBitstreamMimeStartsWith(Context context, BundleName bundleName, Item item, String prefix) { - int count = 0; - try { - for (Bundle bundle : item.getBundles()) { - if (!bundle.getName().equals(bundleName.name())) { - continue; - } - for (Bitstream bit : bundle.getBitstreams()) { - if (bit.getFormat(context).getMIMEType().startsWith(prefix)) { - count++; - } - } - } - } catch (SQLException e) { - // ignore - } - return count; - } - - static boolean hasUnsupportedBundle(Item item, String[] bundleList) { - if (bundleList == null) { - return false; - } - ArrayList bundles = new ArrayList(); - for (String bundleName : bundleList) { - bundles.add(bundleName.trim()); - } - for (Bundle bundle : item.getBundles()) { - if (!bundles.contains(bundle.getName())) { - return true; - } - } - return false; - } - - static boolean hasOriginalBitstreamMime(Context context, Item item, String[] mimeList) { - return hasBitstreamMime(context, BundleName.ORIGINAL, item, mimeList); - } - - static boolean hasBitstreamMime(Context context, BundleName bundleName, Item item, String[] mimeList) { - return countBitstreamMime(context, bundleName, item, mimeList) > 0; - } - - static boolean hasMetadataMatch(Item item, String fieldList, Pattern regex) { - if (fieldList.equals("*")) { - for (MetadataValue md : itemService - .getMetadata(item, org.dspace.content.Item.ANY, org.dspace.content.Item.ANY, - org.dspace.content.Item.ANY, org.dspace.content.Item.ANY)) { - if (regex.matcher(md.getValue()).matches()) { - return true; - } - } - } else { - for (String field : fieldList.split(",")) { - for (MetadataValue md : itemService.getMetadataByMetadataString(item, field.trim())) { - if (regex.matcher(md.getValue()).matches()) { - return true; - } - } - } - } - - return false; - } - - static boolean hasOnlyMetadataMatch(Item item, String fieldList, Pattern regex) { - boolean matches = false; - if (fieldList.equals("*")) { - for (MetadataValue md : itemService - .getMetadata(item, org.dspace.content.Item.ANY, org.dspace.content.Item.ANY, - org.dspace.content.Item.ANY, org.dspace.content.Item.ANY)) { - if (regex.matcher(md.getValue()).matches()) { - matches = true; - } else { - return false; - } - } - } else { - for (String field : fieldList.split(",")) { - for (MetadataValue md : itemService.getMetadataByMetadataString(item, field.trim())) { - if (regex.matcher(md.getValue()).matches()) { - matches = true; - } else { - return false; - } - } - } - } - return matches; - } - - static boolean recentlyModified(Item item, int days) { - Calendar cal = Calendar.getInstance(); - cal.add(Calendar.DATE, -days); - return cal.getTime().before(item.getLastModified()); - } -} diff --git a/dspace-rest/src/main/java/org/dspace/utils/DSpaceWebapp.java b/dspace-rest/src/main/java/org/dspace/utils/DSpaceWebapp.java deleted file mode 100644 index 5d3ce8bfa8..0000000000 --- a/dspace-rest/src/main/java/org/dspace/utils/DSpaceWebapp.java +++ /dev/null @@ -1,28 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ - -package org.dspace.utils; - -import org.dspace.app.util.AbstractDSpaceWebapp; - -/** - * An MBean to identify this web application. - * - * @author Bram Luyten (bram at atmire dot com) - */ -public class DSpaceWebapp - extends AbstractDSpaceWebapp { - public DSpaceWebapp() { - super("REST"); - } - - @Override - public boolean isUI() { - return false; - } -} diff --git a/dspace-rest/src/main/webapp/WEB-INF/applicationContext.xml b/dspace-rest/src/main/webapp/WEB-INF/applicationContext.xml deleted file mode 100644 index ec892fbaa4..0000000000 --- a/dspace-rest/src/main/webapp/WEB-INF/applicationContext.xml +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/dspace-rest/src/main/webapp/WEB-INF/security-applicationContext.xml b/dspace-rest/src/main/webapp/WEB-INF/security-applicationContext.xml deleted file mode 100644 index 677753d7f0..0000000000 --- a/dspace-rest/src/main/webapp/WEB-INF/security-applicationContext.xml +++ /dev/null @@ -1,80 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/dspace-rest/src/main/webapp/WEB-INF/web.xml b/dspace-rest/src/main/webapp/WEB-INF/web.xml deleted file mode 100644 index 34d74d9630..0000000000 --- a/dspace-rest/src/main/webapp/WEB-INF/web.xml +++ /dev/null @@ -1,119 +0,0 @@ - - - - - - dspace.request - org.dspace.utils.servlet.DSpaceWebappServletFilter - - - - dspace.request - /* - - - - - springSecurityFilterChain - org.springframework.web.filter.DelegatingFilterProxy - - - - springSecurityFilterChain - /* - - - - - DSpace REST API (Deprecated) - - org.glassfish.jersey.servlet.ServletContainer - - - javax.ws.rs.Application - org.dspace.rest.DSpaceRestApplication - - 1 - - - - DSpace REST API (Deprecated) - /* - - - - default - /static/* - - - - - - DSpace REST API (Deprecated) - /* - - - CONFIDENTIAL - - - - - - - The location of the DSpace home directory - - dspace.dir - ${dspace.dir} - - - - - The location of the Log4J configuration - - log4jConfiguration - ${dspace.dir}/config/log4j2.xml - - - - contextConfigLocation - - /WEB-INF/applicationContext.xml, - /WEB-INF/security-applicationContext.xml - - - - - org.dspace.app.util.DSpaceContextListener - - - - - org.dspace.servicemanager.servlet.DSpaceKernelServletContextListener - - - - - org.springframework.web.context.ContextLoaderListener - - - - - org.dspace.app.util.DSpaceWebappListener - - - - diff --git a/dspace-rest/src/main/webapp/static/reports/authenticate.html b/dspace-rest/src/main/webapp/static/reports/authenticate.html deleted file mode 100644 index 046ced425c..0000000000 --- a/dspace-rest/src/main/webapp/static/reports/authenticate.html +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - - - - - - - - - - Authenticate for the REST Report Tools - - -

    Login for an Authenticated Report View

    -
    This is intended for sites with Password Authentication Enabled
    - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - \ No newline at end of file diff --git a/dspace-rest/src/main/webapp/static/reports/index.html b/dspace-rest/src/main/webapp/static/reports/index.html deleted file mode 100644 index bc71b0417c..0000000000 --- a/dspace-rest/src/main/webapp/static/reports/index.html +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - - - - - - - - - - - DSpace REST QC Client - - -Query Tool - -
    -

    DSpace REST QC Client

    -
    -

    Filters

    -
    -
    - -
    -
    -
    -
    - -
    -
    -

    Collection Report

    - -

    Item Results

    -
    -

    Additional data to return

    -
    - -
    -
    -

    Bitstream data to return

    -
    -
    - -
    -

    Results

    -
    -

    - -
    - - - - Export will export one page of results -
    - - -
    -
    -
    -
    - - \ No newline at end of file diff --git a/dspace-rest/src/main/webapp/static/reports/query.html b/dspace-rest/src/main/webapp/static/reports/query.html deleted file mode 100644 index 5a7a79cb20..0000000000 --- a/dspace-rest/src/main/webapp/static/reports/query.html +++ /dev/null @@ -1,105 +0,0 @@ - - - - - - - - - - - - - - - DSpace REST Query Client - - -Collection Filter - -
    -

    DSpace REST Query Client

    -
    -
    -

    Collection Selector

    -
    -
    -

    Metadata Field Queries

    -
    -
    - - -
    -
    -
    -
    - -
    -
    -

    Limit/Paginate Queries

    -
    -
    - - - -
    -
    - -
    -
    -

    Filters

    -
    -
    - -
    -
    -
    -
    - -
    -
    -

    Additional data to return

    -
    -
    -
    - -
    -
    -

    Bitstream data to return

    -
    -
    - -
    -

    Item Results

    -
    -

    - -
    - - - - Export will export one page of results, increase result limits as needed -
    - -
    -
    -
    -
    - - \ No newline at end of file diff --git a/dspace-rest/src/main/webapp/static/reports/restClient.css b/dspace-rest/src/main/webapp/static/reports/restClient.css deleted file mode 100644 index d81724ae67..0000000000 --- a/dspace-rest/src/main/webapp/static/reports/restClient.css +++ /dev/null @@ -1,98 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -table {border-collapse: collapse;border-right:solid thin black;} -table td, table th {border: thin solid black; padding: 4px;} -tr.header {background-color: #EEEEEE;} -tr:hover td, tr:hover th {background-color: #DDDDDD;} -tr.even td {border-bottom: thin dotted black;} -tr.odd td {border-top: thin dotted black;} -td.even {background-color: #EEFFEE;} -td.head {background-color: #EEEEFF;} -td.num {text-align: right;} -td.link {text-decoration: underline; color: blue;} -td, th {width: 100px;} -td.error {color: red;background-color: yellow;} -td.title, th.title {width: 400px;} -td.mod, th.mod {width: 200px;} -#itemtable {width: 100%;} -#itemdiv {display: none;} -td.ititle, th.ititle {width: 600px;} -td.partial {color:red; font-style: italic;} - -button:disabled { - background-color:gray; -} -input:read-only { - background-color: gray; -} -div.metadata { - padding: 2px; - width: 880px; -} -#metadatadiv select, #metadatadiv input { - padding: 2px; - margin: 4px; -} -#metadatadiv fieldset { - margin: 6px 15px; - width: 850px; -} - -#metadatadiv label { - font-weight: bold; -} - -#itemtable td div:not(:first-child) { - border-top: thin solid gray; -} - -body { - min-height: 700px; - min-width: 700px; -} - -tr.header th { - vertical-align: bottom; -} - -a.partial::after { - content:" ?"; -} - -fieldset.catdiv { - border: thin solid black; - margin-bottom: 8px; -} - -fieldset.catdiv div { - width: 380px; - float: left; -} - -#collSel { - width: 90%; -} - -#filterdiv label { - font-weight: normal; -} - -.button { - background-color: #EEEEEE; -} - -.toobig::before { - content: "*"; -} -#exlimit { - font-style: italic; -} - -.red { - color: red; -} \ No newline at end of file diff --git a/dspace-rest/src/main/webapp/static/reports/restCollReport.js b/dspace-rest/src/main/webapp/static/reports/restCollReport.js deleted file mode 100644 index 8d800a8edc..0000000000 --- a/dspace-rest/src/main/webapp/static/reports/restCollReport.js +++ /dev/null @@ -1,510 +0,0 @@ -/* - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -var CollReport = function() { - Report.call(this); - //If sortable.js is included, uncomment the following - //this.hasSorttable = function(){return true;} - this.getLangSuffix = function(){ - return "[en]"; - }; - - //Indicate if Password Authentication is supported - //this.makeAuthLink = function(){return true;}; - //Indicate if Shibboleth Authentication is supported - //this.makeShibLink = function(){return true;}; - - this.COLL_LIMIT = 20; - this.TOOBIG = 10000; - this.loadId = 0; - this.THREADS =11; - this.THREADSP = 11; - this.ACCIDX_COLL = 1; - this.ACCIDX_ITEM = 2; - this.IACCIDX_META = 0; - this.IACCIDX_BIT = 1; - this.IACCIDX_ITEM = 2; - this.getDefaultParameters = function(){ - return { - "show_fields[]" : [], - "show_fields_bits[]" : [], - filters : "", - limit : this.COUNT_LIMIT, - offset : 0, - icollection : "", - ifilter : "", - }; - }; - this.getCurrentParameters = function(){ - return { - "show_fields[]" : this.myMetadataFields.getShowFields(), - "show_fields_bits[]" : this.myBitstreamFields.getShowFieldsBits(), - filters : this.myFilters.getFilterList(), - limit : this.myReportParameters.getLimit(), - offset : this.myReportParameters.getOffset(), - icollection : $("#icollection").val(), - ifilter : $("#ifilter").val(), - }; - }; - var self = this; - - this.init = function() { - this.baseInit(); - $("#icollection").val(self.myReportParameters.params.icollection); - $("#ifilter").val(self.myReportParameters.params.ifilter); - $("#itemResults").accordion({ - heightStyle: "content", - collapsible: true, - active: 2 - }); - }; - - this.myAuth.callback = function(data) { - self.createCollectionTable(); - $(".showCollections").bind("click", function(){ - self.loadData(); - }); - $("#refresh-fields,#refresh-fields-bits").bind("click", function(){ - self.drawItemTable($("#icollection").val(), $("#ifilter").val(), 0); - }); - }; - - this.createCollectionTable = function() { - var self = this; - var tbl = $(""); - tbl.attr("id","table"); - $("#report").replaceWith(tbl); - - var thead = $(""); - tbl.append(thead); - var tbody = $(""); - tbl.append(tbody); - var tr = self.myHtmlUtil.addTr(thead).addClass("header"); - self.myHtmlUtil.addTh(tr, "Num").addClass("num").addClass("sorttable_numeric"); - self.myHtmlUtil.addTh(tr, "Community").addClass("title"); - self.myHtmlUtil.addTh(tr, "Collection").addClass("title"); - var thn = self.myHtmlUtil.addTh(tr, "Num Items").addClass("sorttable_numeric"); - self.myHtmlUtil.makeTotalCol(thn); - thn = self.myHtmlUtil.addTh(tr, "Num Filtered").addClass("sorttable_numeric"); - self.myHtmlUtil.makeTotalCol(thn); - - self.addCollections(); - }; - - this.addCollections = function() { - var self = this; - - $.ajax({ - url: "/rest/hierarchy", - dataType: "json", - headers: self.myAuth.getHeaders(), - success: function(data){ - if (data.community != null) { - $.each(data.community, function(index, comm){ - self.addCommunity(comm, comm); - }); - } - self.setCollectionCounts(0); - }, - error: function(xhr, status, errorThrown) { - alert("Error in /rest/hierarchy "+ status+ " " + errorThrown); - } - }); - }; - - this.addCommunity = function(top, comm) { - var self = this; - - if (comm.collection != null) { - $.each(comm.collection, function(index, coll){ - self.addCollection(top, coll); - }); - } - if (comm.community != null) { - $.each(comm.community, function(index, scomm){ - self.addCommunity(top, scomm); - }); - } - }; - - this.addCollection = function(top, coll) { - var self = this; - - var tbody = $("#table tbody"); - var index = tbody.find("tr").length; - - var tr = self.myHtmlUtil.addTr(tbody); - tr.attr("cid", coll.id).attr("index",index).addClass(index % 2 == 0 ? "odd data" : "even data"); - self.myHtmlUtil.addTd(tr, index + 1).addClass("num"); - var parval = self.myHtmlUtil.getAnchor(top.name, self.ROOTPATH + top.handle); - - self.myHtmlUtil.addTd(tr, parval).addClass("title comm"); - self.myHtmlUtil.addTdAnchor(tr, coll.name, self.ROOTPATH + coll.handle).addClass("title"); - }; - - - this.setCollectionCounts = function(offset) { - var self = this; - - $.ajax({ - url: "/rest/filtered-collections", - data: { - limit : self.COLL_LIMIT, - offset : offset - }, - dataType: "json", - headers: self.myAuth.getHeaders(), - success: function(data){ - $.each(data, function(index, coll){ - var id = self.getId(coll); - var tr = $("#table tbody").find("tr[cid="+id+"]"); - var td = tr.find("td.numCount"); - td.text(coll.numberItems); - td.on("click", function(){ - self.drawItemTable(self.getId(coll),'',0); - $("#icollection").val(self.getId(coll)); - $("#ifilter").val(""); - }); - }); - - //cannot assume data returned is full amount in case some items are restricted - //if (data.length == self.COLL_LIMIT) { - if (data.length > 0) { - self.setCollectionCounts(offset + self.COLL_LIMIT); - return; - } - self.myHtmlUtil.totalCol(3); - $("#table").addClass("sortable"); - - if (self.myFilters.getFilterList() != "") { - self.loadData(); - if ($("#icollection").val() != "") { - self.drawItemTable($("#icollection").val(), $("#ifilter").val(), 0); - } - } - }, - error: function(xhr, status, errorThrown) { - alert("Error in /rest/collections "+ status+ " " + errorThrown); - }, - complete: function(xhr, status) { - self.spinner.stop(); - $(".showCollections").attr("disabled", false); - } - }); - }; - - this.loadData = function() { - self.spinner.spin($("h1")[0]); - $(".showCollections").attr("disabled", true); - $("#metadatadiv").accordion("option", "active", self.ACCIDX_COLL); - self.loadId++; - $("td.datacol,th.datacol").remove(); - $("#table tr.data").addClass("processing"); - self.myFilters.filterString = self.myFilters.getFilterList(); - self.doRow(0, self.THREADS, self.loadId); - }; - - this.doRow = function(row, threads, curLoadId) { - if (self.loadId != curLoadId) return; - var tr = $("tr[index="+row+"]"); - if (!tr.is("*")){ - return; - } - - var cid = tr.attr("cid"); - $.ajax({ - url: "/rest/filtered-collections/"+cid, - data: { - limit : self.COUNT_LIMIT, - filters : self.myFilters.filterString, - }, - dataType: "json", - headers: self.myAuth.getHeaders(), - success: function(data) { - var numItems = data.numberItems; - var numItemsProcessed = data.numberItemsProcessed; - $.each(data.itemFilters, function(index, itemFilter){ - if (self.loadId != curLoadId) { - return; - } - var trh = $("#table tr.header"); - var filterName = itemFilter["filter-name"]; - var filterTitle = itemFilter.title == null ? filterName : itemFilter.title; - if (!trh.find("th."+filterName).is("*")) { - var th = self.myHtmlUtil.addTh(trh, filterTitle); - th.addClass(filterName).addClass("datacol").addClass("sorttable_numeric"); - self.myHtmlUtil.makeTotalCol(th); - - if (itemFilter.description != null) { - th.attr("title", itemFilter.description); - } - - $("tr.data").each(function(){ - var td = self.myHtmlUtil.addTd($(this), ""); - td.addClass(filterName).addClass("num").addClass("datacol"); - }); - } - - self.setCellCount(tr, cid, 0, (numItems != numItemsProcessed), itemFilter); - self.setFilteredCount(tr, cid, 0, numItems, numItemsProcessed); - }); - - tr.removeClass("processing"); - if (!$("#table tr.processing").is("*")) { - self.updateSortable(); - self.totalFilters(); - self.spinner.stop(); - $(".showCollections").attr("disabled", false); - return; - } - if (row % threads == 0 || threads == 1) { - for(var i=1; i<=threads; i++) { - self.doRow(row+i, threads, curLoadId); - } - } - }, - error: function(xhr, status, errorThrown) { - alert("Error in /rest/filtered-collections "+ status+ " " + errorThrown); - }, - complete: function(xhr, status) { - self.spinner.stop(); - $(".showCollections").attr("disabled", false); - } - }); - }; - - this.updateSortable = function() { - if (self.hasSorttable()) { - $("#table").removeClass("sortable"); - $("#table").addClass("sortable"); - sorttable.makeSortable($("#table")[0]); - } - }; - - this.totalFilters = function() { - var colcount = $("#table tr th").length; - for(var i=4; i= self.TOOBIG) { - td.addClass("toobig"); - title+= "\nIt will take significant time to apply this filter to the entire collection."; - } - td.attr("title", title); - return false; - } else { - self.totalFilters(); - } - return true; - }; - - this.setCellCount = function(tr, cid, offset, isPartial, itemFilter) { - var filterName = itemFilter["filter-name"]; - var icount = itemFilter["item-count"]; - - var td = tr.find("td."+filterName); - if (icount == null) { - icount = 0; - } - var cur = parseInt(td.text()); - if (!isNaN(cur)) { - icount += cur; - } - - td.removeClass("partial"); - td.removeClass("link"); - td.removeAttr("title"); - td.off(); - td.text(icount); - if (icount != 0) { - td.addClass("link"); - if (isPartial) { - td.addClass("partial"); - td.attr("title", "Collection partially processed, item counts are incomplete"); - } - td.on("click", function(){ - self.drawItemTable(cid,filterName,0); - $("#icollection").val(cid); - $("#ifilter").val(filterName); - }); - } - }; - - - this.drawItemTable = function(cid, filter, offset) { - self = this; - self.spinner.spin($("h1")[0]); - $("#itemtable").replaceWith($('
    ')); - var itbl = $("#itemtable"); - //itbl.find("tr").remove("*"); - var tr = self.myHtmlUtil.addTr(itbl).addClass("header"); - self.myHtmlUtil.addTh(tr, "Num").addClass("num").addClass("sorttable_numeric"); - self.myHtmlUtil.addTh(tr, "id"); - self.myHtmlUtil.addTh(tr, "Handle"); - self.myHtmlUtil.addTh(tr, "dc.title" + self.getLangSuffix()).addClass("title"); - var fields = $("#show-fields select").val(); - if (fields != null) { - $.each(fields, function(index, field){ - self.myHtmlUtil.addTh(tr, field + self.getLangSuffix()); - }); - } - var bitfields = $("#show-fields-bits select").val(); - if (bitfields != null) { - $.each(bitfields, function(index, bitf){ - self.myHtmlUtil.addTh(tr, bitf); - }); - } - - var expand = "items"; - if (fields != null) { - expand += ",metadata"; - } - if (bitfields != null) { - expand += ",bitstreams"; - } - - var params = { - expand: expand, - limit: self.ITEM_LIMIT, - filters: filter, - offset: offset, - "show_fields[]" : fields, - "show_fields_bits[]" : bitfields, - }; - - $.ajax({ - url: "/rest/filtered-collections/"+cid, - data: params, - dataType: "json", - headers: self.myAuth.getHeaders(), - success: function(data){ - var source = filter == "" ? data.items : data.itemFilters[0].items; - - $.each(source, function(index, item){ - var tr = self.myHtmlUtil.addTr(itbl); - tr.addClass(index % 2 == 0 ? "odd data" : "even data"); - self.myHtmlUtil.addTd(tr, offset+index+1).addClass("num"); - self.myHtmlUtil.addTd(tr, self.getId(item)); - self.myHtmlUtil.addTdAnchor(tr, item.handle, self.ROOTPATH + item.handle); - self.myHtmlUtil.addTd(tr, item.name).addClass("ititle"); - if (fields != null) { - $.each(fields, function(index, field){ - var td = self.myHtmlUtil.addTd(tr, ""); - $.each(item.metadata, function(mindex,mv){ - if (mv.key == field) { - td.append($("
    "+mv.value+"
    ")); - } - }); - }); - } - if (bitfields != null) { - $.each(bitfields, function(index, bitfield){ - var td = self.myHtmlUtil.addTd(tr, ""); - var fieldtext = self.myBitstreamFields.getKeyText(bitfield, item, bitfields); - for(var j=0; j"+fieldtext[j]+"")); - } - }); - } - }); - self.displayItems(filter + " Items in " + data.name, - offset, - self.ITEM_LIMIT, - data.numberItems, - function(){self.drawItemTable(cid, filter, (offset - self.ITEM_LIMIT < 0) ? 0 : offset - self.ITEM_LIMIT);}, - function(){self.drawItemTable(cid, filter, offset + self.ITEM_LIMIT);} - ); - - if (self.hasSorttable()){ - sorttable.makeSortable(itbl[0]); - } - $("#metadatadiv").accordion("option", "active", self.ACCIDX_ITEM); - }, - error: function(xhr, status, errorThrown) { - alert("Error in /rest/filtered-collections "+ status+ " " + errorThrown); - }, - complete: function(xhr, status) { - self.spinner.stop(); - $(".showCollections").attr("disabled", false); - $("#itemResults").accordion("option", "active", self.IACCIDX_ITEM); - } - }); - }; - - //Ignore the first column containing a row number and the item handle - this.exportCol = function(colnum, col) { - var data = ""; - if (colnum == 0) return ""; - if (colnum == 2) return ""; - data += (colnum == 1) ? "" : ","; - data += self.exportCell(col); - return data; - }; -}; -CollReport.prototype = Object.create(Report.prototype); - -$(document).ready(function(){ - var myReport=new CollReport(); - myReport.init(); -}); \ No newline at end of file diff --git a/dspace-rest/src/main/webapp/static/reports/restQueryReport.js b/dspace-rest/src/main/webapp/static/reports/restQueryReport.js deleted file mode 100644 index 18e9a61d08..0000000000 --- a/dspace-rest/src/main/webapp/static/reports/restQueryReport.js +++ /dev/null @@ -1,350 +0,0 @@ -/* - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -var QueryReport = function() { - Report.call(this); - - //If sortable.js is included, uncomment the following - //this.hasSorttable = function(){return true;} - this.getLangSuffix = function(){ - return "[en]"; - }; - - //Indicate if Password Authentication is supported - //this.makeAuthLink = function(){return true;}; - //Indicate if Shibboleth Authentication is supported - //this.makeShibLink = function(){return true;}; - - this.getDefaultParameters = function(){ - return { - "collSel[]" : [], - "query_field[]" : [], - "query_op[]" : [], - "query_val[]" : [], - "show_fields[]" : [], - "show_fields_bits[]" : [], - "filters" : "", - "limit" : this.ITEM_LIMIT, - "offset" : 0, - }; - }; - this.getCurrentParameters = function(){ - var expand = "parentCollection,metadata"; - if (this.myBitstreamFields.hasBitstreamFields()) { - expand += ",bitstreams"; - } - var params = { - "query_field[]" : [], - "query_op[]" : [], - "query_val[]" : [], - "collSel[]" : ($("#collSel").val() == null) ? [""] : $("#collSel").val(), - limit : this.myReportParameters.getLimit(), - offset : this.myReportParameters.getOffset(), - "expand" : expand, - filters : this.myFilters.getFilterList(), - "show_fields[]" : this.myMetadataFields.getShowFields(), - "show_fields_bits[]" : this.myBitstreamFields.getShowFieldsBits(), - }; - $("select.query-tool,input.query-tool").each(function() { - var paramArr = params[$(this).attr("name")]; - paramArr[paramArr.length] = $(this).val(); - }); - return params; - }; - var self = this; - - this.init = function() { - this.baseInit(); - }; - - this.initMetadataFields = function() { - this.myMetadataFields = new QueryableMetadataFields(self); - this.myMetadataFields.load(); - }; - this.myAuth.callback = function(data) { - $(".query-button").click(function(){self.runQuery();}); - }; - - this.runQuery = function() { - this.spinner.spin($("body")[0]); - $("button").attr("disabled", true); - $.ajax({ - url: "/rest/filtered-items", - data: this.getCurrentParameters(), - dataType: "json", - headers: self.myAuth.getHeaders(), - success: function(data){ - data.metadata = $("#show-fields select").val(); - data.bitfields = $("#show-fields-bits select").val(); - self.drawItemFilterTable(data); - self.spinner.stop(); - $("button").not("#next,#prev").attr("disabled", false); - }, - error: function(xhr, status, errorThrown) { - alert("Error in /rest/filtered-items "+ status+ " " + errorThrown); - }, - complete: function(xhr, status, errorThrown) { - self.spinner.stop(); - $("button").not("#next,#prev").attr("disabled", false); - } - }); - }; - - this.drawItemFilterTable = function(data) { - $("#itemtable").replaceWith($('
    ')); - var itbl = $("#itemtable"); - var tr = self.myHtmlUtil.addTr(itbl).addClass("header"); - self.myHtmlUtil.addTh(tr, "Num").addClass("num").addClass("sorttable_numeric"); - self.myHtmlUtil.addTh(tr, "id"); - self.myHtmlUtil.addTh(tr, "collection"); - self.myHtmlUtil.addTh(tr, "Item Handle"); - self.myHtmlUtil.addTh(tr, "dc.title" + self.getLangSuffix()); - - var mdCols = []; - if (data.metadata) { - $.each(data.metadata, function(index, field) { - if (field != "") { - self.myHtmlUtil.addTh(tr,field + self.getLangSuffix()).addClass("returnFields"); - mdCols[mdCols.length] = field; - } - }); - } - - if (data.bitfields) { - $.each(data.bitfields, function(index, bitfield) { - if (bitfield != "") { - self.myHtmlUtil.addTh(tr,bitfield).addClass("returnFields"); - mdCols[mdCols.length] = bitfield; - } - }); - } - - $.each(data.items, function(index, item){ - var tr = self.myHtmlUtil.addTr(itbl); - tr.addClass(index % 2 == 0 ? "odd data" : "even data"); - self.myHtmlUtil.addTd(tr, self.myReportParameters.getOffset()+index+1).addClass("num"); - self.myHtmlUtil.addTd(tr, self.getId(item)); - if (item.parentCollection == null) { - self.myHtmlUtil.addTd(tr, "--"); - } else { - self.myHtmlUtil.addTdAnchor(tr, item.parentCollection.name, self.ROOTPATH + item.parentCollection.handle); - } - self.myHtmlUtil.addTdAnchor(tr, item.handle, self.ROOTPATH + item.handle); - self.myHtmlUtil.addTd(tr, item.name); - - for(var i=0; i"+metadata.value+""); - td.append(div); - } - } - }); - var fieldtext = self.myBitstreamFields.getKeyText(key, item, data.bitfields); - for(var j=0; j"+fieldtext[j]+"")); - } - } - }); - - this.displayItems(data["query-annotation"], - this.myReportParameters.getOffset(), - this.myReportParameters.getLimit(), - data["unfiltered-item-count"], - function(){ - self.myReportParameters.updateOffset(false); - self.runQuery(); - }, - function(){ - self.myReportParameters.updateOffset(true); - self.runQuery(); - } - ); - - if (this.hasSorttable()) { - sorttable.makeSortable(itbl[0]); - } - $("#metadatadiv").accordion("option", "active", $("#metadatadiv > h3").length - 1); - }; - - //Ignore the first column containing a row number and the item handle, get handle for the collection - this.exportCol = function(colnum, col) { - var data = ""; - if (colnum == 0) return ""; - if (colnum == 3) return ""; - data += (colnum == 1) ? "" : ","; - - if (colnum == 2) { - var anchor = $(col).find("a"); - var href = anchor.is("a") ? anchor.attr("href").replace(self.ROOTPATH,"") : $(col).text(); - data += "\"" + href + "\""; - } else { - data += self.exportCell(col); } - return data; - }; -}; -QueryReport.prototype = Object.create(Report.prototype); - -$(document).ready(function(){ - var myReport=new QueryReport(); - myReport.init(); -}); - -var QueryableMetadataFields = function(report) { - MetadataFields.call(this, report); - var self = this; - - this.initFields = function(data, report) { - self.metadataSchemas = data; - var params = report.myReportParameters.params; - var fields = params["query_field[]"]; - var ops = params["query_op[]"]; - var vals = params["query_val[]"]; - if (fields && ops && vals) { - if (fields.length == 0) { - self.drawFilterQuery("*","exists",""); - } else { - for(var i=0; i i ? ops[i] : ""; - var val = vals.length > i ? vals[i] : ""; - self.drawFilterQuery(fields[i],op,val); - } - } - } - self.drawShowFields(params["show_fields[]"]); - self.initQueries(); - report.spinner.stop(); - $(".query-button").attr("disabled", false); - }; - - this.initQueries = function() { - $("#predefselect") - .append($("")) - .append($("")) - .append($("")) - .append($("")) - .append($("")) - .append($("")) - .append($("")) - .append($("")) - .append($("")) - .append($("")) - .append($("")) - .append($("")) - .append($("")) - .on("change",function(){ - $("div.metadata").remove(); - var val = $("#predefselect").val(); - if (val == 'new') { - self.drawFilterQuery("","",""); - } else if (val == 'q1') { - self.drawFilterQuery("dc.title","doesnt_exist",""); - } else if (val == 'q2') { - self.drawFilterQuery("dc.identifier.uri","doesnt_exist",""); - } else if (val == 'q3') { - self.drawFilterQuery("dc.subject.*","like","%;%"); - } else if (val == 'q4') { - self.drawFilterQuery("dc.contributor.author","like","% and %"); - } else if (val == 'q5') { - self.drawFilterQuery("dc.creator","like","% and %"); - } else if (val == 'q6') { - self.drawFilterQuery("dc.description","matches","^.*(http://|https://|mailto:).*$"); - } else if (val == 'q7') { - self.drawFilterQuery("dc.description.provenance","matches","^.*No\\. of bitstreams(.|\\r|\\n|\\r\\n)*\\.(PDF|pdf|DOC|doc|PPT|ppt|DOCX|docx|PPTX|pptx).*$"); - } else if (val == 'q8') { - self.drawFilterQuery("dc.description.provenance","doesnt_match","^.*No\\. of bitstreams(.|\\r|\\n|\\r\\n)*\\.(PDF|pdf|DOC|doc|PPT|ppt|DOCX|docx|PPTX|pptx).*$"); - } else if (val == 'q9') { - self.drawFilterQuery("*","matches","^\\s*$"); - } else if (val == 'q10') { - self.drawFilterQuery("dc.description.*","matches","^.*[^\\s]{50,}.*$"); - } else if (val == 'q12') { - self.drawFilterQuery("*","matches","^.*&#.*$"); - } else if (val == 'q13') { - self.drawFilterQuery("*","matches","^.*[^[:ascii:]].*$"); - } - }); - }; - - this.drawFilterQuery = function(pField, pOp, pVal) { - var div = $("