diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 9893d233e16f..8f061cab8866 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -7,16 +7,16 @@ assignees: '' --- -**Describe the bug** +## Describe the bug A clear and concise description of what the bug is. Include the version(s) of DSpace where you've seen this problem. Link to examples if they are public. -**To Reproduce** +## To Reproduce Steps to reproduce the behavior: 1. Do this 2. Then this... -**Expected behavior** +## Expected behavior A clear and concise description of what you expected to happen. -**Related work** +## Related work Link to any related tickets or PRs here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 34cc2c9e4f38..9eaa4d9f3f8f 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -7,14 +7,14 @@ assignees: '' --- -**Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] +## Is your feature request related to a problem? Please describe. +A clear and concise description of what the problem or use case is. For example, I'm always frustrated when [...] -**Describe the solution you'd like** +## Describe the solution you'd like A clear and concise description of what you want to happen. -**Describe alternatives or workarounds you've considered** +## Describe alternatives or workarounds you've considered A clear and concise description of any alternative solutions or features you've considered. -**Additional context** -Add any other context or screenshots about the feature request here. +## Additional information +Add any other information, related tickets or screenshots about the feature request here. diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000000..b6412b25b660 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,118 @@ +#------------------- +# DSpace's dependabot rules. Enables maven updates for all dependencies on a weekly basis +# for main and any maintenance branches. Security updates only apply to main. +#------------------- +version: 2 +updates: + - package-ecosystem: "maven" + directory: "/" + schedule: + interval: "weekly" + # Allow up to 10 open PRs for dependencies + open-pull-requests-limit: 10 + # Group together some upgrades in a single PR + groups: + # Group together all Build Tools in a single PR + build-tools: + applies-to: version-updates + patterns: + - "org.apache.maven.plugins:*" + - "*:*-maven-plugin" + - "*:maven-*-plugin" + - "com.github.spotbugs:spotbugs" + - "com.google.code.findbugs:*" + - "com.google.errorprone:*" + - "com.puppycrawl.tools:checkstyle" + - "org.sonatype.plugins:*" + exclude-patterns: + # Exclude anything from Spring, as that is in a separate group + - "org.springframework.*:*" + update-types: + - "minor" + - "patch" + test-tools: + applies-to: version-updates + patterns: + - "junit:*" + - "com.github.stefanbirker:system-rules" + - "com.h2database:*" + - "io.findify:s3mock*" + - "io.netty:*" + - "org.hamcrest:*" + - "org.mock-server:*" + - "org.mockito:*" + update-types: + - "minor" + - "patch" + # Group together all Apache Commons deps in a single PR + apache-commons: + applies-to: version-updates + patterns: + - "org.apache.commons:*" + - "commons-*:commons-*" + update-types: + - "minor" + - "patch" + # Group together all fasterxml deps in a single PR + fasterxml: + applies-to: version-updates + patterns: + - "com.fasterxml:*" + - "com.fasterxml.*:*" + update-types: + - "minor" + - "patch" + # Group together all Hibernate deps in a single PR + hibernate: + applies-to: version-updates + patterns: + - "org.hibernate.*:*" + update-types: + - "minor" + - "patch" + # Group together all Jakarta deps in a single PR + jakarta: + applies-to: version-updates + patterns: + - "jakarta.*:*" + - "org.eclipse.angus:jakarta.mail" + - "org.glassfish.jaxb:jaxb-runtime" + update-types: + - "minor" + - "patch" + # Group together all Google deps in a single PR + google-apis: + applies-to: version-updates + patterns: + - "com.google.apis:*" + - "com.google.api-client:*" + - "com.google.http-client:*" + - "com.google.oauth-client:*" + update-types: + - "minor" + - "patch" + # Group together all Spring deps in a single PR + spring: + applies-to: version-updates + patterns: + - "org.springframework:*" + - "org.springframework.*:*" + update-types: + - "minor" + - "patch" + # Group together all WebJARs deps in a single PR + webjars: + applies-to: version-updates + patterns: + - "org.webjars:*" + - "org.webjars.*:*" + update-types: + - "minor" + - "patch" + ignore: + # Don't try to auto-update any DSpace dependencies + - dependency-name: "org.dspace:*" + - dependency-name: "org.dspace.*:*" + # Ignore all major version updates for all dependencies. We'll only automate minor/patch updates. + - dependency-name: "*" + update-types: ["version-update:semver-major"] diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 5b3f4336e6a2..4da35876d2ec 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,7 +1,7 @@ ## References _Add references/links to any related issues or PRs. These may include:_ -* Fixes #`issue-number` (if this fixes an issue ticket) -* Related to DSpace/RestContract#`pr-number` (if a corresponding REST Contract PR exists) +* Fixes #issue-number (if this fixes an issue ticket) +* Related to DSpace/RestContract#pr-number (if a corresponding REST Contract PR exists) ## Description Short summary of changes (1-2 sentences). @@ -16,12 +16,15 @@ List of changes in this PR: **Include guidance for how to test or review your PR.** This may include: steps to reproduce a bug, screenshots or description of a new feature, or reasons behind specific changes. ## Checklist -_This checklist provides a reminder of what we are going to look for when reviewing your PR. You need not complete this checklist prior to creating your PR (draft PRs are always welcome). If you are unsure about an item in the checklist, don't hesitate to ask. We're here to help!_ +_This checklist provides a reminder of what we are going to look for when reviewing your PR. You need not complete this checklist prior to creating your PR (draft PRs are always welcome). +However, reviewers may request that you complete any actions in this list if you have not done so. If you are unsure about an item in the checklist, don't hesitate to ask. We're here to help!_ -- [ ] My PR is small in size (e.g. less than 1,000 lines of code, not including comments & integration tests). Exceptions may be made if previously agreed upon. -- [ ] My PR passes Checkstyle validation based on the [Code Style Guide](https://wiki.lyrasis.org/display/DSPACE/Code+Style+Guide). -- [ ] My PR includes Javadoc for _all new (or modified) public methods and classes_. It also includes Javadoc for large or complex private methods. -- [ ] My PR passes all tests and includes new/updated Unit or Integration Tests based on the [Code Testing Guide](https://wiki.lyrasis.org/display/DSPACE/Code+Testing+Guide). +- [ ] My PR is **created against the `main` branch** of code (unless it is a backport or is fixing an issue specific to an older branch). +- [ ] My PR is **small in size** (e.g. less than 1,000 lines of code, not including comments & integration tests). Exceptions may be made if previously agreed upon. +- [ ] My PR **passes Checkstyle** validation based on the [Code Style Guide](https://wiki.lyrasis.org/display/DSPACE/Code+Style+Guide). +- [ ] My PR **includes Javadoc** for _all new (or modified) public methods and classes_. It also includes Javadoc for large or complex private methods. +- [ ] My PR **passes all tests and includes new/updated Unit or Integration Tests** based on the [Code Testing Guide](https://wiki.lyrasis.org/display/DSPACE/Code+Testing+Guide). +- [ ] My PR **includes details on how to test it**. I've provided clear instructions to reviewers on how to successfully test this fix or feature. - [ ] If my PR includes new libraries/dependencies (in any `pom.xml`), I've made sure their licenses align with the [DSpace BSD License](https://github.com/DSpace/DSpace/blob/main/LICENSE) based on the [Licensing of Contributions](https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines#CodeContributionGuidelines-LicensingofContributions) documentation. - [ ] If my PR modifies REST API endpoints, I've opened a separate [REST Contract](https://github.com/DSpace/RestContract/blob/main/README.md) PR related to this change. - [ ] If my PR includes new configurations, I've provided basic technical documentation in the PR itself. diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a2a0d6294f65..39a6f41429fd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,7 +24,7 @@ jobs: # 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 + 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. @@ -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 @@ -49,7 +49,7 @@ jobs: # https://github.com/actions/setup-java - name: Install JDK ${{ matrix.java }} - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: ${{ matrix.java }} distribution: 'temurin' @@ -65,14 +65,14 @@ jobs: # (This artifact is downloadable at the bottom of any job's summary page) - name: Upload Results of ${{ matrix.type }} to Artifact if: ${{ failure() }} - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ matrix.type }} results path: ${{ matrix.resultsdir }} # Upload code coverage report to artifact, so that it can be shared with the 'codecov' job (see below) - name: Upload code coverage report to Artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ matrix.type }} coverage report path: 'dspace/target/site/jacoco-aggregate/jacoco.xml' @@ -91,7 +91,7 @@ jobs: # Download artifacts from previous 'tests' job - name: Download coverage artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 # Now attempt upload to Codecov using its action. # NOTE: We use a retry action to retry the Codecov upload if it fails the first time. @@ -101,10 +101,11 @@ jobs: - name: Upload coverage to Codecov.io uses: Wandalen/wretry.action@v1.3.0 with: - action: codecov/codecov-action@v3 + action: codecov/codecov-action@v4 # Ensure codecov-action throws an error when it fails to upload with: | fail_ci_if_error: true + token: ${{ secrets.CODECOV_TOKEN }} # Try re-running action 5 times max attempt_limit: 5 # Run again in 30 seconds diff --git a/.github/workflows/codescan.yml b/.github/workflows/codescan.yml index 13bb0d2278ad..3a563c6fa39c 100644 --- a/.github/workflows/codescan.yml +++ b/.github/workflows/codescan.yml @@ -39,9 +39,9 @@ jobs: # https://github.com/actions/setup-java - name: Install JDK - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: - java-version: 11 + java-version: 17 distribution: 'temurin' # Initializes the CodeQL tools for scanning. diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 9f1e407cff4b..131cf956d55d 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -43,7 +43,7 @@ jobs: needs: dspace-dependencies uses: ./.github/workflows/reusable-docker-build.yml with: - build_id: dspace + build_id: dspace-prod image_name: dspace/dspace dockerfile_path: ./Dockerfile secrets: @@ -120,7 +120,7 @@ jobs: if: github.repository == 'dspace/dspace' uses: ./.github/workflows/reusable-docker-build.yml with: - build_id: dspace-postgres-pgcrypto + build_id: dspace-postgres-pgcrypto-prod image_name: dspace/dspace-postgres-pgcrypto # Must build out of subdirectory to have access to install script for pgcrypto. # NOTE: this context will build the image based on the Dockerfile in the specified directory @@ -147,4 +147,97 @@ jobs: tags_flavor: suffix=-loadsql secrets: DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} - DOCKER_ACCESS_TOKEN: ${{ secrets.DOCKER_ACCESS_TOKEN }} \ No newline at end of file + DOCKER_ACCESS_TOKEN: ${{ secrets.DOCKER_ACCESS_TOKEN }} + + ################################################################################# + # Test Deployment via Docker to ensure newly built images are working properly + ################################################################################# + docker-deploy: + # Ensure this job never runs on forked repos. It's only executed for 'dspace/dspace' + if: github.repository == 'dspace/dspace' + runs-on: ubuntu-latest + # Must run after all major images are built + needs: [dspace, dspace-test, dspace-cli, dspace-postgres-pgcrypto, dspace-solr] + env: + # Override defaults dspace.server.url because backend starts at http://127.0.0.1:8080 + dspace__P__server__P__url: http://127.0.0.1:8080/server + # Enable all optional modules / controllers for this test deployment. + # This helps check for errors in deploying these modules via Spring Boot + iiif__P__enabled: true + ldn__P__enabled: true + oai__P__enabled: true + rdf__P__enabled: true + signposting__P__enabled: true + sword__D__server__P__enabled: true + swordv2__D__server__P__enabled: true + # If this is a PR, force using "pr-testing" version of all Docker images. Otherwise, if on main branch, use the + # "latest" tag. Otherwise, use the branch name. NOTE: the "pr-testing" tag is a temporary tag that we assign to + # all PR-built docker images in reusable-docker-build.yml + DSPACE_VER: ${{ (github.event_name == 'pull_request' && 'pr-testing') || (github.ref_name == github.event.repository.default_branch && 'latest') || github.ref_name }} + steps: + # Checkout our codebase (to get access to Docker Compose scripts) + - name: Checkout codebase + uses: actions/checkout@v4 + # Download Docker image artifacts (which were just built by reusable-docker-build.yml) + - name: Download Docker image artifacts + uses: actions/download-artifact@v4 + with: + # Download all amd64 Docker images (TAR files) into the /tmp/docker directory + pattern: docker-image-*-linux-amd64 + path: /tmp/docker + merge-multiple: true + # Load each of the images into Docker by calling "docker image load" for each. + # This ensures we are using the images just built & not any prior versions on DockerHub + - name: Load all downloaded Docker images + run: | + find /tmp/docker -type f -name "*.tar" -exec docker image load --input "{}" \; + docker image ls -a + # Start backend using our compose script in the codebase. + - name: Start backend in Docker + run: | + docker compose -f docker-compose.yml up -d + sleep 10 + docker container ls + # Create a test admin account. Load test data from a simple set of AIPs as defined in cli.ingest.yml + - name: Load test data into Backend + run: | + docker compose -f docker-compose-cli.yml run --rm dspace-cli create-administrator -e test@test.edu -f admin -l user -p admin -c en + docker compose -f docker-compose-cli.yml -f dspace/src/main/docker-compose/cli.ingest.yml run --rm dspace-cli + # Verify backend started successfully. + # 1. Make sure root endpoint is responding (check for dspace.name defined in docker-compose.yml) + # 2. Also check /collections endpoint to ensure the test data loaded properly (check for a collection name in AIPs) + - name: Verify backend is responding properly + run: | + result=$(wget -O- -q http://127.0.0.1:8080/server/api) + echo "$result" + echo "$result" | grep -oE "\"DSpace Started with Docker Compose\"," + result=$(wget -O- -q http://127.0.0.1:8080/server/api/core/collections) + echo "$result" + echo "$result" | grep -oE "\"Dog in Yard\"," + # Verify Handle Server can be stared and is working properly + # 1. First generate the "[dspace]/handle-server" folder with the sitebndl.zip + # 2. Start the Handle Server (and wait 20 seconds to let it start up) + # 3. Verify logs do NOT include "Exception" in the text (as that means an error occurred) + # 4. Check that Handle Proxy HTML page is responding on default port (8000) + - name: Verify Handle Server is working properly + run: | + docker exec -i dspace /dspace/bin/make-handle-config + echo "Starting Handle Server..." + docker exec -i dspace /dspace/bin/start-handle-server + sleep 20 + echo "Checking for errors in error.log" + result=$(docker exec -i dspace sh -c "cat /dspace/handle-server/logs/error.log* || echo ''") + echo "$result" + echo "$result" | grep -vqz "Exception" + echo "Checking for errors in handle-server.log..." + result=$(docker exec -i dspace cat /dspace/log/handle-server.log) + echo "$result" + echo "$result" | grep -vqz "Exception" + echo "Checking to see if Handle Proxy webpage is available..." + result=$(wget -O- -q http://127.0.0.1:8000/) + echo "$result" + echo "$result" | grep -oE "Handle Proxy" + # Shutdown our containers + - name: Shutdown Docker containers + run: | + docker compose -f docker-compose.yml down diff --git a/.github/workflows/issue_opened.yml b/.github/workflows/issue_opened.yml index b4436dca3aad..0a35a6a95044 100644 --- a/.github/workflows/issue_opened.yml +++ b/.github/workflows/issue_opened.yml @@ -16,7 +16,7 @@ jobs: # Only add to project board if issue is flagged as "needs triage" or has no labels # NOTE: By default we flag new issues as "needs triage" in our issue template if: (contains(github.event.issue.labels.*.name, 'needs triage') || join(github.event.issue.labels.*.name) == '') - uses: actions/add-to-project@v0.5.0 + uses: actions/add-to-project@v1.0.0 # Note, the authentication token below is an ORG level Secret. # It must be created/recreated manually via a personal access token with admin:org, project, public_repo permissions # See: https://docs.github.com/en/actions/configuring-and-managing-workflows/authenticating-with-the-github_token#permissions-for-the-github_token diff --git a/.github/workflows/pull_request_opened.yml b/.github/workflows/pull_request_opened.yml index f16e81c9fd25..bbac52af2438 100644 --- a/.github/workflows/pull_request_opened.yml +++ b/.github/workflows/pull_request_opened.yml @@ -21,4 +21,4 @@ jobs: # Assign the PR to whomever created it. This is useful for visualizing assignments on project boards # See https://github.com/toshimaru/auto-author-assign - name: Assign PR to creator - uses: toshimaru/auto-author-assign@v2.0.1 + uses: toshimaru/auto-author-assign@v2.1.0 diff --git a/.github/workflows/reusable-docker-build.yml b/.github/workflows/reusable-docker-build.yml index aa8327f4d11b..365d1e89e1e0 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: @@ -93,30 +93,45 @@ jobs: runs-on: ${{ matrix.os }} steps: + # This step converts the slashes in the "arch" matrix values above into dashes & saves to env.ARCH_NAME + # E.g. "linux/amd64" becomes "linux-amd64" + # This is necessary because all upload artifacts CANNOT have special chars (like slashes) + - name: Prepare + run: | + platform=${{ matrix.arch }} + echo "ARCH_NAME=${platform//\//-}" >> $GITHUB_ENV + # https://github.com/actions/checkout - name: Checkout codebase uses: actions/checkout@v4 - # https://github.com/docker/setup-buildx-action - - name: Setup Docker Buildx - uses: docker/setup-buildx-action@v3 - - # https://github.com/docker/setup-qemu-action - - name: Set up QEMU emulation to build for multiple architectures - uses: docker/setup-qemu-action@v3 - # https://github.com/docker/login-action - name: Login to DockerHub - # Only login if not a PR, as PRs only trigger a Docker build and not a push + # Only login if not a PR, as PRs only trigger a Docker build and not a push if: ${{ ! matrix.isPr }} uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_ACCESS_TOKEN }} + # https://github.com/docker/setup-qemu-action + - name: Set up QEMU emulation to build for multiple architectures + uses: docker/setup-qemu-action@v3 + + # https://github.com/docker/setup-buildx-action + - name: Setup Docker Buildx + uses: docker/setup-buildx-action@v3 + + #------------------------------------------------------------ + # Build & deploy steps for new commits to a branch (non-PRs) + # + # These steps build the images, push to DockerHub, and + # (if necessary) redeploy demo/sandbox sites. + #------------------------------------------------------------ # https://github.com/docker/metadata-action # Get Metadata for docker_build_deps step below - - name: Sync metadata (tags, labels) from GitHub to Docker for image + - name: Extract metadata (tags, labels) from GitHub for Docker image + if: ${{ ! matrix.isPr }} id: meta_build uses: docker/metadata-action@v5 with: @@ -125,7 +140,9 @@ jobs: flavor: ${{ env.TAGS_FLAVOR }} # https://github.com/docker/build-push-action - - name: Build and push image + - name: Build and push image to DockerHub + # Only build & push if not a PR + if: ${{ ! matrix.isPr }} id: docker_build uses: docker/build-push-action@v5 with: @@ -134,9 +151,7 @@ jobs: context: ${{ inputs.dockerfile_context }} file: ${{ inputs.dockerfile_path }} platforms: ${{ matrix.arch }} - # For pull requests, we run the Docker build (to ensure no PR changes break the build), - # but we ONLY do an image push to DockerHub if it's NOT a PR - push: ${{ ! matrix.isPr }} + push: true # Use tags / labels provided by 'docker/metadata-action' above tags: ${{ steps.meta_build.outputs.tags }} labels: ${{ steps.meta_build.outputs.labels }} @@ -152,13 +167,39 @@ jobs: # Upload digest to an artifact, so that it can be used in manifest below - name: Upload Docker build digest to artifact if: ${{ ! matrix.isPr }} - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: digests-${{ inputs.build_id }} + name: digests-${{ inputs.build_id }}-${{ env.ARCH_NAME }} path: /tmp/digests/* if-no-files-found: error retention-days: 1 + # Build local image (again) and store in a TAR file in /tmp directory + # NOTE: This cannot be combined with push to DockerHub registry above as it's a different type of output. + - name: Build and push image to local image + if: ${{ ! matrix.isPr }} + uses: docker/build-push-action@v5 + with: + build-contexts: | + ${{ inputs.dockerfile_additional_contexts }} + context: ${{ inputs.dockerfile_context }} + file: ${{ inputs.dockerfile_path }} + platforms: ${{ matrix.arch }} + tags: ${{ steps.meta_build.outputs.tags }} + labels: ${{ steps.meta_build.outputs.labels }} + # Export image to a local TAR file + outputs: type=docker,dest=/tmp/${{ inputs.build_id }}.tar + + # Upload the local docker image (in TAR file) to a build Artifact + - name: Upload local image to artifact + if: ${{ ! matrix.isPr }} + uses: actions/upload-artifact@v4 + with: + name: docker-image-${{ inputs.build_id }}-${{ env.ARCH_NAME }} + path: /tmp/${{ inputs.build_id }}.tar + if-no-files-found: error + retention-days: 1 + # If this build is NOT a PR and passed in a REDEPLOY_SANDBOX_URL secret, # Then redeploy https://sandbox.dspace.org if this build is for our deployment architecture and 'main' branch. - name: Redeploy sandbox.dspace.org (based on main branch) @@ -166,13 +207,13 @@ 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 # If this build is NOT a PR and passed in a REDEPLOY_DEMO_URL secret, # Then redeploy https://demo.dspace.org if this build is for our deployment architecture and demo branch. - - name: Redeploy demo.dspace.org (based on maintenace branch) + - name: Redeploy demo.dspace.org (based on maintenance branch) if: | !matrix.isPR && env.REDEPLOY_DEMO_URL != '' && @@ -181,21 +222,72 @@ jobs: run: | curl -X POST $REDEPLOY_DEMO_URL + #------------------------------------------------------------- + # Build steps for PRs only + # + # These steps build the images and store as a build artifact. + # These artifacts can then be used by later jobs to run the + # brand-new images for automated testing. + #-------------------------------------------------------------- + # Get Metadata for docker_build_deps step below + - name: Extract metadata (tags, labels) for local Docker image + if: matrix.isPr + id: meta_build_pr + uses: docker/metadata-action@v5 + with: + images: ${{ env.IMAGE_NAME }} + # Hardcode to use custom "pr-testing" tag because that will allow us to spin up this PR + # for testing in docker.yml + tags: pr-testing + flavor: ${{ env.TAGS_FLAVOR }} + # Build local image and stores in a TAR file in /tmp directory + - name: Build and push image to local image + if: matrix.isPr + uses: docker/build-push-action@v5 + with: + build-contexts: | + ${{ inputs.dockerfile_additional_contexts }} + context: ${{ inputs.dockerfile_context }} + file: ${{ inputs.dockerfile_path }} + platforms: ${{ matrix.arch }} + tags: ${{ steps.meta_build_pr.outputs.tags }} + labels: ${{ steps.meta_build_pr.outputs.labels }} + # Export image to a local TAR file + outputs: type=docker,dest=/tmp/${{ inputs.build_id }}.tar + # Upload the local docker image (in TAR file) to a build Artifact + - name: Upload local image to artifact + if: matrix.isPr + uses: actions/upload-artifact@v4 + with: + name: docker-image-${{ inputs.build_id }}-${{ env.ARCH_NAME }} + path: /tmp/${{ inputs.build_id }}.tar + if-no-files-found: error + retention-days: 1 + # Merge Docker digests (from various architectures) into a manifest. # This runs after all Docker builds complete above, and it tells hub.docker.com # that these builds should be all included in the manifest for this tag. # (e.g. AMD64 and ARM64 should be listed as options under the same tagged Docker image) docker-build_manifest: + # Only run if this is NOT a PR if: ${{ github.event_name != 'pull_request' }} runs-on: ubuntu-latest needs: - docker-build steps: - name: Download Docker build digests - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: - name: digests-${{ inputs.build_id }} path: /tmp/digests + # Download digests for both AMD64 and ARM64 into same directory + pattern: digests-${{ inputs.build_id }}-* + merge-multiple: true + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_ACCESS_TOKEN }} - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 @@ -208,12 +300,6 @@ jobs: tags: ${{ env.IMAGE_TAGS }} flavor: ${{ env.TAGS_FLAVOR }} - - name: Login to Docker Hub - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_ACCESS_TOKEN }} - - name: Create manifest list from digests and push working-directory: /tmp/digests run: | diff --git a/.gitignore b/.gitignore index 2fcb46b9932c..529351edc5c2 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ tags .project .classpath .checkstyle +.factorypath ## Ignore project files created by IntelliJ IDEA *.iml diff --git a/.lgtm.yml b/.lgtm.yml deleted file mode 100644 index 132de8a6de5a..000000000000 --- a/.lgtm.yml +++ /dev/null @@ -1,9 +0,0 @@ -# LGTM Settings (https://lgtm.com/) -# For reference, see https://lgtm.com/help/lgtm/lgtm.yml-configuration-file -# or template at https://lgtm.com/static/downloads/lgtm.template.yml - -extraction: - java: - index: - # Specify the Java version required to build the project - java_version: 11 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 45a6af9ce5a3..657e11eee771 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,13 +10,14 @@ DSpace is a community built and supported project. We do not have a centralized ## Contribute new code via a Pull Request We accept [GitHub Pull Requests (PRs)](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request-from-a-fork) at any time from anyone. -Contributors to each release are recognized in our [Release Notes](https://wiki.lyrasis.org/display/DSDOC7x/Release+Notes). +Contributors to each release are recognized in our [Release Notes](https://wiki.lyrasis.org/display/DSDOC8x/Release+Notes). Code Contribution Checklist - [ ] PRs _should_ be smaller in size (ideally less than 1,000 lines of code, not including comments & tests) - [ ] PRs **must** pass Checkstyle validation based on our [Code Style Guide](https://wiki.lyrasis.org/display/DSPACE/Code+Style+Guide). - [ ] PRs **must** include Javadoc for _all new/modified public methods and classes_. Larger private methods should also have Javadoc - [ ] PRs **must** pass all automated tests and include new/updated Unit or Integration tests based on our [Code Testing Guide](https://wiki.lyrasis.org/display/DSPACE/Code+Testing+Guide). +- [ ] Details on how to test the PR **must** be provided. Reviewers must be aware of any steps they need to take to successfully test your fix or feature. - [ ] If a PR includes new libraries/dependencies (in any `pom.xml`), then their software licenses **must** align with the [DSpace BSD License](https://github.com/DSpace/DSpace/blob/main/LICENSE) based on the [Licensing of Contributions](https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines#CodeContributionGuidelines-LicensingofContributions) documentation. - [ ] Basic technical documentation _should_ be provided for any new features or changes to the REST API. REST API changes should be documented in our [Rest Contract](https://github.com/DSpace/RestContract). - [ ] If a PR fixes an issue ticket, please [link them together](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue). @@ -25,7 +26,7 @@ Additional details on the code contribution process can be found in our [Code Co ## Contribute documentation -DSpace Documentation is a collaborative effort in a shared Wiki. The latest documentation is at https://wiki.lyrasis.org/display/DSDOC7x +DSpace Documentation is a collaborative effort in a shared Wiki. The latest documentation is at https://wiki.lyrasis.org/display/DSDOC If you find areas of the DSpace Documentation which you wish to improve, please request a Wiki account by emailing wikihelp@lyrasis.org. Once you have an account setup, contact @tdonohue (via [Slack](https://wiki.lyrasis.org/display/DSPACE/Slack) or email) for access to edit our Documentation. @@ -33,7 +34,7 @@ Once you have an account setup, contact @tdonohue (via [Slack](https://wiki.lyra ## Help others on mailing lists or Slack DSpace has our own [Slack](https://wiki.lyrasis.org/display/DSPACE/Slack) community and [Mailing Lists](https://wiki.lyrasis.org/display/DSPACE/Mailing+Lists) where discussions take place and questions are answered. -Anyone is welcome to join and help others. We just ask you to follow our [Code of Conduct](https://www.lyrasis.org/about/Pages/Code-of-Conduct.aspx) (adopted via LYRASIS). +Anyone is welcome to join and help others. We just ask you to follow our [Code of Conduct](https://www.lyrasis.org/about/Pages/Code-of-Conduct.aspx) (adopted via Lyrasis). ## Join a working or interest group @@ -41,5 +42,5 @@ Most of the work in building/improving DSpace comes via [Working Groups](https:/ All working/interest groups are open to anyone to join and participate. A few key groups to be aware of include: -* [DSpace 7 Working Group](https://wiki.lyrasis.org/display/DSPACE/DSpace+7+Working+Group) - This is the main (mostly volunteer) development team. We meet weekly to review our current development [project board](https://github.com/orgs/DSpace/projects), assigning tickets and/or PRs. -* [DSpace Community Advisory Team (DCAT)](https://wiki.lyrasis.org/display/cmtygp/DSpace+Community+Advisory+Team) - This is an interest group for repository managers/administrators. We meet monthly to discuss DSpace, share tips & provide feedback back to developers. \ No newline at end of file +* [DSpace Developer Team](https://wiki.lyrasis.org/display/DSPACE/Developer+Meetings) - This is the primary, volunteer development team. We meet weekly to review our current development [project board](https://github.com/orgs/DSpace/projects), assigning tickets and/or PRs. This is also were discussions of the next release or major issues occur. Anyone is welcome to attend. +* [DSpace Community Advisory Team (DCAT)](https://wiki.lyrasis.org/display/cmtygp/DSpace+Community+Advisory+Team) - This is an interest group for repository managers/administrators. We meet monthly to discuss DSpace, share tips & provide feedback back to developers. Anyone is welcome to attend. diff --git a/Dockerfile b/Dockerfile index 5bcd68376887..d3f85a5bd641 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,13 +3,13 @@ # # - 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_VERSION} 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 @@ -27,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 openjdk:${JDK_VERSION}-slim 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 @@ -49,23 +51,21 @@ 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 +# Need host command for "[dspace]/bin/make-handle-config" +RUN apt-get update \ + && apt-get install -y --no-install-recommends host \ + && apt-get purge -y --auto-remove \ + && rm -rf /var/lib/apt/lists/* +# Expose Tomcat port (8080) & Handle Server HTTP port (8000) +EXPOSE 8080 8000 # Give java extra memory (2GB) ENV JAVA_OPTS=-Xmx2000m - -# Link the DSpace 'server' webapp into Tomcat's webapps directory. -# This ensures that when we start Tomcat, it runs from /server path (e.g. http://localhost:8080/server/) -RUN ln -s $DSPACE_INSTALL/webapps/server /usr/local/tomcat/webapps/server -# If you wish to run "server" webapp off the ROOT path, then comment out the above RUN, and uncomment the below RUN. -# You also MUST update the 'dspace.server.url' configuration to match. -# Please note that server webapp should only run on one path at a time. -#RUN mv /usr/local/tomcat/webapps/ROOT /usr/local/tomcat/webapps/ROOT.bk && \ -# ln -s $DSPACE_INSTALL/webapps/server /usr/local/tomcat/webapps/ROOT +# On startup, run DSpace Runnable JAR +ENTRYPOINT ["java", "-jar", "webapps/server-boot.jar", "--dspace.dir=$DSPACE_INSTALL"] diff --git a/Dockerfile.cli b/Dockerfile.cli index d54978375e54..5254d1eb4d69 100644 --- a/Dockerfile.cli +++ b/Dockerfile.cli @@ -3,13 +3,13 @@ # # - 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_VERSION} 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 @@ -25,7 +25,7 @@ RUN mvn --no-transfer-progress package && \ mvn clean # Step 2 - Run Ant Deploy -FROM openjdk:${JDK_VERSION}-slim 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 @@ -34,9 +34,9 @@ WORKDIR /dspace-src ENV ANT_VERSION 1.10.13 ENV ANT_HOME /tmp/ant-$ANT_VERSION ENV PATH $ANT_HOME/bin:$PATH -# Need wget to install ant, and unzip for managing AIPs +# Need wget to install ant RUN apt-get update \ - && apt-get install -y --no-install-recommends wget unzip \ + && apt-get install -y --no-install-recommends wget \ && apt-get purge -y --auto-remove \ && rm -rf /var/lib/apt/lists/* # Download and install 'ant' @@ -46,10 +46,15 @@ RUN mkdir $ANT_HOME && \ RUN ant init_installation update_configs update_code # Step 3 - Run jdk -FROM openjdk:${JDK_VERSION} +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 # Give java extra memory (1GB) ENV JAVA_OPTS=-Xmx1000m +# Install unzip for AIPs +RUN apt-get update \ + && apt-get install -y --no-install-recommends unzip \ + && apt-get purge -y --auto-remove \ + && rm -rf /var/lib/apt/lists/* diff --git a/Dockerfile.dependencies b/Dockerfile.dependencies index 6f72ab058536..f3bf1f833205 100644 --- a/Dockerfile.dependencies +++ b/Dockerfile.dependencies @@ -2,12 +2,12 @@ # The purpose of this image is to make the build for dspace/dspace run faster # -# This Dockerfile uses JDK11 by default, but has also been tested with JDK17. -# To build with JDK17, use "--build-arg JDK_VERSION=17" -ARG JDK_VERSION=11 +# This Dockerfile uses JDK17 by default. +# To build with other versions, use "--build-arg JDK_VERSION=[value]" +ARG JDK_VERSION=17 # Step 1 - Run Maven Build -FROM maven:3-openjdk-${JDK_VERSION}-slim as build +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 6fcc4eda6be1..218126b17aab 100644 --- a/Dockerfile.test +++ b/Dockerfile.test @@ -5,13 +5,13 @@ # # 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_VERSION} 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 @@ -26,9 +26,11 @@ ADD --chown=dspace . /app/ RUN mvn --no-transfer-progress package && \ mv /app/dspace/target/${TARGET_DIR}/* /install && \ mvn clean +# Remove the server webapp to keep image small. Rename runnable JAR to server-boot.jar. +RUN rm -rf /install/webapps/server/ # Step 2 - Run Ant Deploy -FROM openjdk:${JDK_VERSION}-slim as ant_build +FROM 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,29 +50,23 @@ 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 +# Need host command for "[dspace]/bin/make-handle-config" +RUN apt-get update \ + && apt-get install -y --no-install-recommends host \ + && apt-get purge -y --auto-remove \ + && rm -rf /var/lib/apt/lists/* +# Expose Tomcat port and debugging port +EXPOSE 8080 8000 # Give java extra memory (2GB) ENV JAVA_OPTS=-Xmx2000m # Set up debugging ENV CATALINA_OPTS=-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=*:8000 - -# Link the DSpace 'server' webapp into Tomcat's webapps directory. -# This ensures that when we start Tomcat, it runs from /server path (e.g. http://localhost:8080/server/) -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/LICENSES_THIRD_PARTY b/LICENSES_THIRD_PARTY index e494c80c5d6e..d7e928147c89 100644 --- a/LICENSES_THIRD_PARTY +++ b/LICENSES_THIRD_PARTY @@ -25,25 +25,29 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * AWS Java SDK for AWS KMS (com.amazonaws:aws-java-sdk-kms:1.12.261 - https://aws.amazon.com/sdkforjava) * AWS Java SDK for Amazon S3 (com.amazonaws:aws-java-sdk-s3:1.12.261 - https://aws.amazon.com/sdkforjava) * JMES Path Query library (com.amazonaws:jmespath-java:1.12.261 - https://aws.amazon.com/sdkforjava) + * Titanium JSON-LD 1.1 (JRE11) (com.apicatalog:titanium-json-ld:1.3.2 - https://github.com/filip26/titanium-json-ld) * HPPC Collections (com.carrotsearch:hppc:0.8.1 - http://labs.carrotsearch.com/hppc.html/hppc) - * com.drewnoakes:metadata-extractor (com.drewnoakes:metadata-extractor:2.18.0 - https://drewnoakes.com/code/exif/) + * com.drewnoakes:metadata-extractor (com.drewnoakes:metadata-extractor:2.19.0 - https://drewnoakes.com/code/exif/) * parso (com.epam:parso:2.0.14 - https://github.com/epam/parso) - * Esri Geometry API for Java (com.esri.geometry:esri-geometry-api:2.2.0 - https://github.com/Esri/geometry-api-java) - * ClassMate (com.fasterxml:classmate:1.3.0 - http://github.com/cowtowncoder/java-classmate) - * Jackson-annotations (com.fasterxml.jackson.core:jackson-annotations:2.13.4 - http://github.com/FasterXML/jackson) - * Jackson-core (com.fasterxml.jackson.core:jackson-core:2.13.4 - https://github.com/FasterXML/jackson-core) - * jackson-databind (com.fasterxml.jackson.core:jackson-databind:2.13.4.2 - http://github.com/FasterXML/jackson) + * ClassMate (com.fasterxml:classmate:1.6.0 - https://github.com/FasterXML/java-classmate) + * Jackson-annotations (com.fasterxml.jackson.core:jackson-annotations:2.16.0 - https://github.com/FasterXML/jackson) + * Jackson-core (com.fasterxml.jackson.core:jackson-core:2.16.0 - https://github.com/FasterXML/jackson-core) + * jackson-databind (com.fasterxml.jackson.core:jackson-databind:2.16.0 - https://github.com/FasterXML/jackson) * Jackson dataformat: CBOR (com.fasterxml.jackson.dataformat:jackson-dataformat-cbor:2.12.6 - http://github.com/FasterXML/jackson-dataformats-binary) - * Jackson dataformat: Smile (com.fasterxml.jackson.dataformat:jackson-dataformat-smile:2.13.3 - http://github.com/FasterXML/jackson-dataformats-binary) - * Jackson-dataformat-YAML (com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.11.1 - https://github.com/FasterXML/jackson-dataformats-text) - * Jackson datatype: jdk8 (com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.13.5 - https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jdk8) - * Jackson datatype: JSR310 (com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.11.1 - https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jsr310) - * Jackson datatype: JSR310 (com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.13.5 - https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jsr310) - * Jackson-module-parameter-names (com.fasterxml.jackson.module:jackson-module-parameter-names:2.13.5 - https://github.com/FasterXML/jackson-modules-java8/jackson-module-parameter-names) + * Jackson dataformat: Smile (com.fasterxml.jackson.dataformat:jackson-dataformat-smile:2.15.2 - https://github.com/FasterXML/jackson-dataformats-binary) + * Jackson-dataformat-TOML (com.fasterxml.jackson.dataformat:jackson-dataformat-toml:2.15.2 - https://github.com/FasterXML/jackson-dataformats-text) + * Jackson-dataformat-YAML (com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.16.2 - https://github.com/FasterXML/jackson-dataformats-text) + * Jackson datatype: jdk8 (com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.15.4 - https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jdk8) + * Jackson datatype: JSR310 (com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.16.0 - https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jsr310) + * Jackson Jakarta-RS: base (com.fasterxml.jackson.jakarta.rs:jackson-jakarta-rs-base:2.16.2 - https://github.com/FasterXML/jackson-jakarta-rs-providers/jackson-jakarta-rs-base) + * Jackson Jakarta-RS: JSON (com.fasterxml.jackson.jakarta.rs:jackson-jakarta-rs-json-provider:2.16.2 - https://github.com/FasterXML/jackson-jakarta-rs-providers/jackson-jakarta-rs-json-provider) + * Jackson module: Jakarta XML Bind Annotations (jakarta.xml.bind) (com.fasterxml.jackson.module:jackson-module-jakarta-xmlbind-annotations:2.16.2 - https://github.com/FasterXML/jackson-modules-base) + * Jackson-module-parameter-names (com.fasterxml.jackson.module:jackson-module-parameter-names:2.15.4 - https://github.com/FasterXML/jackson-modules-java8/jackson-module-parameter-names) * Java UUID Generator (com.fasterxml.uuid:java-uuid-generator:4.0.1 - https://github.com/cowtowncoder/java-uuid-generator) - * Woodstox (com.fasterxml.woodstox:woodstox-core:6.2.4 - https://github.com/FasterXML/woodstox) - * zjsonpatch (com.flipkart.zjsonpatch:zjsonpatch:0.4.6 - https://github.com/flipkart-incubator/zjsonpatch/) - * Caffeine cache (com.github.ben-manes.caffeine:caffeine:2.9.2 - https://github.com/ben-manes/caffeine) + * Woodstox (com.fasterxml.woodstox:woodstox-core:6.5.1 - https://github.com/FasterXML/woodstox) + * zjsonpatch (com.flipkart.zjsonpatch:zjsonpatch:0.4.16 - https://github.com/flipkart-incubator/zjsonpatch/) + * Caffeine cache (com.github.ben-manes.caffeine:caffeine:2.9.3 - https://github.com/ben-manes/caffeine) + * Caffeine cache (com.github.ben-manes.caffeine:caffeine:3.1.6 - https://github.com/ben-manes/caffeine) * btf (com.github.java-json-tools:btf:1.3 - https://github.com/java-json-tools/btf) * jackson-coreutils (com.github.java-json-tools:jackson-coreutils:2.0 - https://github.com/java-json-tools/jackson-coreutils) * jackson-coreutils-equivalence (com.github.java-json-tools:jackson-coreutils-equivalence:1.0 - https://github.com/java-json-tools/jackson-coreutils) @@ -54,9 +58,9 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * JCIP Annotations under Apache License (com.github.stephenc.jcip:jcip-annotations:1.0-1 - http://stephenc.github.com/jcip-annotations) * Google APIs Client Library for Java (com.google.api-client:google-api-client:1.23.0 - https://github.com/google/google-api-java-client/google-api-client) * Google Analytics API v3-rev145-1.23.0 (com.google.apis:google-api-services-analytics:v3-rev145-1.23.0 - http://nexus.sonatype.org/oss-repository-hosting.html/google-api-services-analytics) - * FindBugs-jsr305 (com.google.code.findbugs:jsr305:3.0.1 - http://findbugs.sourceforge.net/) - * Gson (com.google.code.gson:gson:2.9.0 - https://github.com/google/gson/gson) - * error-prone annotations (com.google.errorprone:error_prone_annotations:2.18.0 - https://errorprone.info/error_prone_annotations) + * FindBugs-jsr305 (com.google.code.findbugs:jsr305:3.0.2 - http://findbugs.sourceforge.net/) + * Gson (com.google.code.gson:gson:2.10.1 - https://github.com/google/gson/gson) + * error-prone annotations (com.google.errorprone:error_prone_annotations:2.10.0 - https://errorprone.info/error_prone_annotations) * Guava InternalFutureFailureAccess and InternalFutures (com.google.guava:failureaccess:1.0.1 - https://github.com/google/guava/failureaccess) * Guava: Google Core Libraries for Java (com.google.guava:guava:32.0.0-jre - https://github.com/google/guava) * Guava: Google Core Libraries for Java (JDK5 Backport) (com.google.guava:guava-jdk5:17.0 - http://code.google.com/p/guava-libraries/guava-jdk5) @@ -64,25 +68,24 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * Google HTTP Client Library for Java (com.google.http-client:google-http-client:1.23.0 - https://github.com/google/google-http-java-client/google-http-client) * GSON extensions to the Google HTTP Client Library for Java. (com.google.http-client:google-http-client-gson:1.41.7 - https://github.com/googleapis/google-http-java-client/google-http-client-gson) * Jackson 2 extensions to the Google HTTP Client Library for Java. (com.google.http-client:google-http-client-jackson2:1.23.0 - https://github.com/google/google-http-java-client/google-http-client-jackson2) + * J2ObjC Annotations (com.google.j2objc:j2objc-annotations:1.3 - https://github.com/google/j2objc/) * J2ObjC Annotations (com.google.j2objc:j2objc-annotations:2.8 - https://github.com/google/j2objc/) * Google OAuth Client Library for Java (com.google.oauth-client:google-oauth-client:1.33.3 - https://github.com/googleapis/google-oauth-java-client/google-oauth-client) * ConcurrentLinkedHashMap (com.googlecode.concurrentlinkedhashmap:concurrentlinkedhashmap-lru:1.4.2 - http://code.google.com/p/concurrentlinkedhashmap) * libphonenumber (com.googlecode.libphonenumber:libphonenumber:8.11.1 - https://github.com/google/libphonenumber/) - * Jackcess (com.healthmarketscience.jackcess:jackcess:4.0.2 - https://jackcess.sourceforge.io) - * Jackcess Encrypt (com.healthmarketscience.jackcess:jackcess-encrypt:4.0.1 - http://jackcessencrypt.sf.net) - * project ':json-path' (com.jayway.jsonpath:json-path:2.6.0 - https://github.com/jayway/JsonPath) - * project ':json-path-assert' (com.jayway.jsonpath:json-path-assert:2.6.0 - https://github.com/jayway/JsonPath) + * Jackcess (com.healthmarketscience.jackcess:jackcess:4.0.5 - https://jackcess.sourceforge.io) + * Jackcess Encrypt (com.healthmarketscience.jackcess:jackcess-encrypt:4.0.2 - http://jackcessencrypt.sf.net) + * json-path (com.jayway.jsonpath:json-path:2.9.0 - https://github.com/jayway/JsonPath) + * json-path-assert (com.jayway.jsonpath:json-path-assert:2.9.0 - https://github.com/jayway/JsonPath) * Disruptor Framework (com.lmax:disruptor:3.4.2 - http://lmax-exchange.github.com/disruptor) - * builder-commons (com.lyncode:builder-commons:1.0.2 - http://nexus.sonatype.org/oss-repository-hosting.html/builder-commons) - * MaxMind DB Reader (com.maxmind.db:maxmind-db:1.2.2 - http://dev.maxmind.com/) - * MaxMind GeoIP2 API (com.maxmind.geoip2:geoip2:2.11.0 - http://dev.maxmind.com/geoip/geoip2/web-services) - * Nimbus JOSE+JWT (com.nimbusds:nimbus-jose-jwt:7.9 - https://bitbucket.org/connect2id/nimbus-jose-jwt) - * opencsv (com.opencsv:opencsv:5.6 - http://opencsv.sf.net) + * MaxMind DB Reader (com.maxmind.db:maxmind-db:2.1.0 - http://dev.maxmind.com/) + * MaxMind GeoIP2 API (com.maxmind.geoip2:geoip2:2.17.0 - https://dev.maxmind.com/geoip?lang=en) + * Nimbus JOSE+JWT (com.nimbusds:nimbus-jose-jwt:9.37.3 - https://bitbucket.org/connect2id/nimbus-jose-jwt) + * opencsv (com.opencsv:opencsv:5.9 - http://opencsv.sf.net) * java-libpst (com.pff:java-libpst:0.9.3 - https://github.com/rjohnsondev/java-libpst) * rome (com.rometools:rome:1.19.0 - http://rometools.com/rome) * rome-modules (com.rometools:rome-modules:1.19.0 - http://rometools.com/rome-modules) * rome-utils (com.rometools:rome-utils:1.19.0 - http://rometools.com/rome-utils) - * fastinfoset (com.sun.xml.fastinfoset:FastInfoset:1.2.15 - http://fi.java.net) * T-Digest (com.tdunning:t-digest:3.1 - https://github.com/tdunning/t-digest) * config (com.typesafe:config:1.3.3 - https://github.com/lightbend/config) * ssl-config-core (com.typesafe:ssl-config-core_2.13:0.3.8 - https://github.com/lightbend/ssl-config) @@ -94,240 +97,282 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * akka-stream (com.typesafe.akka:akka-stream_2.13:2.5.31 - https://akka.io/) * scala-logging (com.typesafe.scala-logging:scala-logging_2.13:3.9.2 - https://github.com/lightbend/scala-logging) * JSON library from Android SDK (com.vaadin.external.google:android-json:0.0.20131108.vaadin1 - http://developer.android.com/sdk) - * SparseBitSet (com.zaxxer:SparseBitSet:1.2 - https://github.com/brettwooldridge/SparseBitSet) + * SparseBitSet (com.zaxxer:SparseBitSet:1.3 - https://github.com/brettwooldridge/SparseBitSet) * Apache Commons BeanUtils (commons-beanutils:commons-beanutils:1.9.4 - https://commons.apache.org/proper/commons-beanutils/) - * Apache Commons CLI (commons-cli:commons-cli:1.4 - http://commons.apache.org/proper/commons-cli/) - * Apache Commons Codec (commons-codec:commons-codec:1.10 - http://commons.apache.org/proper/commons-codec/) + * Apache Commons CLI (commons-cli:commons-cli:1.6.0 - https://commons.apache.org/proper/commons-cli/) + * Apache Commons Codec (commons-codec:commons-codec:1.16.0 - https://commons.apache.org/proper/commons-codec/) * Apache Commons Collections (commons-collections:commons-collections:3.2.2 - http://commons.apache.org/collections/) - * Commons Digester (commons-digester:commons-digester:1.8.1 - http://commons.apache.org/digester/) - * Apache Commons FileUpload (commons-fileupload:commons-fileupload:1.5 - https://commons.apache.org/proper/commons-fileupload/) - * Apache Commons IO (commons-io:commons-io:2.7 - https://commons.apache.org/proper/commons-io/) + * Commons Digester (commons-digester:commons-digester:2.1 - http://commons.apache.org/digester/) + * Apache Commons IO (commons-io:commons-io:2.15.1 - https://commons.apache.org/proper/commons-io/) * Commons Lang (commons-lang:commons-lang:2.6 - http://commons.apache.org/lang/) - * Apache Commons Logging (commons-logging:commons-logging:1.2 - http://commons.apache.org/proper/commons-logging/) - * Apache Commons Validator (commons-validator:commons-validator:1.5.0 - http://commons.apache.org/proper/commons-validator/) + * Apache Commons Logging (commons-logging:commons-logging:1.3.0 - https://commons.apache.org/proper/commons-logging/) + * Apache Commons Validator (commons-validator:commons-validator:1.7 - http://commons.apache.org/proper/commons-validator/) * GeoJson POJOs for Jackson (de.grundid.opendatalab:geojson-jackson:1.14 - https://github.com/opendatalab-de/geojson-jackson) + * broker-client (eu.openaire:broker-client:1.1.2 - http://api.openaire.eu/broker/broker-client) * OpenAIRE Funders Model (eu.openaire:funders-model:2.0.0 - https://api.openaire.eu) * Metrics Core (io.dropwizard.metrics:metrics-core:4.1.5 - https://metrics.dropwizard.io/metrics-core) * Graphite Integration for Metrics (io.dropwizard.metrics:metrics-graphite:4.1.5 - https://metrics.dropwizard.io/metrics-graphite) * Metrics Integration for Jetty 9.3 and higher (io.dropwizard.metrics:metrics-jetty9:4.1.5 - https://metrics.dropwizard.io/metrics-jetty9) * Metrics Integration with JMX (io.dropwizard.metrics:metrics-jmx:4.1.5 - https://metrics.dropwizard.io/metrics-jmx) * JVM Integration for Metrics (io.dropwizard.metrics:metrics-jvm:4.1.5 - https://metrics.dropwizard.io/metrics-jvm) - * micrometer-core (io.micrometer:micrometer-core:1.9.11 - https://github.com/micrometer-metrics/micrometer) - * Netty/Buffer (io.netty:netty-buffer:4.1.68.Final - https://netty.io/netty-buffer/) - * Netty/Codec (io.netty:netty-codec:4.1.68.Final - https://netty.io/netty-codec/) + * SWORD v2 Common Server Library (forked) (io.gdcc:sword2-server:2.0.0 - https://github.com/gdcc/sword2-server) + * micrometer-commons (io.micrometer:micrometer-commons:1.12.6 - https://github.com/micrometer-metrics/micrometer) + * micrometer-core (io.micrometer:micrometer-core:1.12.6 - https://github.com/micrometer-metrics/micrometer) + * micrometer-jakarta9 (io.micrometer:micrometer-jakarta9:1.12.6 - https://github.com/micrometer-metrics/micrometer) + * micrometer-observation (io.micrometer:micrometer-observation:1.12.6 - https://github.com/micrometer-metrics/micrometer) + * Netty/Buffer (io.netty:netty-buffer:4.1.106.Final - https://netty.io/netty-buffer/) + * Netty/Buffer (io.netty:netty-buffer:4.1.99.Final - https://netty.io/netty-buffer/) + * Netty/Codec (io.netty:netty-codec:4.1.106.Final - https://netty.io/netty-codec/) + * Netty/Codec (io.netty:netty-codec:4.1.99.Final - https://netty.io/netty-codec/) * Netty/Codec/HTTP (io.netty:netty-codec-http:4.1.53.Final - https://netty.io/netty-codec-http/) * Netty/Codec/Socks (io.netty:netty-codec-socks:4.1.53.Final - https://netty.io/netty-codec-socks/) - * Netty/Common (io.netty:netty-common:4.1.68.Final - https://netty.io/netty-common/) - * Netty/Handler (io.netty:netty-handler:4.1.68.Final - https://netty.io/netty-handler/) + * Netty/Common (io.netty:netty-common:4.1.106.Final - https://netty.io/netty-common/) + * Netty/Common (io.netty:netty-common:4.1.99.Final - https://netty.io/netty-common/) + * Netty/Handler (io.netty:netty-handler:4.1.106.Final - https://netty.io/netty-handler/) + * Netty/Handler (io.netty:netty-handler:4.1.99.Final - https://netty.io/netty-handler/) * Netty/Handler/Proxy (io.netty:netty-handler-proxy:4.1.53.Final - https://netty.io/netty-handler-proxy/) - * Netty/Resolver (io.netty:netty-resolver:4.1.68.Final - https://netty.io/netty-resolver/) - * Netty/Transport (io.netty:netty-transport:4.1.68.Final - https://netty.io/netty-transport/) - * Netty/Transport/Native/Epoll (io.netty:netty-transport-native-epoll:4.1.68.Final - https://netty.io/netty-transport-native-epoll/) - * Netty/Transport/Native/Unix/Common (io.netty:netty-transport-native-unix-common:4.1.68.Final - https://netty.io/netty-transport-native-unix-common/) + * Netty/Resolver (io.netty:netty-resolver:4.1.99.Final - https://netty.io/netty-resolver/) + * Netty/Transport (io.netty:netty-transport:4.1.106.Final - https://netty.io/netty-transport/) + * Netty/Transport (io.netty:netty-transport:4.1.99.Final - https://netty.io/netty-transport/) + * Netty/Transport/Native/Epoll (io.netty:netty-transport-native-epoll:4.1.99.Final - https://netty.io/netty-transport-native-epoll/) + * Netty/Transport/Native/Unix/Common (io.netty:netty-transport-native-unix-common:4.1.106.Final - https://netty.io/netty-transport-native-unix-common/) + * Netty/Transport/Native/Unix/Common (io.netty:netty-transport-native-unix-common:4.1.99.Final - https://netty.io/netty-transport-native-unix-common/) * OpenTracing API (io.opentracing:opentracing-api:0.33.0 - https://github.com/opentracing/opentracing-java/opentracing-api) * OpenTracing-noop (io.opentracing:opentracing-noop:0.33.0 - https://github.com/opentracing/opentracing-java/opentracing-noop) * OpenTracing-util (io.opentracing:opentracing-util:0.33.0 - https://github.com/opentracing/opentracing-java/opentracing-util) * Google S2 geometry library (io.sgr:s2-geometry-library-java:1.0.0 - https://github.com/sgr-io/s2-geometry-library-java) + * Jandex: Core (io.smallrye:jandex:3.1.2 - https://smallrye.io) * swagger-annotations (io.swagger:swagger-annotations:1.6.2 - https://github.com/swagger-api/swagger-core/modules/swagger-annotations) * swagger-compat-spec-parser (io.swagger:swagger-compat-spec-parser:1.0.52 - http://nexus.sonatype.org/oss-repository-hosting.html/swagger-parser-project/modules/swagger-compat-spec-parser) * swagger-core (io.swagger:swagger-core:1.6.2 - https://github.com/swagger-api/swagger-core/modules/swagger-core) * swagger-models (io.swagger:swagger-models:1.6.2 - https://github.com/swagger-api/swagger-core/modules/swagger-models) * swagger-parser (io.swagger:swagger-parser:1.0.52 - http://nexus.sonatype.org/oss-repository-hosting.html/swagger-parser-project/modules/swagger-parser) * swagger-annotations (io.swagger.core.v3:swagger-annotations:2.1.5 - https://github.com/swagger-api/swagger-core/modules/swagger-annotations) + * swagger-annotations-jakarta (io.swagger.core.v3:swagger-annotations-jakarta:2.2.21 - https://github.com/swagger-api/swagger-core/modules/swagger-annotations-jakarta) * swagger-core (io.swagger.core.v3:swagger-core:2.1.5 - https://github.com/swagger-api/swagger-core/modules/swagger-core) + * swagger-core-jakarta (io.swagger.core.v3:swagger-core-jakarta:2.2.21 - https://github.com/swagger-api/swagger-core/modules/swagger-core-jakarta) + * swagger-integration-jakarta (io.swagger.core.v3:swagger-integration-jakarta:2.2.21 - https://github.com/swagger-api/swagger-core/modules/swagger-integration-jakarta) + * swagger-jaxrs2-jakarta (io.swagger.core.v3:swagger-jaxrs2-jakarta:2.2.21 - https://github.com/swagger-api/swagger-core/modules/swagger-jaxrs2-jakarta) * swagger-models (io.swagger.core.v3:swagger-models:2.1.5 - https://github.com/swagger-api/swagger-core/modules/swagger-models) + * swagger-models-jakarta (io.swagger.core.v3:swagger-models-jakarta:2.2.21 - https://github.com/swagger-api/swagger-core/modules/swagger-models-jakarta) * swagger-parser (io.swagger.parser.v3:swagger-parser:2.0.23 - http://nexus.sonatype.org/oss-repository-hosting.html/swagger-parser-project/modules/swagger-parser) * swagger-parser (io.swagger.parser.v3:swagger-parser-core:2.0.23 - http://nexus.sonatype.org/oss-repository-hosting.html/swagger-parser-project/modules/swagger-parser-core) * swagger-parser-v2-converter (io.swagger.parser.v3:swagger-parser-v2-converter:2.0.23 - http://nexus.sonatype.org/oss-repository-hosting.html/swagger-parser-project/modules/swagger-parser-v2-converter) * swagger-parser-v3 (io.swagger.parser.v3:swagger-parser-v3:2.0.23 - http://nexus.sonatype.org/oss-repository-hosting.html/swagger-parser-project/modules/swagger-parser-v3) - * Jakarta Bean Validation API (jakarta.validation:jakarta.validation-api:2.0.2 - https://beanvalidation.org) - * JSR107 API and SPI (javax.cache:cache-api:1.1.0 - https://github.com/jsr107/jsr107spec) + * Jakarta Dependency Injection (jakarta.inject:jakarta.inject-api:2.0.1 - https://github.com/eclipse-ee4j/injection-api) + * Jakarta Bean Validation API (jakarta.validation:jakarta.validation-api:3.0.2 - https://beanvalidation.org) + * JSR107 API and SPI (javax.cache:cache-api:1.1.1 - https://github.com/jsr107/jsr107spec) * javax.inject (javax.inject:javax.inject:1 - http://code.google.com/p/atinject/) - * Bean Validation API (javax.validation:validation-api:2.0.1.Final - http://beanvalidation.org) + * Bean Validation API (javax.validation:validation-api:1.1.0.Final - http://beanvalidation.org) * jdbm (jdbm:jdbm:1.0 - no url defined) - * Joda-Time (joda-time:joda-time:2.9.2 - http://www.joda.org/joda-time/) + * Joda-Time (joda-time:joda-time:2.12.5 - https://www.joda.org/joda-time/) * Byte Buddy (without dependencies) (net.bytebuddy:byte-buddy:1.11.13 - https://bytebuddy.net/byte-buddy) * Byte Buddy agent (net.bytebuddy:byte-buddy-agent:1.11.13 - https://bytebuddy.net/byte-buddy-agent) * eigenbase-properties (net.hydromatic:eigenbase-properties:1.1.5 - http://github.com/julianhyde/eigenbase-properties) * json-unit-core (net.javacrumbs.json-unit:json-unit-core:2.19.0 - https://github.com/lukas-krecan/JsonUnit/json-unit-core) * "Java Concurrency in Practice" book annotations (net.jcip:jcip-annotations:1.0 - http://jcip.net/) - * ASM based accessors helper used by json-smart (net.minidev:accessors-smart:1.2 - http://www.minidev.net/) - * ASM based accessors helper used by json-smart (net.minidev:accessors-smart:2.4.7 - https://urielch.github.io/) - * JSON Small and Fast Parser (net.minidev:json-smart:2.3 - http://www.minidev.net/) - * JSON Small and Fast Parser (net.minidev:json-smart:2.4.7 - https://urielch.github.io/) + * ASM based accessors helper used by json-smart (net.minidev:accessors-smart:2.5.0 - https://urielch.github.io/) + * JSON Small and Fast Parser (net.minidev:json-smart:2.5.0 - https://urielch.github.io/) * Abdera Core (org.apache.abdera:abdera-core:1.1.3 - http://abdera.apache.org/abdera-core) * I18N Libraries (org.apache.abdera:abdera-i18n:1.1.3 - http://abdera.apache.org) - * Apache Ant Core (org.apache.ant:ant:1.10.11 - https://ant.apache.org/) - * Apache Ant Launcher (org.apache.ant:ant-launcher:1.10.11 - https://ant.apache.org/) - * Apache Commons BCEL (org.apache.bcel:bcel:6.6.0 - https://commons.apache.org/proper/commons-bcel) - * Calcite Core (org.apache.calcite:calcite-core:1.27.0 - https://calcite.apache.org) - * Calcite Linq4j (org.apache.calcite:calcite-linq4j:1.27.0 - https://calcite.apache.org) - * Apache Calcite Avatica (org.apache.calcite.avatica:avatica-core:1.18.0 - https://calcite.apache.org/avatica) - * Apache Commons Collections (org.apache.commons:commons-collections4:4.1 - http://commons.apache.org/proper/commons-collections/) - * Apache Commons Compress (org.apache.commons:commons-compress:1.21 - https://commons.apache.org/proper/commons-compress/) - * Apache Commons Configuration (org.apache.commons:commons-configuration2:2.8.0 - https://commons.apache.org/proper/commons-configuration/) - * Apache Commons CSV (org.apache.commons:commons-csv:1.9.0 - https://commons.apache.org/proper/commons-csv/) - * Apache Commons DBCP (org.apache.commons:commons-dbcp2:2.9.0 - https://commons.apache.org/dbcp/) + * Abdera Parser (org.apache.abdera:abdera-parser:1.1.3 - http://abdera.apache.org/abdera-parser) + * Apache Ant Core (org.apache.ant:ant:1.10.14 - https://ant.apache.org/) + * Apache Ant Launcher (org.apache.ant:ant-launcher:1.10.14 - https://ant.apache.org/) + * Apache Commons BCEL (org.apache.bcel:bcel:6.7.0 - https://commons.apache.org/proper/commons-bcel) + * Calcite Core (org.apache.calcite:calcite-core:1.35.0 - https://calcite.apache.org) + * Calcite Linq4j (org.apache.calcite:calcite-linq4j:1.35.0 - https://calcite.apache.org) + * Apache Calcite Avatica (org.apache.calcite.avatica:avatica-core:1.23.0 - https://calcite.apache.org/avatica) + * Apache Calcite Avatica Metrics (org.apache.calcite.avatica:avatica-metrics:1.23.0 - https://calcite.apache.org/avatica) + * Apache Commons Collections (org.apache.commons:commons-collections4:4.4 - https://commons.apache.org/proper/commons-collections/) + * Apache Commons Compress (org.apache.commons:commons-compress:1.26.0 - https://commons.apache.org/proper/commons-compress/) + * Apache Commons Configuration (org.apache.commons:commons-configuration2:2.10.1 - https://commons.apache.org/proper/commons-configuration/) + * Apache Commons CSV (org.apache.commons:commons-csv:1.10.0 - https://commons.apache.org/proper/commons-csv/) + * Apache Commons DBCP (org.apache.commons:commons-dbcp2:2.11.0 - https://commons.apache.org/dbcp/) * Apache Commons Exec (org.apache.commons:commons-exec:1.3 - http://commons.apache.org/proper/commons-exec/) - * Apache Commons Lang (org.apache.commons:commons-lang3:3.12.0 - https://commons.apache.org/proper/commons-lang/) + * Apache Commons Exec (org.apache.commons:commons-exec:1.4.0 - https://commons.apache.org/proper/commons-exec/) + * Apache Commons Lang (org.apache.commons:commons-lang3:3.14.0 - https://commons.apache.org/proper/commons-lang/) * Apache Commons Math (org.apache.commons:commons-math3:3.6.1 - http://commons.apache.org/proper/commons-math/) - * Apache Commons Pool (org.apache.commons:commons-pool2:2.11.1 - https://commons.apache.org/proper/commons-pool/) + * Apache Commons Pool (org.apache.commons:commons-pool2:2.12.0 - https://commons.apache.org/proper/commons-pool/) * Apache Commons Text (org.apache.commons:commons-text:1.10.0 - https://commons.apache.org/proper/commons-text) * Curator Client (org.apache.curator:curator-client:2.13.0 - http://curator.apache.org/curator-client) * Curator Framework (org.apache.curator:curator-framework:2.13.0 - http://curator.apache.org/curator-framework) * Curator Recipes (org.apache.curator:curator-recipes:2.13.0 - http://curator.apache.org/curator-recipes) - * Apache Hadoop Annotations (org.apache.hadoop:hadoop-annotations:3.2.2 - no url defined) - * Apache Hadoop Auth (org.apache.hadoop:hadoop-auth:3.2.2 - no url defined) - * Apache Hadoop Common (org.apache.hadoop:hadoop-common:3.2.2 - no url defined) - * Apache Hadoop HDFS Client (org.apache.hadoop:hadoop-hdfs-client:3.2.2 - no url defined) + * Apache Hadoop Annotations (org.apache.hadoop:hadoop-annotations:3.2.4 - no url defined) + * Apache Hadoop Auth (org.apache.hadoop:hadoop-auth:3.2.4 - no url defined) + * Apache Hadoop Common (org.apache.hadoop:hadoop-common:3.2.4 - no url defined) + * Apache Hadoop HDFS Client (org.apache.hadoop:hadoop-hdfs-client:3.2.4 - no url defined) * htrace-core4 (org.apache.htrace:htrace-core4:4.1.0-incubating - http://incubator.apache.org/projects/htrace.html) - * Apache HttpClient (org.apache.httpcomponents:httpclient:4.5.13 - http://hc.apache.org/httpcomponents-client) - * Apache HttpClient Cache (org.apache.httpcomponents:httpclient-cache:4.2.6 - http://hc.apache.org/httpcomponents-client) - * Apache HttpCore (org.apache.httpcomponents:httpcore:4.4.15 - http://hc.apache.org/httpcomponents-core-ga) - * Apache HttpClient Mime (org.apache.httpcomponents:httpmime:4.5.13 - http://hc.apache.org/httpcomponents-client) - * Apache James :: Mime4j :: Core (org.apache.james:apache-mime4j-core:0.8.4 - http://james.apache.org/mime4j/apache-mime4j-core) - * Apache James :: Mime4j :: DOM (org.apache.james:apache-mime4j-dom:0.8.4 - http://james.apache.org/mime4j/apache-mime4j-dom) - * Apache Jena - Libraries POM (org.apache.jena:apache-jena-libs:2.13.0 - http://jena.apache.org/apache-jena-libs/) - * Apache Jena - ARQ (SPARQL 1.1 Query Engine) (org.apache.jena:jena-arq:2.13.0 - http://jena.apache.org/jena-arq/) - * Apache Jena - Core (org.apache.jena:jena-core:2.13.0 - http://jena.apache.org/jena-core/) - * Apache Jena - IRI (org.apache.jena:jena-iri:1.1.2 - http://jena.apache.org/jena-iri/) - * Apache Jena - TDB (Native Triple Store) (org.apache.jena:jena-tdb:1.1.2 - http://jena.apache.org/jena-tdb/) + * Apache HttpClient (org.apache.httpcomponents:httpclient:4.5.14 - http://hc.apache.org/httpcomponents-client-ga) + * Apache HttpClient Cache (org.apache.httpcomponents:httpclient-cache:4.5.14 - http://hc.apache.org/httpcomponents-client-ga) + * Apache HttpCore (org.apache.httpcomponents:httpcore:4.4.16 - http://hc.apache.org/httpcomponents-core-ga) + * Apache HttpClient Mime (org.apache.httpcomponents:httpmime:4.5.14 - http://hc.apache.org/httpcomponents-client-ga) + * Apache HttpClient (org.apache.httpcomponents.client5:httpclient5:5.1.3 - https://hc.apache.org/httpcomponents-client-5.0.x/5.1.3/httpclient5/) + * Apache HttpClient (org.apache.httpcomponents.client5:httpclient5:5.3.1 - https://hc.apache.org/httpcomponents-client-5.0.x/5.3.1/httpclient5/) + * Apache HttpComponents Core HTTP/1.1 (org.apache.httpcomponents.core5:httpcore5:5.1.3 - https://hc.apache.org/httpcomponents-core-5.1.x/5.1.3/httpcore5/) + * Apache HttpComponents Core HTTP/1.1 (org.apache.httpcomponents.core5:httpcore5:5.2.4 - https://hc.apache.org/httpcomponents-core-5.2.x/5.2.4/httpcore5/) + * Apache HttpComponents Core HTTP/2 (org.apache.httpcomponents.core5:httpcore5-h2:5.1.3 - https://hc.apache.org/httpcomponents-core-5.1.x/5.1.3/httpcore5-h2/) + * Apache HttpComponents Core HTTP/2 (org.apache.httpcomponents.core5:httpcore5-h2:5.2.4 - https://hc.apache.org/httpcomponents-core-5.2.x/5.2.4/httpcore5-h2/) + * Apache James :: Mime4j :: Core (org.apache.james:apache-mime4j-core:0.8.10 - http://james.apache.org/mime4j/apache-mime4j-core) + * Apache James :: Mime4j :: DOM (org.apache.james:apache-mime4j-dom:0.8.11 - http://james.apache.org/mime4j/apache-mime4j-dom) + * Apache Jena - Libraries POM (org.apache.jena:apache-jena-libs:4.9.0 - https://jena.apache.org/apache-jena-libs/) + * Apache Jena - ARQ (org.apache.jena:jena-arq:4.9.0 - https://jena.apache.org/jena-arq/) + * Apache Jena - Base (org.apache.jena:jena-base:4.9.0 - https://jena.apache.org/jena-base/) + * Apache Jena - Core (org.apache.jena:jena-core:4.9.0 - https://jena.apache.org/jena-core/) + * Apache Jena - DBOE Base (org.apache.jena:jena-dboe-base:4.9.0 - https://jena.apache.org/jena-dboe-base/) + * Apache Jena - DBOE Indexes (org.apache.jena:jena-dboe-index:4.9.0 - https://jena.apache.org/jena-dboe-index/) + * Apache Jena - DBOE Storage (org.apache.jena:jena-dboe-storage:4.9.0 - https://jena.apache.org/jena-dboe-storage/) + * Apache Jena - DBOE Transactional Datastructures (org.apache.jena:jena-dboe-trans-data:4.9.0 - https://jena.apache.org/jena-dboe-trans-data/) + * Apache Jena - DBOE Transactions (org.apache.jena:jena-dboe-transaction:4.9.0 - https://jena.apache.org/jena-dboe-transaction/) + * Apache Jena - IRI (org.apache.jena:jena-iri:4.9.0 - https://jena.apache.org/jena-iri/) + * Apache Jena - RDF Connection (org.apache.jena:jena-rdfconnection:4.9.0 - https://jena.apache.org/jena-rdfconnection/) + * Apache Jena - RDF Patch (org.apache.jena:jena-rdfpatch:4.9.0 - https://jena.apache.org/jena-rdfpatch/) + * Apache Jena - SHACL (org.apache.jena:jena-shacl:4.9.0 - https://jena.apache.org/jena-shacl/) + * Apache Jena - ShEx (org.apache.jena:jena-shex:4.9.0 - https://jena.apache.org/jena-shex/) + * Apache Jena - TDB1 (Native Triple Store) (org.apache.jena:jena-tdb:4.9.0 - https://jena.apache.org/jena-tdb/) + * Apache Jena - TDB2 (Native Triple Store) (org.apache.jena:jena-tdb2:4.9.0 - https://jena.apache.org/jena-tdb2/) * Kerby-kerb core (org.apache.kerby:kerb-core:1.0.1 - http://directory.apache.org/kerby/kerby-kerb/kerb-core) * Kerby-kerb Util (org.apache.kerby:kerb-util:1.0.1 - http://directory.apache.org/kerby/kerby-kerb/kerb-util) * Kerby ASN1 Project (org.apache.kerby:kerby-asn1:1.0.1 - http://directory.apache.org/kerby/kerby-common/kerby-asn1) * Kerby PKIX Project (org.apache.kerby:kerby-pkix:1.0.1 - http://directory.apache.org/kerby/kerby-pkix) - * Apache Log4j 1.x Compatibility API (org.apache.logging.log4j:log4j-1.2-api:2.20.0 - https://logging.apache.org/log4j/2.x/log4j-1.2-api/) - * Apache Log4j API (org.apache.logging.log4j:log4j-api:2.20.0 - https://logging.apache.org/log4j/2.x/log4j-api/) - * Apache Log4j Core (org.apache.logging.log4j:log4j-core:2.20.0 - https://logging.apache.org/log4j/2.x/log4j-core/) - * Apache Log4j JUL Adapter (org.apache.logging.log4j:log4j-jul:2.20.0 - https://logging.apache.org/log4j/2.x/log4j-jul/) - * Apache Log4j Layout for JSON template (org.apache.logging.log4j:log4j-layout-template-json:2.17.1 - https://logging.apache.org/log4j/2.x/log4j-layout-template-json/) - * Apache Log4j SLF4J Binding (org.apache.logging.log4j:log4j-slf4j-impl:2.20.0 - https://logging.apache.org/log4j/2.x/log4j-slf4j-impl/) - * Apache Log4j Web (org.apache.logging.log4j:log4j-web:2.20.0 - https://logging.apache.org/log4j/2.x/log4j-web/) - * Lucene Common Analyzers (org.apache.lucene:lucene-analyzers-common:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-analyzers-common) - * Lucene ICU Analysis Components (org.apache.lucene:lucene-analyzers-icu:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-analyzers-icu) - * Lucene Kuromoji Japanese Morphological Analyzer (org.apache.lucene:lucene-analyzers-kuromoji:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-analyzers-kuromoji) - * Lucene Nori Korean Morphological Analyzer (org.apache.lucene:lucene-analyzers-nori:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-analyzers-nori) - * Lucene Phonetic Filters (org.apache.lucene:lucene-analyzers-phonetic:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-analyzers-phonetic) - * Lucene Smart Chinese Analyzer (org.apache.lucene:lucene-analyzers-smartcn:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-analyzers-smartcn) - * Lucene Stempel Analyzer (org.apache.lucene:lucene-analyzers-stempel:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-analyzers-stempel) - * Lucene Memory (org.apache.lucene:lucene-backward-codecs:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-backward-codecs) - * Lucene Classification (org.apache.lucene:lucene-classification:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-classification) - * Lucene codecs (org.apache.lucene:lucene-codecs:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-codecs) - * Lucene Core (org.apache.lucene:lucene-core:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-core) - * Lucene Expressions (org.apache.lucene:lucene-expressions:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-expressions) - * Lucene Grouping (org.apache.lucene:lucene-grouping:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-grouping) - * Lucene Highlighter (org.apache.lucene:lucene-highlighter:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-highlighter) - * Lucene Join (org.apache.lucene:lucene-join:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-join) - * Lucene Memory (org.apache.lucene:lucene-memory:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-memory) - * Lucene Miscellaneous (org.apache.lucene:lucene-misc:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-misc) - * Lucene Queries (org.apache.lucene:lucene-queries:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-queries) - * Lucene QueryParsers (org.apache.lucene:lucene-queryparser:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-queryparser) - * Lucene Sandbox (org.apache.lucene:lucene-sandbox:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-sandbox) - * Lucene Spatial Extras (org.apache.lucene:lucene-spatial-extras:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-spatial-extras) - * Lucene Spatial 3D (org.apache.lucene:lucene-spatial3d:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-spatial3d) - * Lucene Suggest (org.apache.lucene:lucene-suggest:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-suggest) - * Apache FontBox (org.apache.pdfbox:fontbox:2.0.28 - http://pdfbox.apache.org/) + * Apache Log4j 1.x Compatibility API (org.apache.logging.log4j:log4j-1.2-api:2.23.1 - https://logging.apache.org/log4j/2.x/log4j/log4j-1.2-api/) + * Apache Log4j API (org.apache.logging.log4j:log4j-api:2.23.1 - https://logging.apache.org/log4j/2.x/log4j/log4j-api/) + * Apache Log4j Core (org.apache.logging.log4j:log4j-core:2.23.1 - https://logging.apache.org/log4j/2.x/log4j/log4j-core/) + * Apache Log4j JUL Adapter (org.apache.logging.log4j:log4j-jul:2.23.1 - https://logging.apache.org/log4j/2.x/log4j/log4j-jul/) + * Apache Log4j Layout for JSON template (org.apache.logging.log4j:log4j-layout-template-json:2.17.2 - https://logging.apache.org/log4j/2.x/log4j-layout-template-json/) + * Apache Log4j SLF4J Binding (org.apache.logging.log4j:log4j-slf4j-impl:2.23.1 - https://logging.apache.org/log4j/2.x/log4j/log4j-slf4j-impl/) + * Apache Log4j SLF4J 2.0 Binding (org.apache.logging.log4j:log4j-slf4j2-impl:2.21.1 - https://logging.apache.org/log4j/2.x/log4j/log4j-slf4j2-impl/) + * Apache Log4j Web (org.apache.logging.log4j:log4j-web:2.23.1 - https://logging.apache.org/log4j/2.x/log4j/log4j-web/) + * Lucene Common Analyzers (org.apache.lucene:lucene-analyzers-common:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-analyzers-common) + * Lucene ICU Analysis Components (org.apache.lucene:lucene-analyzers-icu:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-analyzers-icu) + * Lucene Kuromoji Japanese Morphological Analyzer (org.apache.lucene:lucene-analyzers-kuromoji:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-analyzers-kuromoji) + * Lucene Nori Korean Morphological Analyzer (org.apache.lucene:lucene-analyzers-nori:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-analyzers-nori) + * Lucene Phonetic Filters (org.apache.lucene:lucene-analyzers-phonetic:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-analyzers-phonetic) + * Lucene Smart Chinese Analyzer (org.apache.lucene:lucene-analyzers-smartcn:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-analyzers-smartcn) + * Lucene Stempel Analyzer (org.apache.lucene:lucene-analyzers-stempel:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-analyzers-stempel) + * Lucene Memory (org.apache.lucene:lucene-backward-codecs:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-backward-codecs) + * Lucene Classification (org.apache.lucene:lucene-classification:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-classification) + * Lucene codecs (org.apache.lucene:lucene-codecs:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-codecs) + * Lucene Core (org.apache.lucene:lucene-core:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-core) + * Lucene Expressions (org.apache.lucene:lucene-expressions:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-expressions) + * Lucene Grouping (org.apache.lucene:lucene-grouping:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-grouping) + * Lucene Highlighter (org.apache.lucene:lucene-highlighter:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-highlighter) + * Lucene Join (org.apache.lucene:lucene-join:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-join) + * Lucene Memory (org.apache.lucene:lucene-memory:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-memory) + * Lucene Miscellaneous (org.apache.lucene:lucene-misc:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-misc) + * Lucene Queries (org.apache.lucene:lucene-queries:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-queries) + * Lucene QueryParsers (org.apache.lucene:lucene-queryparser:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-queryparser) + * Lucene Sandbox (org.apache.lucene:lucene-sandbox:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-sandbox) + * Lucene Spatial Extras (org.apache.lucene:lucene-spatial-extras:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-spatial-extras) + * Lucene Spatial 3D (org.apache.lucene:lucene-spatial3d:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-spatial3d) + * Lucene Suggest (org.apache.lucene:lucene-suggest:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-suggest) + * Apache FontBox (org.apache.pdfbox:fontbox:2.0.31 - http://pdfbox.apache.org/) * PDFBox JBIG2 ImageIO plugin (org.apache.pdfbox:jbig2-imageio:3.0.4 - https://www.apache.org/jbig2-imageio/) * Apache JempBox (org.apache.pdfbox:jempbox:1.8.17 - http://www.apache.org/pdfbox-parent/jempbox/) - * Apache PDFBox (org.apache.pdfbox:pdfbox:2.0.28 - https://www.apache.org/pdfbox-parent/pdfbox/) - * Apache PDFBox tools (org.apache.pdfbox:pdfbox-tools:2.0.27 - https://www.apache.org/pdfbox-parent/pdfbox-tools/) - * Apache XmpBox (org.apache.pdfbox:xmpbox:2.0.27 - https://www.apache.org/pdfbox-parent/xmpbox/) - * Apache POI - Common (org.apache.poi:poi:5.2.3 - https://poi.apache.org/) - * Apache POI - API based on OPC and OOXML schemas (org.apache.poi:poi-ooxml:5.2.3 - https://poi.apache.org/) - * Apache POI (org.apache.poi:poi-ooxml-lite:5.2.3 - https://poi.apache.org/) - * Apache POI (org.apache.poi:poi-scratchpad:5.2.3 - https://poi.apache.org/) - * Apache Solr Core (org.apache.solr:solr-core:8.11.2 - https://lucene.apache.org/solr-parent/solr-core) - * Apache Solr Solrj (org.apache.solr:solr-solrj:8.11.2 - https://lucene.apache.org/solr-parent/solr-solrj) + * Apache PDFBox (org.apache.pdfbox:pdfbox:2.0.31 - https://www.apache.org/pdfbox-parent/pdfbox/) + * Apache PDFBox tools (org.apache.pdfbox:pdfbox-tools:2.0.31 - https://www.apache.org/pdfbox-parent/pdfbox-tools/) + * Apache XmpBox (org.apache.pdfbox:xmpbox:2.0.31 - https://www.apache.org/pdfbox-parent/xmpbox/) + * Apache POI - Common (org.apache.poi:poi:5.2.5 - https://poi.apache.org/) + * Apache POI - API based on OPC and OOXML schemas (org.apache.poi:poi-ooxml:5.2.5 - https://poi.apache.org/) + * Apache POI (org.apache.poi:poi-ooxml-lite:5.2.5 - https://poi.apache.org/) + * Apache POI (org.apache.poi:poi-scratchpad:5.2.5 - https://poi.apache.org/) + * Apache Solr Core (org.apache.solr:solr-core:8.11.3 - https://lucene.apache.org/solr-parent/solr-core) + * Apache Solr Solrj (org.apache.solr:solr-solrj:8.11.3 - https://lucene.apache.org/solr-parent/solr-solrj) * Apache Standard Taglib Implementation (org.apache.taglibs:taglibs-standard-impl:1.2.5 - http://tomcat.apache.org/taglibs/standard-1.2.5/taglibs-standard-impl) * Apache Standard Taglib Specification API (org.apache.taglibs:taglibs-standard-spec:1.2.5 - http://tomcat.apache.org/taglibs/standard-1.2.5/taglibs-standard-spec) - * Apache Thrift (org.apache.thrift:libthrift:0.9.2 - http://thrift.apache.org) - * Apache Tika core (org.apache.tika:tika-core:2.5.0 - https://tika.apache.org/) - * Apache Tika Apple parser module (org.apache.tika:tika-parser-apple-module:2.5.0 - https://tika.apache.org/tika-parser-apple-module/) - * Apache Tika audiovideo parser module (org.apache.tika:tika-parser-audiovideo-module:2.5.0 - https://tika.apache.org/tika-parser-audiovideo-module/) - * Apache Tika cad parser module (org.apache.tika:tika-parser-cad-module:2.5.0 - https://tika.apache.org/tika-parser-cad-module/) - * Apache Tika code parser module (org.apache.tika:tika-parser-code-module:2.5.0 - https://tika.apache.org/tika-parser-code-module/) - * Apache Tika crypto parser module (org.apache.tika:tika-parser-crypto-module:2.5.0 - https://tika.apache.org/tika-parser-crypto-module/) - * Apache Tika digest commons (org.apache.tika:tika-parser-digest-commons:2.5.0 - https://tika.apache.org/tika-parser-digest-commons/) - * Apache Tika font parser module (org.apache.tika:tika-parser-font-module:2.5.0 - https://tika.apache.org/tika-parser-font-module/) - * Apache Tika html parser module (org.apache.tika:tika-parser-html-module:2.5.0 - https://tika.apache.org/tika-parser-html-module/) - * Apache Tika image parser module (org.apache.tika:tika-parser-image-module:2.5.0 - https://tika.apache.org/tika-parser-image-module/) - * Apache Tika mail commons (org.apache.tika:tika-parser-mail-commons:2.5.0 - https://tika.apache.org/tika-parser-mail-commons/) - * Apache Tika mail parser module (org.apache.tika:tika-parser-mail-module:2.5.0 - https://tika.apache.org/tika-parser-mail-module/) - * Apache Tika Microsoft parser module (org.apache.tika:tika-parser-microsoft-module:2.5.0 - https://tika.apache.org/tika-parser-microsoft-module/) - * Apache Tika miscellaneous office format parser module (org.apache.tika:tika-parser-miscoffice-module:2.5.0 - https://tika.apache.org/tika-parser-miscoffice-module/) - * Apache Tika news parser module (org.apache.tika:tika-parser-news-module:2.5.0 - https://tika.apache.org/tika-parser-news-module/) - * Apache Tika OCR parser module (org.apache.tika:tika-parser-ocr-module:2.5.0 - https://tika.apache.org/tika-parser-ocr-module/) - * Apache Tika PDF parser module (org.apache.tika:tika-parser-pdf-module:2.5.0 - https://tika.apache.org/tika-parser-pdf-module/) - * Apache Tika package parser module (org.apache.tika:tika-parser-pkg-module:2.5.0 - https://tika.apache.org/tika-parser-pkg-module/) - * Apache Tika text parser module (org.apache.tika:tika-parser-text-module:2.5.0 - https://tika.apache.org/tika-parser-text-module/) - * Apache Tika WARC parser module (org.apache.tika:tika-parser-webarchive-module:2.5.0 - https://tika.apache.org/tika-parser-webarchive-module/) - * Apache Tika XML parser module (org.apache.tika:tika-parser-xml-module:2.5.0 - https://tika.apache.org/tika-parser-xml-module/) - * Apache Tika XMP commons (org.apache.tika:tika-parser-xmp-commons:2.5.0 - https://tika.apache.org/tika-parser-xmp-commons/) - * Apache Tika ZIP commons (org.apache.tika:tika-parser-zip-commons:2.5.0 - https://tika.apache.org/tika-parser-zip-commons/) - * Apache Tika standard parser package (org.apache.tika:tika-parsers-standard-package:2.5.0 - https://tika.apache.org/tika-parsers/tika-parsers-standard/tika-parsers-standard-package/) - * tomcat-embed-core (org.apache.tomcat.embed:tomcat-embed-core:9.0.75 - https://tomcat.apache.org/) - * tomcat-embed-el (org.apache.tomcat.embed:tomcat-embed-el:9.0.75 - https://tomcat.apache.org/) - * tomcat-embed-websocket (org.apache.tomcat.embed:tomcat-embed-websocket:9.0.75 - https://tomcat.apache.org/) + * Apache Thrift (org.apache.thrift:libthrift:0.18.1 - http://thrift.apache.org) + * Apache Tika core (org.apache.tika:tika-core:2.9.2 - https://tika.apache.org/) + * Apache Tika Apple parser module (org.apache.tika:tika-parser-apple-module:2.9.2 - https://tika.apache.org/tika-parser-apple-module/) + * Apache Tika audiovideo parser module (org.apache.tika:tika-parser-audiovideo-module:2.9.2 - https://tika.apache.org/tika-parser-audiovideo-module/) + * Apache Tika cad parser module (org.apache.tika:tika-parser-cad-module:2.9.2 - https://tika.apache.org/tika-parser-cad-module/) + * Apache Tika code parser module (org.apache.tika:tika-parser-code-module:2.9.2 - https://tika.apache.org/tika-parser-code-module/) + * Apache Tika crypto parser module (org.apache.tika:tika-parser-crypto-module:2.9.2 - https://tika.apache.org/tika-parser-crypto-module/) + * Apache Tika digest commons (org.apache.tika:tika-parser-digest-commons:2.9.2 - https://tika.apache.org/tika-parser-digest-commons/) + * Apache Tika font parser module (org.apache.tika:tika-parser-font-module:2.9.2 - https://tika.apache.org/tika-parser-font-module/) + * Apache Tika html parser module (org.apache.tika:tika-parser-html-module:2.9.2 - https://tika.apache.org/tika-parser-html-module/) + * Apache Tika image parser module (org.apache.tika:tika-parser-image-module:2.9.2 - https://tika.apache.org/tika-parser-image-module/) + * Apache Tika mail commons (org.apache.tika:tika-parser-mail-commons:2.9.2 - https://tika.apache.org/tika-parser-mail-commons/) + * Apache Tika mail parser module (org.apache.tika:tika-parser-mail-module:2.9.2 - https://tika.apache.org/tika-parser-mail-module/) + * Apache Tika Microsoft parser module (org.apache.tika:tika-parser-microsoft-module:2.9.2 - https://tika.apache.org/tika-parser-microsoft-module/) + * Apache Tika miscellaneous office format parser module (org.apache.tika:tika-parser-miscoffice-module:2.9.2 - https://tika.apache.org/tika-parser-miscoffice-module/) + * Apache Tika news parser module (org.apache.tika:tika-parser-news-module:2.9.2 - https://tika.apache.org/tika-parser-news-module/) + * Apache Tika OCR parser module (org.apache.tika:tika-parser-ocr-module:2.9.2 - https://tika.apache.org/tika-parser-ocr-module/) + * Apache Tika PDF parser module (org.apache.tika:tika-parser-pdf-module:2.9.2 - https://tika.apache.org/tika-parser-pdf-module/) + * Apache Tika package parser module (org.apache.tika:tika-parser-pkg-module:2.9.2 - https://tika.apache.org/tika-parser-pkg-module/) + * Apache Tika text parser module (org.apache.tika:tika-parser-text-module:2.9.2 - https://tika.apache.org/tika-parser-text-module/) + * Apache Tika WARC parser module (org.apache.tika:tika-parser-webarchive-module:2.9.2 - https://tika.apache.org/tika-parser-webarchive-module/) + * Apache Tika XML parser module (org.apache.tika:tika-parser-xml-module:2.9.2 - https://tika.apache.org/tika-parser-xml-module/) + * Apache Tika XMP commons (org.apache.tika:tika-parser-xmp-commons:2.9.2 - https://tika.apache.org/tika-parser-xmp-commons/) + * Apache Tika ZIP commons (org.apache.tika:tika-parser-zip-commons:2.9.2 - https://tika.apache.org/tika-parser-zip-commons/) + * Apache Tika standard parser package (org.apache.tika:tika-parsers-standard-package:2.9.2 - https://tika.apache.org/tika-parsers/tika-parsers-standard/tika-parsers-standard-package/) + * tomcat-embed-core (org.apache.tomcat.embed:tomcat-embed-core:10.1.24 - https://tomcat.apache.org/) + * tomcat-embed-el (org.apache.tomcat.embed:tomcat-embed-el:10.1.24 - https://tomcat.apache.org/) + * tomcat-embed-websocket (org.apache.tomcat.embed:tomcat-embed-websocket:10.1.24 - https://tomcat.apache.org/) * Apache Velocity - Engine (org.apache.velocity:velocity-engine-core:2.3 - http://velocity.apache.org/engine/devel/velocity-engine-core/) * Apache Velocity - JSR 223 Scripting (org.apache.velocity:velocity-engine-scripting:2.2 - http://velocity.apache.org/engine/devel/velocity-engine-scripting/) - * Axiom API (org.apache.ws.commons.axiom:axiom-api:1.2.22 - http://ws.apache.org/axiom/) - * Abdera Model (FOM) Implementation (org.apache.ws.commons.axiom:fom-impl:1.2.22 - http://ws.apache.org/axiom/implementations/fom-impl/) - * XmlBeans (org.apache.xmlbeans:xmlbeans:5.1.1 - https://xmlbeans.apache.org/) + * Axiom API (org.apache.ws.commons.axiom:axiom-api:1.2.14 - http://ws.apache.org/axiom/) + * Axiom Impl (org.apache.ws.commons.axiom:axiom-impl:1.2.14 - http://ws.apache.org/axiom/) + * XmlBeans (org.apache.xmlbeans:xmlbeans:5.2.0 - https://xmlbeans.apache.org/) * Apache ZooKeeper - Server (org.apache.zookeeper:zookeeper:3.6.2 - http://zookeeper.apache.org/zookeeper) * Apache ZooKeeper - Jute (org.apache.zookeeper:zookeeper-jute:3.6.2 - http://zookeeper.apache.org/zookeeper-jute) - * org.apiguardian:apiguardian-api (org.apiguardian:apiguardian-api:1.1.0 - https://github.com/apiguardian-team/apiguardian) - * AssertJ fluent assertions (org.assertj:assertj-core:3.22.0 - https://assertj.github.io/doc/assertj-core/) + * org.apiguardian:apiguardian-api (org.apiguardian:apiguardian-api:1.1.2 - https://github.com/apiguardian-team/apiguardian) + * AssertJ Core (org.assertj:assertj-core:3.24.2 - https://assertj.github.io/doc/#assertj-core) * Evo Inflector (org.atteo:evo-inflector:1.3 - http://atteo.org/static/evo-inflector) + * Awaitility (org.awaitility:awaitility:4.2.1 - http://awaitility.org) * jose4j (org.bitbucket.b_c:jose4j:0.6.5 - https://bitbucket.org/b_c/jose4j/) * TagSoup (org.ccil.cowan.tagsoup:tagsoup:1.2.1 - http://home.ccil.org/~cowan/XML/tagsoup/) + * Woodstox (org.codehaus.woodstox:wstx-asl:3.2.6 - http://woodstox.codehaus.org) * jems (org.dmfs:jems:1.18 - https://github.com/dmfs/jems) * rfc3986-uri (org.dmfs:rfc3986-uri:0.8.1 - https://github.com/dmfs/uri-toolkit) * Jetty :: Apache JSP Implementation (org.eclipse.jetty:apache-jsp:9.4.15.v20190215 - http://www.eclipse.org/jetty) * Apache :: JSTL module (org.eclipse.jetty:apache-jstl:9.4.15.v20190215 - http://tomcat.apache.org/taglibs/standard/) - * Jetty :: ALPN :: Client (org.eclipse.jetty:jetty-alpn-client:9.4.44.v20210927 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-client) - * Jetty :: ALPN :: JDK9 Client Implementation (org.eclipse.jetty:jetty-alpn-java-client:9.4.44.v20210927 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-java-client) - * Jetty :: ALPN :: JDK9 Server Implementation (org.eclipse.jetty:jetty-alpn-java-server:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-java-server) - * Jetty :: ALPN :: Server (org.eclipse.jetty:jetty-alpn-server:9.4.44.v20210927 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-server) - * Jetty :: ALPN :: Server (org.eclipse.jetty:jetty-alpn-server:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-server) + * Jetty :: ALPN :: Client (org.eclipse.jetty:jetty-alpn-client:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-client) + * Jetty :: ALPN :: JDK9 Client Implementation (org.eclipse.jetty:jetty-alpn-java-client:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-java-client) + * Jetty :: ALPN :: JDK9 Server Implementation (org.eclipse.jetty:jetty-alpn-java-server:9.4.15.v20190215 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-java-server) + * Jetty :: ALPN :: JDK9 Server Implementation (org.eclipse.jetty:jetty-alpn-java-server:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-java-server) + * Jetty :: ALPN :: Server (org.eclipse.jetty:jetty-alpn-server:9.4.15.v20190215 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-server) + * Jetty :: ALPN :: Server (org.eclipse.jetty:jetty-alpn-server:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-server) * Jetty :: Servlet Annotations (org.eclipse.jetty:jetty-annotations:9.4.15.v20190215 - http://www.eclipse.org/jetty) - * Jetty :: Asynchronous HTTP Client (org.eclipse.jetty:jetty-client:9.4.44.v20210927 - https://eclipse.org/jetty/jetty-client) - * Jetty :: Continuation (org.eclipse.jetty:jetty-continuation:9.4.44.v20210927 - https://eclipse.org/jetty/jetty-continuation) - * Jetty :: Continuation (org.eclipse.jetty:jetty-continuation:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-continuation) - * Jetty :: Deployers (org.eclipse.jetty:jetty-deploy:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-deploy) - * Jetty :: Http Utility (org.eclipse.jetty:jetty-http:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-http) - * Jetty :: IO Utility (org.eclipse.jetty:jetty-io:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-io) - * Jetty :: JMX Management (org.eclipse.jetty:jetty-jmx:9.4.44.v20210927 - https://eclipse.org/jetty/jetty-jmx) + * Jetty :: Asynchronous HTTP Client (org.eclipse.jetty:jetty-client:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-client) + * Jetty :: Continuation (org.eclipse.jetty:jetty-continuation:9.4.15.v20190215 - http://www.eclipse.org/jetty) + * Jetty :: Continuation (org.eclipse.jetty:jetty-continuation:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-continuation) + * Jetty :: Deployers (org.eclipse.jetty:jetty-deploy:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-deploy) + * Jetty :: Http Utility (org.eclipse.jetty:jetty-http:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-http) + * Jetty :: IO Utility (org.eclipse.jetty:jetty-io:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-io) + * Jetty :: JMX Management (org.eclipse.jetty:jetty-jmx:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-jmx) * Jetty :: JNDI Naming (org.eclipse.jetty:jetty-jndi:9.4.15.v20190215 - http://www.eclipse.org/jetty) * Jetty :: Plus (org.eclipse.jetty:jetty-plus:9.4.15.v20190215 - http://www.eclipse.org/jetty) - * Jetty :: Rewrite Handler (org.eclipse.jetty:jetty-rewrite:9.4.44.v20210927 - https://eclipse.org/jetty/jetty-rewrite) - * Jetty :: Security (org.eclipse.jetty:jetty-security:9.4.44.v20210927 - https://eclipse.org/jetty/jetty-security) - * Jetty :: Security (org.eclipse.jetty:jetty-security:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-security) - * Jetty :: Server Core (org.eclipse.jetty:jetty-server:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-server) - * Jetty :: Servlet Handling (org.eclipse.jetty:jetty-servlet:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-servlet) - * Jetty :: Utility Servlets and Filters (org.eclipse.jetty:jetty-servlets:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-servlets) - * Jetty :: Utilities (org.eclipse.jetty:jetty-util:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-util) - * Jetty :: Utilities :: Ajax(JSON) (org.eclipse.jetty:jetty-util-ajax:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-util-ajax) - * Jetty :: Webapp Application Support (org.eclipse.jetty:jetty-webapp:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-webapp) - * Jetty :: XML utilities (org.eclipse.jetty:jetty-xml:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-xml) - * Jetty :: HTTP2 :: Client (org.eclipse.jetty.http2:http2-client:9.4.44.v20210927 - https://eclipse.org/jetty/http2-parent/http2-client) - * Jetty :: HTTP2 :: Common (org.eclipse.jetty.http2:http2-common:9.4.51.v20230217 - https://eclipse.org/jetty/http2-parent/http2-common) - * Jetty :: HTTP2 :: HPACK (org.eclipse.jetty.http2:http2-hpack:9.4.44.v20210927 - https://eclipse.org/jetty/http2-parent/http2-hpack) - * Jetty :: HTTP2 :: HTTP Client Transport (org.eclipse.jetty.http2:http2-http-client-transport:9.4.44.v20210927 - https://eclipse.org/jetty/http2-parent/http2-http-client-transport) - * Jetty :: HTTP2 :: Server (org.eclipse.jetty.http2:http2-server:9.4.51.v20230217 - https://eclipse.org/jetty/http2-parent/http2-server) + * Jetty :: Rewrite Handler (org.eclipse.jetty:jetty-rewrite:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-rewrite) + * Jetty :: Security (org.eclipse.jetty:jetty-security:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-security) + * Jetty :: Security (org.eclipse.jetty:jetty-security:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-security) + * Jetty :: Server Core (org.eclipse.jetty:jetty-server:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-server) + * Jetty :: Servlet Handling (org.eclipse.jetty:jetty-servlet:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-servlet) + * Jetty :: Utility Servlets and Filters (org.eclipse.jetty:jetty-servlets:9.4.15.v20190215 - http://www.eclipse.org/jetty) + * Jetty :: Utility Servlets and Filters (org.eclipse.jetty:jetty-servlets:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-servlets) + * Jetty :: Utilities (org.eclipse.jetty:jetty-util:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-util) + * Jetty :: Utilities :: Ajax(JSON) (org.eclipse.jetty:jetty-util-ajax:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-util-ajax) + * Jetty :: Webapp Application Support (org.eclipse.jetty:jetty-webapp:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-webapp) + * Jetty :: XML utilities (org.eclipse.jetty:jetty-xml:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-xml) + * Jetty :: XML utilities (org.eclipse.jetty:jetty-xml:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-xml) + * Jetty :: ALPN :: API (org.eclipse.jetty.alpn:alpn-api:1.1.3.v20160715 - http://www.eclipse.org/jetty/alpn-api) + * Jetty :: HTTP2 :: Client (org.eclipse.jetty.http2:http2-client:9.4.53.v20231009 - https://eclipse.org/jetty/http2-parent/http2-client) + * Jetty :: HTTP2 :: Common (org.eclipse.jetty.http2:http2-common:9.4.54.v20240208 - https://eclipse.org/jetty/http2-parent/http2-common) + * Jetty :: HTTP2 :: HPACK (org.eclipse.jetty.http2:http2-hpack:9.4.53.v20231009 - https://eclipse.org/jetty/http2-parent/http2-hpack) + * Jetty :: HTTP2 :: HTTP Client Transport (org.eclipse.jetty.http2:http2-http-client-transport:9.4.53.v20231009 - https://eclipse.org/jetty/http2-parent/http2-http-client-transport) + * Jetty :: HTTP2 :: Server (org.eclipse.jetty.http2:http2-server:9.4.15.v20190215 - https://eclipse.org/jetty/http2-parent/http2-server) + * Jetty :: HTTP2 :: Server (org.eclipse.jetty.http2:http2-server:9.4.53.v20231009 - https://eclipse.org/jetty/http2-parent/http2-server) * Jetty :: Schemas (org.eclipse.jetty.toolchain:jetty-schemas:3.1.2 - https://eclipse.org/jetty/jetty-schemas) - * Ehcache (org.ehcache:ehcache:3.4.0 - http://ehcache.org) - * flyway-core (org.flywaydb:flyway-core:8.4.4 - https://flywaydb.org/flyway-core) + * Ehcache (org.ehcache:ehcache:3.10.8 - http://ehcache.org) + * flyway-core (org.flywaydb:flyway-core:10.10.0 - https://flywaydb.org/flyway-core) + * flyway-database-postgresql (org.flywaydb:flyway-database-postgresql:10.10.0 - https://flywaydb.org/flyway-database-postgresql) * Ogg and Vorbis for Java, Core (org.gagravarr:vorbis-java-core:0.8 - https://github.com/Gagravarr/VorbisJava) * Apache Tika plugin for Ogg, Vorbis and FLAC (org.gagravarr:vorbis-java-tika:0.8 - https://github.com/Gagravarr/VorbisJava) - * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) - * jersey-core-common (org.glassfish.jersey.core:jersey-common:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-common) - * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) - * Hibernate Validator Engine (org.hibernate.validator:hibernate-validator:6.2.5.Final - http://hibernate.org/validator/hibernate-validator) - * Hibernate Validator Portable Extension (org.hibernate.validator:hibernate-validator-cdi:6.2.5.Final - http://hibernate.org/validator/hibernate-validator-cdi) + * jersey-core-common (org.glassfish.jersey.core:jersey-common:3.1.5 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-common) + * Hibernate Validator Engine (org.hibernate.validator:hibernate-validator:8.0.1.Final - http://hibernate.org/validator/hibernate-validator) + * Hibernate Validator Portable Extension (org.hibernate.validator:hibernate-validator-cdi:8.0.1.Final - http://hibernate.org/validator/hibernate-validator-cdi) + * org.immutables.value-annotations (org.immutables:value-annotations:2.9.2 - http://immutables.org/value-annotations) * leveldb (org.iq80.leveldb:leveldb:0.12 - http://github.com/dain/leveldb/leveldb) * leveldb-api (org.iq80.leveldb:leveldb-api:0.12 - http://github.com/dain/leveldb/leveldb-api) - * Javassist (org.javassist:javassist:3.25.0-GA - http://www.javassist.org/) - * Java Annotation Indexer (org.jboss:jandex:2.4.2.Final - http://www.jboss.org/jandex) + * Javassist (org.javassist:javassist:3.29.2-GA - http://www.javassist.org/) * JBoss Logging 3 (org.jboss.logging:jboss-logging:3.4.3.Final - http://www.jboss.org) * JDOM (org.jdom:jdom2:2.0.6.1 - http://www.jdom.org) * jtwig-core (org.jtwig:jtwig-core:5.87.0.RELEASE - http://jtwig.org) @@ -335,115 +380,108 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * jtwig-spring (org.jtwig:jtwig-spring:5.87.0.RELEASE - http://jtwig.org) * jtwig-spring-boot-starter (org.jtwig:jtwig-spring-boot-starter:5.87.0.RELEASE - http://jtwig.org) * jtwig-web (org.jtwig:jtwig-web:5.87.0.RELEASE - http://jtwig.org) + * Proj4J (org.locationtech.proj4j:proj4j:1.1.5 - https://github.com/locationtech/proj4j) * Spatial4J (org.locationtech.spatial4j:spatial4j:0.7 - https://projects.eclipse.org/projects/locationtech.spatial4j) * MockServer Java Client (org.mock-server:mockserver-client-java:5.11.2 - http://www.mock-server.com) * MockServer Core (org.mock-server:mockserver-core:5.11.2 - http://www.mock-server.com) * MockServer JUnit 4 Integration (org.mock-server:mockserver-junit-rule:5.11.2 - http://www.mock-server.com) * MockServer & Proxy Netty (org.mock-server:mockserver-netty:5.11.2 - http://www.mock-server.com) - * MortBay :: Apache EL :: API and Implementation (org.mortbay.jasper:apache-el:8.5.35.1 - https://github.com/jetty-project/jasper-jsp/apache-el) - * MortBay :: Apache Jasper :: JSP Implementation (org.mortbay.jasper:apache-jsp:8.5.35.1 - https://github.com/jetty-project/jasper-jsp/apache-jsp) * Jetty Server (org.mortbay.jetty:jetty:6.1.26 - http://www.eclipse.org/jetty/jetty-parent/project/modules/jetty) * Jetty Servlet Tester (org.mortbay.jetty:jetty-servlet-tester:6.1.26 - http://www.eclipse.org/jetty/jetty-parent/project/jetty-servlet-tester) * Jetty Utilities (org.mortbay.jetty:jetty-util:6.1.26 - http://www.eclipse.org/jetty/jetty-parent/project/jetty-util) * Servlet Specification API (org.mortbay.jetty:servlet-api:2.5-20081211 - http://jetty.mortbay.org/servlet-api) - * jwarc (org.netpreserve:jwarc:0.19.0 - https://github.com/iipc/jwarc) + * jwarc (org.netpreserve:jwarc:0.29.0 - https://github.com/iipc/jwarc) * Objenesis (org.objenesis:objenesis:3.2 - http://objenesis.org/objenesis) * parboiled-core (org.parboiled:parboiled-core:1.3.1 - http://parboiled.org) * parboiled-java (org.parboiled:parboiled-java:1.3.1 - http://parboiled.org) + * org.roaringbitmap:RoaringBitmap (org.roaringbitmap:RoaringBitmap:0.9.45 - https://github.com/RoaringBitmap/RoaringBitmap) + * org.roaringbitmap:shims (org.roaringbitmap:shims:0.9.45 - https://github.com/RoaringBitmap/RoaringBitmap) * RRD4J (org.rrd4j:rrd4j:3.5 - https://github.com/rrd4j/rrd4j/) - * Scala Library (org.scala-lang:scala-library:2.13.9 - https://www.scala-lang.org/) + * Scala Library (org.scala-lang:scala-library:2.13.11 - https://www.scala-lang.org/) * Scala Compiler (org.scala-lang:scala-reflect:2.13.0 - https://www.scala-lang.org/) * scala-collection-compat (org.scala-lang.modules:scala-collection-compat_2.13:2.1.6 - http://www.scala-lang.org/) * scala-java8-compat (org.scala-lang.modules:scala-java8-compat_2.13:0.9.0 - http://www.scala-lang.org/) * scala-parser-combinators (org.scala-lang.modules:scala-parser-combinators_2.13:1.1.2 - http://www.scala-lang.org/) * scala-xml (org.scala-lang.modules:scala-xml_2.13:1.3.0 - http://www.scala-lang.org/) * JSONassert (org.skyscreamer:jsonassert:1.5.1 - https://github.com/skyscreamer/JSONassert) - * JCL 1.2 implemented over SLF4J (org.slf4j:jcl-over-slf4j:1.7.36 - http://www.slf4j.org) - * Spring AOP (org.springframework:spring-aop:5.3.27 - https://github.com/spring-projects/spring-framework) - * Spring Beans (org.springframework:spring-beans:5.3.27 - https://github.com/spring-projects/spring-framework) - * Spring Context (org.springframework:spring-context:5.3.27 - https://github.com/spring-projects/spring-framework) - * Spring Context Support (org.springframework:spring-context-support:5.3.27 - https://github.com/spring-projects/spring-framework) - * Spring Core (org.springframework:spring-core:5.3.27 - https://github.com/spring-projects/spring-framework) - * Spring Expression Language (SpEL) (org.springframework:spring-expression:5.3.27 - https://github.com/spring-projects/spring-framework) - * Spring Commons Logging Bridge (org.springframework:spring-jcl:5.3.27 - https://github.com/spring-projects/spring-framework) - * Spring JDBC (org.springframework:spring-jdbc:5.3.27 - https://github.com/spring-projects/spring-framework) - * Spring Object/Relational Mapping (org.springframework:spring-orm:5.3.27 - https://github.com/spring-projects/spring-framework) - * Spring TestContext Framework (org.springframework:spring-test:5.3.27 - https://github.com/spring-projects/spring-framework) - * Spring Transaction (org.springframework:spring-tx:5.3.27 - https://github.com/spring-projects/spring-framework) - * Spring Web (org.springframework:spring-web:5.3.27 - https://github.com/spring-projects/spring-framework) - * Spring Web MVC (org.springframework:spring-webmvc:5.3.27 - https://github.com/spring-projects/spring-framework) - * spring-boot (org.springframework.boot:spring-boot:2.7.12 - https://spring.io/projects/spring-boot) - * spring-boot-actuator (org.springframework.boot:spring-boot-actuator:2.7.12 - https://spring.io/projects/spring-boot) - * spring-boot-actuator-autoconfigure (org.springframework.boot:spring-boot-actuator-autoconfigure:2.7.12 - https://spring.io/projects/spring-boot) - * spring-boot-autoconfigure (org.springframework.boot:spring-boot-autoconfigure:2.7.12 - https://spring.io/projects/spring-boot) + * JCL 1.2 implemented over SLF4J (org.slf4j:jcl-over-slf4j:2.0.11 - http://www.slf4j.org) + * Spring AOP (org.springframework:spring-aop:6.1.8 - https://github.com/spring-projects/spring-framework) + * Spring Beans (org.springframework:spring-beans:6.1.8 - https://github.com/spring-projects/spring-framework) + * Spring Context (org.springframework:spring-context:6.1.8 - https://github.com/spring-projects/spring-framework) + * Spring Context Support (org.springframework:spring-context-support:6.1.8 - https://github.com/spring-projects/spring-framework) + * Spring Core (org.springframework:spring-core:6.1.8 - https://github.com/spring-projects/spring-framework) + * Spring Expression Language (SpEL) (org.springframework:spring-expression:6.1.8 - https://github.com/spring-projects/spring-framework) + * Spring Commons Logging Bridge (org.springframework:spring-jcl:6.1.8 - https://github.com/spring-projects/spring-framework) + * Spring JDBC (org.springframework:spring-jdbc:6.1.8 - https://github.com/spring-projects/spring-framework) + * Spring Object/Relational Mapping (org.springframework:spring-orm:6.1.8 - https://github.com/spring-projects/spring-framework) + * Spring TestContext Framework (org.springframework:spring-test:6.1.8 - https://github.com/spring-projects/spring-framework) + * Spring Transaction (org.springframework:spring-tx:6.1.8 - https://github.com/spring-projects/spring-framework) + * Spring Web (org.springframework:spring-web:6.1.8 - https://github.com/spring-projects/spring-framework) + * Spring Web MVC (org.springframework:spring-webmvc:6.1.8 - https://github.com/spring-projects/spring-framework) + * spring-boot (org.springframework.boot:spring-boot:3.2.6 - https://spring.io/projects/spring-boot) + * spring-boot-actuator (org.springframework.boot:spring-boot-actuator:3.2.6 - https://spring.io/projects/spring-boot) + * spring-boot-actuator-autoconfigure (org.springframework.boot:spring-boot-actuator-autoconfigure:3.2.6 - https://spring.io/projects/spring-boot) + * spring-boot-autoconfigure (org.springframework.boot:spring-boot-autoconfigure:3.2.6 - https://spring.io/projects/spring-boot) * Spring Boot Configuration Processor (org.springframework.boot:spring-boot-configuration-processor:2.0.0.RELEASE - https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot-tools/spring-boot-configuration-processor) - * spring-boot-starter (org.springframework.boot:spring-boot-starter:2.7.12 - https://spring.io/projects/spring-boot) - * spring-boot-starter-actuator (org.springframework.boot:spring-boot-starter-actuator:2.7.12 - https://spring.io/projects/spring-boot) - * spring-boot-starter-aop (org.springframework.boot:spring-boot-starter-aop:2.7.12 - https://spring.io/projects/spring-boot) - * spring-boot-starter-cache (org.springframework.boot:spring-boot-starter-cache:2.7.12 - https://spring.io/projects/spring-boot) - * spring-boot-starter-data-rest (org.springframework.boot:spring-boot-starter-data-rest:2.7.12 - https://spring.io/projects/spring-boot) - * spring-boot-starter-json (org.springframework.boot:spring-boot-starter-json:2.7.12 - https://spring.io/projects/spring-boot) - * spring-boot-starter-log4j2 (org.springframework.boot:spring-boot-starter-log4j2:2.7.12 - https://spring.io/projects/spring-boot) - * spring-boot-starter-security (org.springframework.boot:spring-boot-starter-security:2.7.12 - https://spring.io/projects/spring-boot) - * spring-boot-starter-test (org.springframework.boot:spring-boot-starter-test:2.7.12 - https://spring.io/projects/spring-boot) - * spring-boot-starter-tomcat (org.springframework.boot:spring-boot-starter-tomcat:2.7.12 - https://spring.io/projects/spring-boot) - * spring-boot-starter-web (org.springframework.boot:spring-boot-starter-web:2.7.12 - https://spring.io/projects/spring-boot) - * spring-boot-test (org.springframework.boot:spring-boot-test:2.7.12 - https://spring.io/projects/spring-boot) - * spring-boot-test-autoconfigure (org.springframework.boot:spring-boot-test-autoconfigure:2.7.12 - https://spring.io/projects/spring-boot) - * Spring Data Core (org.springframework.data:spring-data-commons:2.7.12 - https://www.spring.io/spring-data/spring-data-commons) - * Spring Data REST - Core (org.springframework.data:spring-data-rest-core:3.7.12 - https://www.spring.io/spring-data/spring-data-rest-parent/spring-data-rest-core) - * Spring Data REST - WebMVC (org.springframework.data:spring-data-rest-webmvc:3.7.12 - https://www.spring.io/spring-data/spring-data-rest-parent/spring-data-rest-webmvc) - * Spring HATEOAS (org.springframework.hateoas:spring-hateoas:1.5.4 - https://github.com/spring-projects/spring-hateoas) - * Spring Plugin - Core (org.springframework.plugin:spring-plugin-core:2.0.0.RELEASE - https://github.com/spring-projects/spring-plugin/spring-plugin-core) - * spring-security-config (org.springframework.security:spring-security-config:5.7.8 - https://spring.io/projects/spring-security) - * spring-security-core (org.springframework.security:spring-security-core:5.7.8 - https://spring.io/projects/spring-security) - * spring-security-crypto (org.springframework.security:spring-security-crypto:5.7.8 - https://spring.io/projects/spring-security) - * spring-security-test (org.springframework.security:spring-security-test:5.7.8 - https://spring.io/projects/spring-security) - * spring-security-web (org.springframework.security:spring-security-web:5.7.8 - https://spring.io/projects/spring-security) - * SWORD v2 :: Common Server Library (org.swordapp:sword2-server:1.0 - http://www.swordapp.org/) - * snappy-java (org.xerial.snappy:snappy-java:1.1.7.6 - https://github.com/xerial/snappy-java) + * spring-boot-starter (org.springframework.boot:spring-boot-starter:3.2.6 - https://spring.io/projects/spring-boot) + * spring-boot-starter-actuator (org.springframework.boot:spring-boot-starter-actuator:3.2.6 - https://spring.io/projects/spring-boot) + * spring-boot-starter-aop (org.springframework.boot:spring-boot-starter-aop:3.2.6 - https://spring.io/projects/spring-boot) + * spring-boot-starter-cache (org.springframework.boot:spring-boot-starter-cache:3.2.6 - https://spring.io/projects/spring-boot) + * spring-boot-starter-data-rest (org.springframework.boot:spring-boot-starter-data-rest:3.2.6 - https://spring.io/projects/spring-boot) + * spring-boot-starter-json (org.springframework.boot:spring-boot-starter-json:3.2.6 - https://spring.io/projects/spring-boot) + * spring-boot-starter-log4j2 (org.springframework.boot:spring-boot-starter-log4j2:3.2.6 - https://spring.io/projects/spring-boot) + * spring-boot-starter-security (org.springframework.boot:spring-boot-starter-security:3.2.6 - https://spring.io/projects/spring-boot) + * spring-boot-starter-test (org.springframework.boot:spring-boot-starter-test:3.2.6 - https://spring.io/projects/spring-boot) + * spring-boot-starter-tomcat (org.springframework.boot:spring-boot-starter-tomcat:3.2.6 - https://spring.io/projects/spring-boot) + * spring-boot-starter-web (org.springframework.boot:spring-boot-starter-web:3.2.6 - https://spring.io/projects/spring-boot) + * spring-boot-test (org.springframework.boot:spring-boot-test:3.2.6 - https://spring.io/projects/spring-boot) + * spring-boot-test-autoconfigure (org.springframework.boot:spring-boot-test-autoconfigure:3.2.6 - https://spring.io/projects/spring-boot) + * Spring Data Core (org.springframework.data:spring-data-commons:3.2.6 - https://spring.io/projects/spring-data) + * Spring Data REST - Core (org.springframework.data:spring-data-rest-core:4.2.6 - https://www.spring.io/spring-data/spring-data-rest-parent/spring-data-rest-core) + * Spring Data REST - WebMVC (org.springframework.data:spring-data-rest-webmvc:4.2.6 - https://www.spring.io/spring-data/spring-data-rest-parent/spring-data-rest-webmvc) + * Spring HATEOAS (org.springframework.hateoas:spring-hateoas:2.2.2 - https://github.com/spring-projects/spring-hateoas) + * Spring Plugin - Core (org.springframework.plugin:spring-plugin-core:3.0.0 - https://github.com/spring-projects/spring-plugin/spring-plugin-core) + * spring-security-config (org.springframework.security:spring-security-config:6.2.4 - https://spring.io/projects/spring-security) + * spring-security-core (org.springframework.security:spring-security-core:6.2.4 - https://spring.io/projects/spring-security) + * spring-security-crypto (org.springframework.security:spring-security-crypto:6.2.4 - https://spring.io/projects/spring-security) + * spring-security-test (org.springframework.security:spring-security-test:6.2.4 - https://spring.io/projects/spring-security) + * spring-security-web (org.springframework.security:spring-security-web:6.2.4 - https://spring.io/projects/spring-security) + * snappy-java (org.xerial.snappy:snappy-java:1.1.10.1 - https://github.com/xerial/snappy-java) * xml-matchers (org.xmlmatchers:xml-matchers:0.10 - http://code.google.com/p/xml-matchers/) - * org.xmlunit:xmlunit-core (org.xmlunit:xmlunit-core:2.8.0 - https://www.xmlunit.org/) + * org.xmlunit:xmlunit-core (org.xmlunit:xmlunit-core:2.10.0 - https://www.xmlunit.org/) * org.xmlunit:xmlunit-core (org.xmlunit:xmlunit-core:2.9.1 - https://www.xmlunit.org/) * org.xmlunit:xmlunit-placeholders (org.xmlunit:xmlunit-placeholders:2.8.0 - https://www.xmlunit.org/xmlunit-placeholders/) - * SnakeYAML (org.yaml:snakeyaml:1.30 - https://bitbucket.org/snakeyaml/snakeyaml) + * SnakeYAML (org.yaml:snakeyaml:2.2 - https://bitbucket.org/snakeyaml/snakeyaml) * software.amazon.ion:ion-java (software.amazon.ion:ion-java:1.0.2 - https://github.com/amznlabs/ion-java/) - * Xalan Java Serializer (xalan:serializer:2.7.2 - http://xml.apache.org/xalan-j/) - * xalan (xalan:xalan:2.7.0 - no url defined) - * Xalan Java (xalan:xalan:2.7.2 - http://xml.apache.org/xalan-j/) * Xerces2-j (xerces:xercesImpl:2.12.2 - https://xerces.apache.org/xerces2-j/) - * XML Commons External Components XML APIs (xml-apis:xml-apis:1.4.01 - http://xml.apache.org/commons/components/external/) BSD License: - * AntLR Parser Generator (antlr:antlr:2.7.7 - http://www.antlr.org/) * Adobe XMPCore (com.adobe.xmp:xmpcore:6.1.11 - https://www.adobe.com/devnet/xmp/library/eula-xmp-library-java.html) * coverity-escapers (com.coverity.security:coverity-escapers:1.1.1 - http://coverity.com/security) * Java Advanced Imaging Image I/O Tools API core (standalone) (com.github.jai-imageio:jai-imageio-core:1.4.0 - https://github.com/jai-imageio/jai-imageio-core) - * JSONLD Java :: Core (com.github.jsonld-java:jsonld-java:0.5.1 - http://github.com/jsonld-java/jsonld-java/jsonld-java/) - * curvesapi (com.github.virtuald:curvesapi:1.07 - https://github.com/virtuald/curvesapi) - * Protocol Buffers [Core] (com.google.protobuf:protobuf-java:3.11.0 - https://developers.google.com/protocol-buffers/protobuf-java/) + * JSONLD Java :: Core (com.github.jsonld-java:jsonld-java:0.13.4 - http://github.com/jsonld-java/jsonld-java/jsonld-java/) + * curvesapi (com.github.virtuald:curvesapi:1.08 - https://github.com/virtuald/curvesapi) + * Protocol Buffers [Core] (com.google.protobuf:protobuf-java:3.15.0 - https://developers.google.com/protocol-buffers/protobuf-java/) + * Protocol Buffers [Core] (com.google.protobuf:protobuf-java:3.23.3 - https://developers.google.com/protocol-buffers/protobuf-java/) * JZlib (com.jcraft:jzlib:1.1.3 - http://www.jcraft.com/jzlib/) - * dnsjava (dnsjava:dnsjava:2.1.7 - http://www.dnsjava.org) - * jaxen (jaxen:jaxen:1.1.6 - http://jaxen.codehaus.org/) - * ANTLR 4 Runtime (org.antlr:antlr4-runtime:4.5.1-1 - http://www.antlr.org/antlr4-runtime) - * commons-compiler (org.codehaus.janino:commons-compiler:3.0.9 - http://janino-compiler.github.io/commons-compiler/) - * janino (org.codehaus.janino:janino:3.0.9 - http://janino-compiler.github.io/janino/) + * dnsjava (dnsjava:dnsjava:2.1.9 - http://www.dnsjava.org) + * jaxen (jaxen:jaxen:2.0.0 - http://www.cafeconleche.org/jaxen/jaxen) + * ANTLR 4 Runtime (org.antlr:antlr4-runtime:4.13.1 - https://www.antlr.org/antlr4-runtime/) + * commons-compiler (org.codehaus.janino:commons-compiler:3.1.8 - http://janino-compiler.github.io/commons-compiler/) + * janino (org.codehaus.janino:janino:3.1.8 - http://janino-compiler.github.io/janino/) * Stax2 API (org.codehaus.woodstox:stax2-api:4.2.1 - http://github.com/FasterXML/stax2-api) - * Hamcrest Date (org.exparity:hamcrest-date:2.0.7 - https://github.com/exparity/hamcrest-date) - * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) - * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) + * Hamcrest Date (org.exparity:hamcrest-date:2.0.8 - https://github.com/exparity/hamcrest-date) * Hamcrest (org.hamcrest:hamcrest:2.2 - http://hamcrest.org/JavaHamcrest/) - * Hamcrest All (org.hamcrest:hamcrest-all:1.3 - https://github.com/hamcrest/JavaHamcrest/hamcrest-all) - * Hamcrest Core (org.hamcrest:hamcrest-core:1.3 - https://github.com/hamcrest/JavaHamcrest/hamcrest-core) + * Hamcrest Core (org.hamcrest:hamcrest-core:2.2 - http://hamcrest.org/JavaHamcrest/) * HdrHistogram (org.hdrhistogram:HdrHistogram:2.1.12 - http://hdrhistogram.github.io/HdrHistogram/) * JBibTeX (org.jbibtex:jbibtex:1.0.20 - http://www.jbibtex.org) * asm (org.ow2.asm:asm:8.0.1 - http://asm.ow2.io/) - * asm-analysis (org.ow2.asm:asm-analysis:7.1 - http://asm.ow2.org/) + * asm-analysis (org.ow2.asm:asm-analysis:8.0.1 - http://asm.ow2.io/) * asm-commons (org.ow2.asm:asm-commons:8.0.1 - http://asm.ow2.io/) - * asm-tree (org.ow2.asm:asm-tree:7.1 - http://asm.ow2.org/) + * asm-tree (org.ow2.asm:asm-tree:8.0.1 - http://asm.ow2.io/) * asm-util (org.ow2.asm:asm-util:7.1 - http://asm.ow2.org/) - * PostgreSQL JDBC Driver (org.postgresql:postgresql:42.6.0 - https://jdbc.postgresql.org) + * PostgreSQL JDBC Driver (org.postgresql:postgresql:42.7.3 - https://jdbc.postgresql.org) * Reflections (org.reflections:reflections:0.9.12 - http://github.com/ronmamo/reflections) * JMatIO (org.tallison:jmatio:1.5 - https://github.com/tballison/jmatio) * XMLUnit for Java (xmlunit:xmlunit:1.3 - http://xmlunit.sourceforge.net/) @@ -454,101 +492,121 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines Common Development and Distribution License (CDDL): - * istack common utility code runtime (com.sun.istack:istack-commons-runtime:3.0.7 - http://java.net/istack-commons/istack-commons-runtime/) - * JavaMail API (com.sun.mail:javax.mail:1.6.2 - http://javaee.github.io/javamail/javax.mail) * JavaMail API (no providers) (com.sun.mail:mailapi:1.6.2 - http://javaee.github.io/javamail/mailapi) * Old JAXB Core (com.sun.xml.bind:jaxb-core:2.3.0.1 - http://jaxb.java.net/jaxb-bundles/jaxb-core) * Old JAXB Runtime (com.sun.xml.bind:jaxb-impl:2.3.1 - http://jaxb.java.net/jaxb-bundles/jaxb-impl) - * Jakarta Annotations API (jakarta.annotation:jakarta.annotation-api:1.3.5 - https://projects.eclipse.org/projects/ee4j.ca) - * jakarta.ws.rs-api (jakarta.ws.rs:jakarta.ws.rs-api:2.1.6 - https://github.com/eclipse-ee4j/jaxrs-api) - * JavaBeans Activation Framework (JAF) (javax.activation:activation:1.1 - http://java.sun.com/products/javabeans/jaf/index.jsp) + * Jakarta Annotations API (jakarta.annotation:jakarta.annotation-api:2.1.1 - https://projects.eclipse.org/projects/ee4j.ca) + * Jakarta Mail API (jakarta.mail:jakarta.mail-api:2.1.3 - https://projects.eclipse.org/projects/ee4j/jakarta.mail-api) + * Jakarta Servlet (jakarta.servlet:jakarta.servlet-api:6.0.0 - https://projects.eclipse.org/projects/ee4j.servlet) + * jakarta.transaction API (jakarta.transaction:jakarta.transaction-api:2.0.1 - https://projects.eclipse.org/projects/ee4j.jta) * JavaBeans Activation Framework API jar (javax.activation:javax.activation-api:1.2.0 - http://java.net/all/javax.activation-api/) - * javax.annotation API (javax.annotation:javax.annotation-api:1.3.2 - http://jcp.org/en/jsr/detail?id=250) + * javax.annotation API (javax.annotation:javax.annotation-api:1.3 - http://jcp.org/en/jsr/detail?id=250) * Java Servlet API (javax.servlet:javax.servlet-api:3.1.0 - http://servlet-spec.java.net) * javax.transaction API (javax.transaction:javax.transaction-api:1.3 - http://jta-spec.java.net) * jaxb-api (javax.xml.bind:jaxb-api:2.3.1 - https://github.com/javaee/jaxb-spec/jaxb-api) * JHighlight (org.codelibs:jhighlight:1.1.0 - https://github.com/codelibs/jhighlight) - * HK2 API module (org.glassfish.hk2:hk2-api:2.6.1 - https://github.com/eclipse-ee4j/glassfish-hk2/hk2-api) - * ServiceLocator Default Implementation (org.glassfish.hk2:hk2-locator:2.6.1 - https://github.com/eclipse-ee4j/glassfish-hk2/hk2-locator) - * HK2 Implementation Utilities (org.glassfish.hk2:hk2-utils:2.6.1 - https://github.com/eclipse-ee4j/glassfish-hk2/hk2-utils) + * Angus Mail default provider (org.eclipse.angus:jakarta.mail:2.0.3 - http://eclipse-ee4j.github.io/angus-mail/jakarta.mail) + * HK2 API module (org.glassfish.hk2:hk2-api:3.0.5 - https://github.com/eclipse-ee4j/glassfish-hk2/hk2-api) + * ServiceLocator Default Implementation (org.glassfish.hk2:hk2-locator:3.0.5 - https://github.com/eclipse-ee4j/glassfish-hk2/hk2-locator) + * HK2 Implementation Utilities (org.glassfish.hk2:hk2-utils:3.0.5 - https://github.com/eclipse-ee4j/glassfish-hk2/hk2-utils) * OSGi resource locator (org.glassfish.hk2:osgi-resource-locator:1.0.3 - https://projects.eclipse.org/projects/ee4j/osgi-resource-locator) - * aopalliance version 1.0 repackaged as a module (org.glassfish.hk2.external:aopalliance-repackaged:2.6.1 - https://github.com/eclipse-ee4j/glassfish-hk2/external/aopalliance-repackaged) - * javax.inject:1 as OSGi bundle (org.glassfish.hk2.external:jakarta.inject:2.6.1 - https://github.com/eclipse-ee4j/glassfish-hk2/external/jakarta.inject) - * JAXB Runtime (org.glassfish.jaxb:jaxb-runtime:2.3.1 - http://jaxb.java.net/jaxb-runtime-parent/jaxb-runtime) - * TXW2 Runtime (org.glassfish.jaxb:txw2:2.3.1 - http://jaxb.java.net/jaxb-txw-parent/txw2) - * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) - * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) - * Java Transaction API (org.jboss.spec.javax.transaction:jboss-transaction-api_1.2_spec:1.1.1.Final - http://www.jboss.org/jboss-transaction-api_1.2_spec) - * Extended StAX API (org.jvnet.staxex:stax-ex:1.8 - http://stax-ex.java.net/) + * aopalliance version 1.0 repackaged as a module (org.glassfish.hk2.external:aopalliance-repackaged:3.0.5 - https://github.com/eclipse-ee4j/glassfish-hk2/external/aopalliance-repackaged) + * jersey-core-client (org.glassfish.jersey.core:jersey-client:3.1.5 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) + * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:3.1.5 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) + * jersey-media-multipart (org.glassfish.jersey.media:jersey-media-multipart:3.1.3 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-media-multipart) Cordra (Version 2) License Agreement: - * net.cnri:cnri-servlet-container (net.cnri:cnri-servlet-container:3.0.0 - https://gitlab.com/cnri/cnri-servlet-container) - * net.cnri:cnri-servlet-container-lib (net.cnri:cnri-servlet-container-lib:3.0.0 - https://gitlab.com/cnri/cnri-servlet-container) + * net.cnri:cnri-servlet-container-lib (net.cnri:cnri-servlet-container-lib:3.1.0 - https://gitlab.com/cnri/cnri-servlet-container) * net.cnri:cnriutil (net.cnri:cnriutil:2.0 - https://gitlab.com/cnri/cnriutil) + Cordra (Version 2.5.0) License Agreement: + + * net.cnri:cnri-servlet-container (net.cnri:cnri-servlet-container:3.1.0 - https://gitlab.com/cnri/cnri-servlet-container) + Eclipse Distribution License, Version 1.0: - * Jakarta Activation API jar (jakarta.activation:jakarta.activation-api:1.2.2 - https://github.com/eclipse-ee4j/jaf/jakarta.activation-api) - * Jakarta XML Binding API (jakarta.xml.bind:jakarta.xml.bind-api:2.3.3 - https://github.com/eclipse-ee4j/jaxb-api/jakarta.xml.bind-api) - * javax.persistence-api (javax.persistence:javax.persistence-api:2.2 - https://github.com/javaee/jpa-spec) - * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) - * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) - * Java Persistence API, Version 2.1 (org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.2.Final - http://hibernate.org) + * istack common utility code runtime (com.sun.istack:istack-commons-runtime:4.1.2 - https://projects.eclipse.org/projects/ee4j/istack-commons/istack-commons-runtime) + * Jakarta Activation API (jakarta.activation:jakarta.activation-api:2.1.3 - https://github.com/jakartaee/jaf-api) + * Jakarta Mail API (jakarta.mail:jakarta.mail-api:2.1.3 - https://projects.eclipse.org/projects/ee4j/jakarta.mail-api) + * Jakarta Persistence API (jakarta.persistence:jakarta.persistence-api:3.1.0 - https://github.com/eclipse-ee4j/jpa-api) + * Jakarta XML Binding API (jakarta.xml.bind:jakarta.xml.bind-api:4.0.2 - https://github.com/jakartaee/jaxb-api/jakarta.xml.bind-api) + * Angus Activation Registries (org.eclipse.angus:angus-activation:2.0.2 - https://github.com/eclipse-ee4j/angus-activation/angus-activation) + * Angus Mail default provider (org.eclipse.angus:jakarta.mail:2.0.3 - http://eclipse-ee4j.github.io/angus-mail/jakarta.mail) + * JAXB Core (org.glassfish.jaxb:jaxb-core:4.0.5 - https://eclipse-ee4j.github.io/jaxb-ri/) + * JAXB Runtime (org.glassfish.jaxb:jaxb-runtime:4.0.5 - https://eclipse-ee4j.github.io/jaxb-ri/) + * TXW2 Runtime (org.glassfish.jaxb:txw2:4.0.5 - https://eclipse-ee4j.github.io/jaxb-ri/) + * jersey-core-client (org.glassfish.jersey.core:jersey-client:3.1.5 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) + * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:3.1.5 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) + * jersey-media-multipart (org.glassfish.jersey.media:jersey-media-multipart:3.1.3 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-media-multipart) + * MIME streaming extension (org.jvnet.mimepull:mimepull:1.9.15 - https://github.com/eclipse-ee4j/metro-mimepull) + * org.locationtech.jts:jts-core (org.locationtech.jts:jts-core:1.19.0 - https://www.locationtech.org/projects/technology.jts/jts-modules/jts-core) + * org.locationtech.jts.io:jts-io-common (org.locationtech.jts.io:jts-io-common:1.19.0 - https://www.locationtech.org/projects/technology.jts/jts-modules/jts-io/jts-io-common) Eclipse Public License: * System Rules (com.github.stefanbirkner:system-rules:1.19.0 - http://stefanbirkner.github.io/system-rules/) - * H2 Database Engine (com.h2database:h2:2.1.210 - https://h2database.com) - * Jakarta Annotations API (jakarta.annotation:jakarta.annotation-api:1.3.5 - https://projects.eclipse.org/projects/ee4j.ca) - * jakarta.ws.rs-api (jakarta.ws.rs:jakarta.ws.rs-api:2.1.6 - https://github.com/eclipse-ee4j/jaxrs-api) - * javax.persistence-api (javax.persistence:javax.persistence-api:2.2 - https://github.com/javaee/jpa-spec) - * JUnit (junit:junit:4.13.1 - http://junit.org) - * AspectJ Weaver (org.aspectj:aspectjweaver:1.9.7 - https://www.eclipse.org/aspectj/) - * Eclipse Compiler for Java(TM) (org.eclipse.jdt:ecj:3.14.0 - http://www.eclipse.org/jdt) + * H2 Database Engine (com.h2database:h2:2.2.224 - https://h2database.com) + * Jakarta Annotations API (jakarta.annotation:jakarta.annotation-api:2.1.1 - https://projects.eclipse.org/projects/ee4j.ca) + * Jakarta Expression Language API (jakarta.el:jakarta.el-api:5.0.1 - https://projects.eclipse.org/projects/ee4j.el) + * Jakarta Mail API (jakarta.mail:jakarta.mail-api:2.1.3 - https://projects.eclipse.org/projects/ee4j/jakarta.mail-api) + * Jakarta Persistence API (jakarta.persistence:jakarta.persistence-api:3.1.0 - https://github.com/eclipse-ee4j/jpa-api) + * Jakarta Servlet (jakarta.servlet:jakarta.servlet-api:6.0.0 - https://projects.eclipse.org/projects/ee4j.servlet) + * jakarta.transaction API (jakarta.transaction:jakarta.transaction-api:2.0.1 - https://projects.eclipse.org/projects/ee4j.jta) + * Jakarta RESTful WS API (jakarta.ws.rs:jakarta.ws.rs-api:3.1.0 - https://github.com/eclipse-ee4j/jaxrs-api) + * JUnit (junit:junit:4.13.2 - http://junit.org) + * AspectJ Weaver (org.aspectj:aspectjweaver:1.9.22 - https://www.eclipse.org/aspectj/) + * Angus Mail default provider (org.eclipse.angus:jakarta.mail:2.0.3 - http://eclipse-ee4j.github.io/angus-mail/jakarta.mail) * Jetty :: Apache JSP Implementation (org.eclipse.jetty:apache-jsp:9.4.15.v20190215 - http://www.eclipse.org/jetty) * Apache :: JSTL module (org.eclipse.jetty:apache-jstl:9.4.15.v20190215 - http://tomcat.apache.org/taglibs/standard/) - * Jetty :: ALPN :: Client (org.eclipse.jetty:jetty-alpn-client:9.4.44.v20210927 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-client) - * Jetty :: ALPN :: JDK9 Client Implementation (org.eclipse.jetty:jetty-alpn-java-client:9.4.44.v20210927 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-java-client) - * Jetty :: ALPN :: JDK9 Server Implementation (org.eclipse.jetty:jetty-alpn-java-server:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-java-server) - * Jetty :: ALPN :: Server (org.eclipse.jetty:jetty-alpn-server:9.4.44.v20210927 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-server) - * Jetty :: ALPN :: Server (org.eclipse.jetty:jetty-alpn-server:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-server) + * Jetty :: ALPN :: Client (org.eclipse.jetty:jetty-alpn-client:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-client) + * Jetty :: ALPN :: JDK9 Client Implementation (org.eclipse.jetty:jetty-alpn-java-client:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-java-client) + * Jetty :: ALPN :: JDK9 Server Implementation (org.eclipse.jetty:jetty-alpn-java-server:9.4.15.v20190215 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-java-server) + * Jetty :: ALPN :: JDK9 Server Implementation (org.eclipse.jetty:jetty-alpn-java-server:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-java-server) + * Jetty :: ALPN :: Server (org.eclipse.jetty:jetty-alpn-server:9.4.15.v20190215 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-server) + * Jetty :: ALPN :: Server (org.eclipse.jetty:jetty-alpn-server:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-server) * Jetty :: Servlet Annotations (org.eclipse.jetty:jetty-annotations:9.4.15.v20190215 - http://www.eclipse.org/jetty) - * Jetty :: Asynchronous HTTP Client (org.eclipse.jetty:jetty-client:9.4.44.v20210927 - https://eclipse.org/jetty/jetty-client) - * Jetty :: Continuation (org.eclipse.jetty:jetty-continuation:9.4.44.v20210927 - https://eclipse.org/jetty/jetty-continuation) - * Jetty :: Continuation (org.eclipse.jetty:jetty-continuation:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-continuation) - * Jetty :: Deployers (org.eclipse.jetty:jetty-deploy:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-deploy) - * Jetty :: Http Utility (org.eclipse.jetty:jetty-http:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-http) - * Jetty :: IO Utility (org.eclipse.jetty:jetty-io:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-io) - * Jetty :: JMX Management (org.eclipse.jetty:jetty-jmx:9.4.44.v20210927 - https://eclipse.org/jetty/jetty-jmx) + * Jetty :: Asynchronous HTTP Client (org.eclipse.jetty:jetty-client:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-client) + * Jetty :: Continuation (org.eclipse.jetty:jetty-continuation:9.4.15.v20190215 - http://www.eclipse.org/jetty) + * Jetty :: Continuation (org.eclipse.jetty:jetty-continuation:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-continuation) + * Jetty :: Deployers (org.eclipse.jetty:jetty-deploy:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-deploy) + * Jetty :: Http Utility (org.eclipse.jetty:jetty-http:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-http) + * Jetty :: IO Utility (org.eclipse.jetty:jetty-io:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-io) + * Jetty :: JMX Management (org.eclipse.jetty:jetty-jmx:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-jmx) * Jetty :: JNDI Naming (org.eclipse.jetty:jetty-jndi:9.4.15.v20190215 - http://www.eclipse.org/jetty) * Jetty :: Plus (org.eclipse.jetty:jetty-plus:9.4.15.v20190215 - http://www.eclipse.org/jetty) - * Jetty :: Rewrite Handler (org.eclipse.jetty:jetty-rewrite:9.4.44.v20210927 - https://eclipse.org/jetty/jetty-rewrite) - * Jetty :: Security (org.eclipse.jetty:jetty-security:9.4.44.v20210927 - https://eclipse.org/jetty/jetty-security) - * Jetty :: Security (org.eclipse.jetty:jetty-security:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-security) - * Jetty :: Server Core (org.eclipse.jetty:jetty-server:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-server) - * Jetty :: Servlet Handling (org.eclipse.jetty:jetty-servlet:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-servlet) - * Jetty :: Utility Servlets and Filters (org.eclipse.jetty:jetty-servlets:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-servlets) - * Jetty :: Utilities (org.eclipse.jetty:jetty-util:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-util) - * Jetty :: Utilities :: Ajax(JSON) (org.eclipse.jetty:jetty-util-ajax:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-util-ajax) - * Jetty :: Webapp Application Support (org.eclipse.jetty:jetty-webapp:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-webapp) - * Jetty :: XML utilities (org.eclipse.jetty:jetty-xml:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-xml) - * Jetty :: HTTP2 :: Client (org.eclipse.jetty.http2:http2-client:9.4.44.v20210927 - https://eclipse.org/jetty/http2-parent/http2-client) - * Jetty :: HTTP2 :: Common (org.eclipse.jetty.http2:http2-common:9.4.51.v20230217 - https://eclipse.org/jetty/http2-parent/http2-common) - * Jetty :: HTTP2 :: HPACK (org.eclipse.jetty.http2:http2-hpack:9.4.44.v20210927 - https://eclipse.org/jetty/http2-parent/http2-hpack) - * Jetty :: HTTP2 :: HTTP Client Transport (org.eclipse.jetty.http2:http2-http-client-transport:9.4.44.v20210927 - https://eclipse.org/jetty/http2-parent/http2-http-client-transport) - * Jetty :: HTTP2 :: Server (org.eclipse.jetty.http2:http2-server:9.4.51.v20230217 - https://eclipse.org/jetty/http2-parent/http2-server) + * Jetty :: Rewrite Handler (org.eclipse.jetty:jetty-rewrite:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-rewrite) + * Jetty :: Security (org.eclipse.jetty:jetty-security:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-security) + * Jetty :: Security (org.eclipse.jetty:jetty-security:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-security) + * Jetty :: Server Core (org.eclipse.jetty:jetty-server:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-server) + * Jetty :: Servlet Handling (org.eclipse.jetty:jetty-servlet:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-servlet) + * Jetty :: Utility Servlets and Filters (org.eclipse.jetty:jetty-servlets:9.4.15.v20190215 - http://www.eclipse.org/jetty) + * Jetty :: Utility Servlets and Filters (org.eclipse.jetty:jetty-servlets:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-servlets) + * Jetty :: Utilities (org.eclipse.jetty:jetty-util:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-util) + * Jetty :: Utilities :: Ajax(JSON) (org.eclipse.jetty:jetty-util-ajax:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-util-ajax) + * Jetty :: Webapp Application Support (org.eclipse.jetty:jetty-webapp:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-webapp) + * Jetty :: XML utilities (org.eclipse.jetty:jetty-xml:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-xml) + * Jetty :: XML utilities (org.eclipse.jetty:jetty-xml:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-xml) + * Jetty :: ALPN :: API (org.eclipse.jetty.alpn:alpn-api:1.1.3.v20160715 - http://www.eclipse.org/jetty/alpn-api) + * Jetty :: HTTP2 :: Client (org.eclipse.jetty.http2:http2-client:9.4.53.v20231009 - https://eclipse.org/jetty/http2-parent/http2-client) + * Jetty :: HTTP2 :: Common (org.eclipse.jetty.http2:http2-common:9.4.54.v20240208 - https://eclipse.org/jetty/http2-parent/http2-common) + * Jetty :: HTTP2 :: HPACK (org.eclipse.jetty.http2:http2-hpack:9.4.53.v20231009 - https://eclipse.org/jetty/http2-parent/http2-hpack) + * Jetty :: HTTP2 :: HTTP Client Transport (org.eclipse.jetty.http2:http2-http-client-transport:9.4.53.v20231009 - https://eclipse.org/jetty/http2-parent/http2-http-client-transport) + * Jetty :: HTTP2 :: Server (org.eclipse.jetty.http2:http2-server:9.4.15.v20190215 - https://eclipse.org/jetty/http2-parent/http2-server) + * Jetty :: HTTP2 :: Server (org.eclipse.jetty.http2:http2-server:9.4.53.v20231009 - https://eclipse.org/jetty/http2-parent/http2-server) * Jetty :: Schemas (org.eclipse.jetty.toolchain:jetty-schemas:3.1.2 - https://eclipse.org/jetty/jetty-schemas) - * HK2 API module (org.glassfish.hk2:hk2-api:2.6.1 - https://github.com/eclipse-ee4j/glassfish-hk2/hk2-api) - * ServiceLocator Default Implementation (org.glassfish.hk2:hk2-locator:2.6.1 - https://github.com/eclipse-ee4j/glassfish-hk2/hk2-locator) - * HK2 Implementation Utilities (org.glassfish.hk2:hk2-utils:2.6.1 - https://github.com/eclipse-ee4j/glassfish-hk2/hk2-utils) + * JSON-P Default Provider (org.glassfish:jakarta.json:2.0.1 - https://github.com/eclipse-ee4j/jsonp) + * HK2 API module (org.glassfish.hk2:hk2-api:3.0.5 - https://github.com/eclipse-ee4j/glassfish-hk2/hk2-api) + * ServiceLocator Default Implementation (org.glassfish.hk2:hk2-locator:3.0.5 - https://github.com/eclipse-ee4j/glassfish-hk2/hk2-locator) + * HK2 Implementation Utilities (org.glassfish.hk2:hk2-utils:3.0.5 - https://github.com/eclipse-ee4j/glassfish-hk2/hk2-utils) * OSGi resource locator (org.glassfish.hk2:osgi-resource-locator:1.0.3 - https://projects.eclipse.org/projects/ee4j/osgi-resource-locator) - * aopalliance version 1.0 repackaged as a module (org.glassfish.hk2.external:aopalliance-repackaged:2.6.1 - https://github.com/eclipse-ee4j/glassfish-hk2/external/aopalliance-repackaged) - * javax.inject:1 as OSGi bundle (org.glassfish.hk2.external:jakarta.inject:2.6.1 - https://github.com/eclipse-ee4j/glassfish-hk2/external/jakarta.inject) - * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) - * jersey-core-common (org.glassfish.jersey.core:jersey-common:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-common) - * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) - * Java Persistence API, Version 2.1 (org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.2.Final - http://hibernate.org) + * aopalliance version 1.0 repackaged as a module (org.glassfish.hk2.external:aopalliance-repackaged:3.0.5 - https://github.com/eclipse-ee4j/glassfish-hk2/external/aopalliance-repackaged) + * jersey-core-client (org.glassfish.jersey.core:jersey-client:3.1.5 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) + * jersey-core-common (org.glassfish.jersey.core:jersey-common:3.1.5 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-common) + * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:3.1.5 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) + * jersey-media-multipart (org.glassfish.jersey.media:jersey-media-multipart:3.1.3 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-media-multipart) + * org.locationtech.jts:jts-core (org.locationtech.jts:jts-core:1.19.0 - https://www.locationtech.org/projects/technology.jts/jts-modules/jts-core) + * org.locationtech.jts.io:jts-io-common (org.locationtech.jts.io:jts-io-common:1.19.0 - https://www.locationtech.org/projects/technology.jts/jts-modules/jts-io/jts-io-common) * Jetty Server (org.mortbay.jetty:jetty:6.1.26 - http://www.eclipse.org/jetty/jetty-parent/project/modules/jetty) * Jetty Servlet Tester (org.mortbay.jetty:jetty-servlet-tester:6.1.26 - http://www.eclipse.org/jetty/jetty-parent/project/jetty-servlet-tester) * Jetty Utilities (org.mortbay.jetty:jetty-util:6.1.26 - http://www.eclipse.org/jetty/jetty-parent/project/jetty-util) @@ -564,14 +622,13 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * uri-template (com.github.java-json-tools:uri-template:0.10 - https://github.com/java-json-tools/uri-template) * FindBugs-Annotations (com.google.code.findbugs:annotations:3.0.1u2 - http://findbugs.sourceforge.net/) * JHighlight (org.codelibs:jhighlight:1.1.0 - https://github.com/codelibs/jhighlight) - * Hibernate ORM - hibernate-core (org.hibernate:hibernate-core:5.6.15.Final - https://hibernate.org/orm) - * Hibernate ORM - hibernate-jcache (org.hibernate:hibernate-jcache:5.6.15.Final - https://hibernate.org/orm) - * Hibernate ORM - hibernate-jpamodelgen (org.hibernate:hibernate-jpamodelgen:5.6.15.Final - https://hibernate.org/orm) - * Hibernate Commons Annotations (org.hibernate.common:hibernate-commons-annotations:5.1.2.Final - http://hibernate.org) + * Hibernate Commons Annotations (org.hibernate.common:hibernate-commons-annotations:6.0.6.Final - http://hibernate.org) + * Hibernate ORM - hibernate-core (org.hibernate.orm:hibernate-core:6.4.8.Final - https://hibernate.org/orm) + * Hibernate ORM - hibernate-jcache (org.hibernate.orm:hibernate-jcache:6.4.8.Final - https://hibernate.org/orm) + * Hibernate ORM - hibernate-jpamodelgen (org.hibernate.orm:hibernate-jpamodelgen:6.4.8.Final - https://hibernate.org/orm) * im4java (org.im4java:im4java:1.4.0 - http://sourceforge.net/projects/im4java/) - * Javassist (org.javassist:javassist:3.25.0-GA - http://www.javassist.org/) - * XOM (xom:xom:1.2.5 - http://xom.nu) - * XOM (xom:xom:1.3.7 - https://xom.nu) + * Javassist (org.javassist:javassist:3.29.2-GA - http://www.javassist.org/) + * XOM (xom:xom:1.3.9 - https://xom.nu) Go License: @@ -579,63 +636,70 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines Handle.Net Public License Agreement (Ver.2): - * Handle Server (net.handle:handle:9.3.0 - https://www.handle.net) + * Handle Server (net.handle:handle:9.3.1 - https://www.handle.net) + + ISC License: + + * Simple Magic (com.j256.simplemagic:simplemagic:1.17 - https://256stuff.com/sources/simplemagic/) MIT License: + * dexx (com.github.andrewoma.dexx:collection:0.7 - https://github.com/andrewoma/dexx) * better-files (com.github.pathikrit:better-files_2.13:3.9.1 - https://github.com/pathikrit/better-files) * Java SemVer (com.github.zafarkhaja:java-semver:0.9.0 - https://github.com/zafarkhaja/jsemver) - * dd-plist (com.googlecode.plist:dd-plist:1.25 - http://www.github.com/3breadt/dd-plist) - * DigitalCollections: IIIF API Library (de.digitalcollections.iiif:iiif-apis:0.3.9 - https://github.com/dbmdz/iiif-apis) + * dd-plist (com.googlecode.plist:dd-plist:1.28 - http://www.github.com/3breadt/dd-plist) + * DigitalCollections: IIIF API Library (de.digitalcollections.iiif:iiif-apis:0.3.10 - https://github.com/dbmdz/iiif-apis) * s3mock (io.findify:s3mock_2.13:0.2.6 - https://github.com/findify/s3mock) + * ClassGraph (io.github.classgraph:classgraph:4.8.165 - https://github.com/classgraph/classgraph) * JOpt Simple (net.sf.jopt-simple:jopt-simple:5.0.4 - http://jopt-simple.github.io/jopt-simple) - * Bouncy Castle S/MIME API (org.bouncycastle:bcmail-jdk15on:1.70 - https://www.bouncycastle.org/java.html) - * Bouncy Castle PKIX, CMS, EAC, TSP, PKCS, OCSP, CMP, and CRMF APIs (org.bouncycastle:bcpkix-jdk15on:1.70 - https://www.bouncycastle.org/java.html) - * Bouncy Castle Provider (org.bouncycastle:bcprov-jdk15on:1.70 - https://www.bouncycastle.org/java.html) - * Bouncy Castle ASN.1 Extension and Utility APIs (org.bouncycastle:bcutil-jdk15on:1.70 - https://www.bouncycastle.org/java.html) + * Bouncy Castle S/MIME API (org.bouncycastle:bcmail-jdk18on:1.77 - https://www.bouncycastle.org/java.html) + * Bouncy Castle PKIX, CMS, EAC, TSP, PKCS, OCSP, CMP, and CRMF APIs (org.bouncycastle:bcpkix-jdk15on:1.67 - http://www.bouncycastle.org/java.html) + * Bouncy Castle PKIX, CMS, EAC, TSP, PKCS, OCSP, CMP, and CRMF APIs (org.bouncycastle:bcpkix-jdk18on:1.78.1 - https://www.bouncycastle.org/java.html) + * Bouncy Castle Provider (org.bouncycastle:bcprov-jdk15on:1.67 - http://www.bouncycastle.org/java.html) + * Bouncy Castle Provider (org.bouncycastle:bcprov-jdk18on:1.78.1 - https://www.bouncycastle.org/java.html) + * Bouncy Castle ASN.1 Extension and Utility APIs (org.bouncycastle:bcutil-jdk18on:1.78.1 - https://www.bouncycastle.org/java.html) * org.brotli:dec (org.brotli:dec:0.1.2 - http://brotli.org/dec) - * Checker Qual (org.checkerframework:checker-qual:3.10.0 - https://checkerframework.org) * Checker Qual (org.checkerframework:checker-qual:3.31.0 - https://checkerframework.org) - * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) - * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) + * jersey-core-client (org.glassfish.jersey.core:jersey-client:3.1.5 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) + * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:3.1.5 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) + * jersey-media-multipart (org.glassfish.jersey.media:jersey-media-multipart:3.1.3 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-media-multipart) * mockito-core (org.mockito:mockito-core:3.12.4 - https://github.com/mockito/mockito) * mockito-inline (org.mockito:mockito-inline:3.12.4 - https://github.com/mockito/mockito) - * ORCID - Model (org.orcid:orcid-model:3.0.2 - http://github.com/ORCID/orcid-model) - * JUL to SLF4J bridge (org.slf4j:jul-to-slf4j:1.7.36 - http://www.slf4j.org) - * SLF4J API Module (org.slf4j:slf4j-api:1.7.36 - http://www.slf4j.org) + * SLF4J API Module (org.slf4j:slf4j-api:2.0.11 - http://www.slf4j.org) * SLF4J Extensions Module (org.slf4j:slf4j-ext:1.7.28 - http://www.slf4j.org) * HAL Browser (org.webjars:hal-browser:ad9b865 - http://webjars.org) * toastr (org.webjars.bowergithub.codeseven:toastr:2.1.4 - http://webjars.org) * backbone (org.webjars.bowergithub.jashkenas:backbone:1.4.1 - https://www.webjars.org) * underscore (org.webjars.bowergithub.jashkenas:underscore:1.13.2 - https://www.webjars.org) - * jquery (org.webjars.bowergithub.jquery:jquery-dist:3.6.0 - https://www.webjars.org) - * urijs (org.webjars.bowergithub.medialize:uri.js:1.19.10 - https://www.webjars.org) - * bootstrap (org.webjars.bowergithub.twbs:bootstrap:4.6.1 - https://www.webjars.org) - * core-js (org.webjars.npm:core-js:3.30.1 - https://www.webjars.org) + * jquery (org.webjars.bowergithub.jquery:jquery-dist:3.7.1 - https://www.webjars.org) + * urijs (org.webjars.bowergithub.medialize:uri.js:1.19.11 - https://www.webjars.org) + * bootstrap (org.webjars.bowergithub.twbs:bootstrap:4.6.2 - https://www.webjars.org) + * core-js (org.webjars.npm:core-js:3.37.1 - https://www.webjars.org) * @json-editor/json-editor (org.webjars.npm:json-editor__json-editor:2.6.1 - https://www.webjars.org) Mozilla Public License: - * juniversalchardet (com.googlecode.juniversalchardet:juniversalchardet:1.0.3 - http://juniversalchardet.googlecode.com/) - * H2 Database Engine (com.h2database:h2:2.1.210 - https://h2database.com) + * juniversalchardet (com.github.albfernandez:juniversalchardet:2.4.0 - https://github.com/albfernandez/juniversalchardet) + * H2 Database Engine (com.h2database:h2:2.2.224 - https://h2database.com) * Saxon-HE (net.sf.saxon:Saxon-HE:9.8.0-14 - http://www.saxonica.com/) - * Javassist (org.javassist:javassist:3.25.0-GA - http://www.javassist.org/) + * Javassist (org.javassist:javassist:3.29.2-GA - http://www.javassist.org/) * Mozilla Rhino (org.mozilla:rhino:1.7.7.2 - https://developer.mozilla.org/en/Rhino) Public Domain: - * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) - * jersey-core-common (org.glassfish.jersey.core:jersey-common:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-common) - * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) + * jersey-core-client (org.glassfish.jersey.core:jersey-client:3.1.5 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) + * jersey-core-common (org.glassfish.jersey.core:jersey-common:3.1.5 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-common) + * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:3.1.5 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) + * jersey-media-multipart (org.glassfish.jersey.media:jersey-media-multipart:3.1.3 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-media-multipart) * HdrHistogram (org.hdrhistogram:HdrHistogram:2.1.12 - http://hdrhistogram.github.io/HdrHistogram/) - * JSON in Java (org.json:json:20230227 - https://github.com/douglascrockford/JSON-java) + * JSON in Java (org.json:json:20231013 - https://github.com/douglascrockford/JSON-java) * LatencyUtils (org.latencyutils:LatencyUtils:2.0.3 - http://latencyutils.github.io/LatencyUtils/) * Reflections (org.reflections:reflections:0.9.12 - http://github.com/ronmamo/reflections) * XZ for Java (org.tukaani:xz:1.9 - https://tukaani.org/xz/java.html) UnRar License: - * Java Unrar (com.github.junrar:junrar:7.5.3 - https://github.com/junrar/junrar) + * Java Unrar (com.github.junrar:junrar:7.5.5 - https://github.com/junrar/junrar) Unicode/ICU License: @@ -643,10 +707,12 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines W3C license: - * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) - * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) + * jersey-core-client (org.glassfish.jersey.core:jersey-client:3.1.5 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) + * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:3.1.5 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) + * jersey-media-multipart (org.glassfish.jersey.media:jersey-media-multipart:3.1.3 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-media-multipart) jQuery license: - * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) - * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) + * jersey-core-client (org.glassfish.jersey.core:jersey-client:3.1.5 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) + * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:3.1.5 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) + * jersey-media-multipart (org.glassfish.jersey.media:jersey-media-multipart:3.1.3 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-media-multipart) diff --git a/README.md b/README.md index af9158eff361..1d93abe49948 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ DSpace consists of both a Java-based backend and an Angular-based frontend. * The REST Contract is at https://github.com/DSpace/RestContract * Frontend (https://github.com/DSpace/dspace-angular/) is the User Interface built on the REST API -Prior versions of DSpace (v6.x and below) used two different UIs (XMLUI and JSPUI). Those UIs are no longer supported in v7 (and above). +Prior versions of DSpace (v6.x and below) used two different UIs (XMLUI and JSPUI). Those UIs are no longer supported in v7 and above. * A maintenance branch for older versions is still available, see `dspace-6_x` for 6.x maintenance. ## Downloads @@ -33,18 +33,18 @@ Prior versions of DSpace (v6.x and below) used two different UIs (XMLUI and JSPU Documentation for each release may be viewed online or downloaded via our [Documentation Wiki](https://wiki.lyrasis.org/display/DSDOC/). The latest DSpace Installation instructions are available at: -https://wiki.lyrasis.org/display/DSDOC7x/Installing+DSpace +https://wiki.lyrasis.org/display/DSDOC8x/Installing+DSpace Please be aware that, as a Java web application, DSpace requires a database (PostgreSQL) and a servlet container (usually Tomcat) in order to function. More information about these and all other prerequisites can be found in the Installation instructions above. -## Running DSpace 7 in Docker +## Running DSpace 8 in Docker NOTE: At this time, we do not have production-ready Docker images for DSpace. That said, we do have quick-start Docker Compose scripts for development or testing purposes. -See [Running DSpace 7 with Docker Compose](dspace/src/main/docker-compose/README.md) +See [Running DSpace 8 with Docker Compose](dspace/src/main/docker-compose/README.md) ## Contributing @@ -64,7 +64,7 @@ Great Q&A is also available under the [DSpace tag on Stackoverflow](http://stack Additional support options are at https://wiki.lyrasis.org/display/DSPACE/Support DSpace also has an active service provider network. If you'd rather hire a service provider to -install, upgrade, customize or host DSpace, then we recommend getting in touch with one of our +install, upgrade, customize, or host DSpace, then we recommend getting in touch with one of our [Registered Service Providers](http://www.dspace.org/service-providers). ## Issue Tracker @@ -112,7 +112,7 @@ run automatically by [GitHub Actions](https://github.com/DSpace/DSpace/actions?q ``` * How to run only tests of a specific DSpace module ``` - # Before you can run only one module's tests, other modules may need installing into your ~/.m2 + # Before you can run only one module's tests, other modules may need to be installed into your ~/.m2 cd [dspace-src] mvn clean install diff --git a/checkstyle.xml b/checkstyle.xml index e0fa808d83cb..a33fc4831950 100644 --- a/checkstyle.xml +++ b/checkstyle.xml @@ -92,7 +92,7 @@ For more information on CheckStyle configurations below, see: http://checkstyle. - + diff --git a/docker-compose-cli.yml b/docker-compose-cli.yml index b2c6df636b64..91f89916d208 100644 --- a/docker-compose-cli.yml +++ b/docker-compose-cli.yml @@ -1,4 +1,3 @@ -version: "3.7" networks: # Default to using network named 'dspacenet' from docker-compose.yml. # Its full name will be prepended with the project name (e.g. "-p d7" means it will be named "d7_dspacenet") diff --git a/docker-compose.yml b/docker-compose.yml index 2e3d640940e4..6a930a8d31ec 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,3 @@ -version: '3.7' networks: dspacenet: ipam: @@ -40,8 +39,6 @@ services: ports: - published: 8080 target: 8080 - - published: 8009 - target: 8009 - published: 8000 target: 8000 stdin_open: true @@ -55,14 +52,14 @@ 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 diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index f6b06f69edb7..9b48128fcb89 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -12,7 +12,7 @@ org.dspace dspace-parent - 8.0-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,23 @@ log4j-api - org.hibernate + org.apache.logging.log4j + log4j-core + + + org.apache.logging.log4j + log4j-slf4j2-impl + + + org.hibernate.orm hibernate-core - - - - org.javassist - javassist - - - org.hibernate + org.hibernate.orm + hibernate-jpamodelgen + + + org.hibernate.orm hibernate-jcache @@ -375,43 +380,49 @@ - javax.cache - cache-api + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 - org.hibernate - hibernate-jpamodelgen + javax.cache + cache-api org.hibernate.validator hibernate-validator-cdi ${hibernate-validator.version} - - org.hibernate.javax.persistence - hibernate-jpa-2.1-api - 1.0.2.Final - org.springframework spring-orm + + + + org.springframework + spring-jcl + + net.handle handle + net.cnri cnri-servlet-container + runtime - + - org.ow2.asm - asm-commons + org.mortbay.jasper + apache-jsp - + org.bouncycastle bcpkix-jdk15on @@ -422,10 +433,12 @@ - + + org.eclipse.jetty jetty-server + runtime org.dspace @@ -435,12 +448,6 @@ org.apache.jena apache-jena-libs pom - - - log4j - log4j - - commons-cli @@ -458,10 +465,6 @@ org.apache.commons commons-dbcp2 - - commons-fileupload - commons-fileupload - commons-io @@ -480,17 +483,26 @@ commons-validator - com.sun.mail - javax.mail + jakarta.mail + jakarta.mail-api + provided + + + org.eclipse.angus + jakarta.mail - javax.servlet - javax.servlet-api + jakarta.servlet + jakarta.servlet-api provided - javax.annotation - javax.annotation-api + jakarta.annotation + jakarta.annotation-api + + + jakarta.el + jakarta.el-api jaxen @@ -586,6 +598,13 @@ solr-core test ${solr.client.version} + + + + org.antlr + antlr4-runtime + + org.apache.lucene @@ -629,7 +648,7 @@ dnsjava dnsjava - 2.1.9 + 3.6.2 @@ -665,7 +684,12 @@ org.flywaydb flyway-core - 8.5.13 + ${flyway.version} + + + org.flywaydb + flyway-database-postgresql + ${flyway.version} @@ -673,22 +697,6 @@ com.google.apis google-api-services-analytics - - com.google.api-client - google-api-client - - - com.google.http-client - google-http-client - - - com.google.http-client - google-http-client-jackson2 - - - com.google.oauth-client - google-oauth-client - @@ -701,23 +709,21 @@ - javax.inject - javax.inject - 1 - jar + jakarta.inject + jakarta.inject-api - javax.xml.bind - jaxb-api + jakarta.xml.bind + jakarta.xml.bind-api org.glassfish.jaxb jaxb-runtime - + org.glassfish.jersey.core jersey-client @@ -735,34 +741,21 @@ com.amazonaws aws-java-sdk-s3 - 1.12.261 + 1.12.779 + + - org.orcid - orcid-model - 3.0.2 + org.dspace + orcid-model-jakarta + 3.3.0 - - javax.validation - validation-api - - - com.fasterxml.jackson.jaxrs - jackson-jaxrs-json-provider - - - org.yaml - snakeyaml - org.javassist javassist - - io.swagger - swagger-jersey-jaxrs - @@ -791,18 +784,20 @@ org.apache.velocity velocity-engine-core + 2.3 org.xmlunit xmlunit-core + 2.10.0 test org.apache.bcel bcel - 6.7.0 + 6.10.0 test @@ -829,7 +824,7 @@ org.mock-server mockserver-junit-rule - 5.11.2 + 5.15.0 test @@ -837,6 +832,20 @@ org.yaml snakeyaml + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + + + + javax.xml.bind + jaxb-api + + + + javax.servlet + javax.servlet-api + @@ -856,82 +865,5 @@ - - - - - - - - io.netty - netty-buffer - 4.1.106.Final - - - io.netty - netty-transport - 4.1.106.Final - - - io.netty - netty-transport-native-unix-common - 4.1.106.Final - - - io.netty - netty-common - 4.1.106.Final - - - io.netty - netty-handler - 4.1.106.Final - - - io.netty - netty-codec - 4.1.106.Final - - - org.apache.velocity - velocity-engine-core - 2.3 - - - org.xmlunit - xmlunit-core - 2.9.1 - test - - - com.github.java-json-tools - json-schema-validator - 2.2.14 - - - jakarta.xml.bind - jakarta.xml.bind-api - 2.3.3 - - - javax.validation - validation-api - 2.0.1.Final - - - io.swagger - swagger-core - 1.6.2 - - - org.scala-lang - scala-library - 2.13.11 - test - - - - 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 2ed47bde4cd2..e86c5a69f4cf 100644 --- a/dspace-api/src/main/java/org/dspace/access/status/service/AccessStatusService.java +++ b/dspace-api/src/main/java/org/dspace/access/status/service/AccessStatusService.java @@ -18,7 +18,7 @@ * Configuration properties: (with examples) * {@code * # values for the forever embargo date threshold - * # This threshold date is used in the default access status helper to dermine if an item is + * # This threshold date is used in the default access status helper to determine if an item is * # restricted or embargoed based on the start date of the primary (or first) file policies. * # In this case, if the policy start date is inferior to the threshold date, the status will * # be embargo, else it will be restricted. diff --git a/dspace-api/src/main/java/org/dspace/administer/MetadataImporter.java b/dspace-api/src/main/java/org/dspace/administer/MetadataImporter.java index 2677cb20501f..80bda610c7dd 100644 --- a/dspace-api/src/main/java/org/dspace/administer/MetadataImporter.java +++ b/dspace-api/src/main/java/org/dspace/administer/MetadataImporter.java @@ -21,6 +21,8 @@ import org.apache.commons.cli.DefaultParser; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.authorize.AuthorizeException; import org.dspace.content.MetadataField; import org.dspace.content.MetadataSchema; @@ -30,8 +32,6 @@ import org.dspace.content.service.MetadataFieldService; import org.dspace.content.service.MetadataSchemaService; import org.dspace.core.Context; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; @@ -40,9 +40,9 @@ /** * @author Richard Jones * - * This class takes an xml document as passed in the arguments and + * This class takes an XML document as passed in the arguments and * uses it to create metadata elements in the Metadata Registry if - * they do not already exist + * they do not already exist. * * The format of the XML file is as follows: * @@ -69,7 +69,7 @@ public class MetadataImporter { /** * logging category */ - private static final Logger log = LoggerFactory.getLogger(MetadataImporter.class); + private static final Logger log = LogManager.getLogger(); /** * Default constructor @@ -89,6 +89,7 @@ private MetadataImporter() { } * @throws SAXException if parser error * @throws NonUniqueMetadataException if duplicate metadata * @throws RegistryImportException if import fails + * @throws XPathExpressionException passed through **/ public static void main(String[] args) throws ParseException, SQLException, IOException, TransformerException, @@ -125,6 +126,7 @@ public static void main(String[] args) * @throws SAXException if parser error * @throws NonUniqueMetadataException if duplicate metadata * @throws RegistryImportException if import fails + * @throws XPathExpressionException passed through */ public static void loadRegistry(String file, boolean forceUpdate) throws SQLException, IOException, TransformerException, ParserConfigurationException, AuthorizeException, @@ -203,7 +205,7 @@ private static void loadSchema(Context context, Node node, boolean updateExistin if (s == null) { // Schema does not exist - create - log.info("Registering Schema " + name + " (" + namespace + ")"); + log.info("Registering Schema {}({})", name, namespace); metadataSchemaService.create(context, name, namespace); } else { // Schema exists - if it's the same namespace, allow the type imports to continue @@ -215,7 +217,7 @@ private static void loadSchema(Context context, Node node, boolean updateExistin // It's a different namespace - have we been told to update? if (updateExisting) { // Update the existing schema namespace and continue to type import - log.info("Updating Schema " + name + ": New namespace " + namespace); + log.info("Updating Schema {}: New namespace {}", name, namespace); s.setNamespace(namespace); metadataSchemaService.update(context, s); } else { @@ -274,7 +276,7 @@ private static void loadType(Context context, Node node) if (qualifier == null) { fieldName = schema + "." + element; } - log.info("Registering metadata field " + fieldName); + log.info("Registering metadata field {}", fieldName); MetadataField field = metadataFieldService.create(context, schemaObj, element, qualifier, scopeNote); metadataFieldService.update(context, field); } diff --git a/dspace-api/src/main/java/org/dspace/administer/StructBuilder.java b/dspace-api/src/main/java/org/dspace/administer/StructBuilder.java index 13a1b3b5bbf8..8bbcfe0ff753 100644 --- a/dspace-api/src/main/java/org/dspace/administer/StructBuilder.java +++ b/dspace-api/src/main/java/org/dspace/administer/StructBuilder.java @@ -802,7 +802,7 @@ private static Element[] handleCollections(Context context, // default the short description to the empty string collectionService.setMetadataSingleValue(context, collection, - MD_SHORT_DESCRIPTION, Item.ANY, " "); + MD_SHORT_DESCRIPTION, null, " "); // import the rest of the metadata for (Map.Entry entry : collectionMap.entrySet()) { diff --git a/dspace-api/src/main/java/org/dspace/alerts/SystemWideAlert.java b/dspace-api/src/main/java/org/dspace/alerts/SystemWideAlert.java index f56cbdcce9e9..432c633ea591 100644 --- a/dspace-api/src/main/java/org/dspace/alerts/SystemWideAlert.java +++ b/dspace-api/src/main/java/org/dspace/alerts/SystemWideAlert.java @@ -8,17 +8,17 @@ package org.dspace.alerts; import java.util.Date; -import javax.persistence.Cacheable; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.SequenceGenerator; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Cacheable; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.SequenceGenerator; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.dspace.core.ReloadableEntity; diff --git a/dspace-api/src/main/java/org/dspace/alerts/dao/impl/SystemWideAlertDAOImpl.java b/dspace-api/src/main/java/org/dspace/alerts/dao/impl/SystemWideAlertDAOImpl.java index 13a0e0af236a..79dc1bcf27a3 100644 --- a/dspace-api/src/main/java/org/dspace/alerts/dao/impl/SystemWideAlertDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/alerts/dao/impl/SystemWideAlertDAOImpl.java @@ -9,10 +9,10 @@ import java.sql.SQLException; import java.util.List; -import javax.persistence.criteria.CriteriaBuilder; -import javax.persistence.criteria.CriteriaQuery; -import javax.persistence.criteria.Root; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Root; import org.dspace.alerts.SystemWideAlert; import org.dspace.alerts.SystemWideAlert_; import org.dspace.alerts.dao.SystemWideAlertDAO; diff --git a/dspace-api/src/main/java/org/dspace/app/bulkedit/DSpaceCSV.java b/dspace-api/src/main/java/org/dspace/app/bulkedit/DSpaceCSV.java index cbc052b5573f..ecd6a24287d1 100644 --- a/dspace-api/src/main/java/org/dspace/app/bulkedit/DSpaceCSV.java +++ b/dspace-api/src/main/java/org/dspace/app/bulkedit/DSpaceCSV.java @@ -188,6 +188,15 @@ public DSpaceCSV(InputStream inputStream, Context c) throws Exception { // Verify that the heading is valid in the metadata registry String[] clean = element.split("\\["); String[] parts = clean[0].split("\\."); + // Check language if present, if it's ANY then throw an exception + if (clean.length > 1 && clean[1].equals(Item.ANY + "]")) { + throw new MetadataImportInvalidHeadingException("Language ANY (*) was found in the heading " + + "of the metadata value to import, " + + "this should never be the case", + MetadataImportInvalidHeadingException.ENTRY, + columnCounter); + + } if (parts.length < 2) { throw new MetadataImportInvalidHeadingException(element, @@ -223,6 +232,15 @@ public DSpaceCSV(InputStream inputStream, Context c) throws Exception { } } + // Verify there isn’t already a header that is the same; if it already exists, + // throw MetadataImportInvalidHeadingException + String header = authorityPrefix + element; + if (headings.contains(header)) { + throw new MetadataImportInvalidHeadingException("Duplicate heading found: " + header, + MetadataImportInvalidHeadingException.ENTRY, + columnCounter); + } + // Store the heading headings.add(authorityPrefix + element); } @@ -457,7 +475,7 @@ public final void addItem(Item i) throws Exception { key = key + "." + metadataField.getQualifier(); } - // Add the language if there is one (schema.element.qualifier[langauge]) + // Add the language if there is one (schema.element.qualifier[language]) //if ((value.language != null) && (!"".equals(value.language))) if (value.getLanguage() != null) { key = key + "[" + value.getLanguage() + "]"; diff --git a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java index af6976acb14a..988768864ed4 100644 --- a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java +++ b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java @@ -20,8 +20,8 @@ import java.util.Optional; import java.util.Set; import java.util.UUID; -import javax.annotation.Nullable; +import jakarta.annotation.Nullable; import org.apache.commons.cli.ParseException; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; @@ -253,7 +253,7 @@ public void internalRun() throws Exception { displayChanges(changes, true); } - // Finsh off and tidy up + // Finish off and tidy up c.restoreAuthSystemState(); c.complete(); } catch (Exception e) { @@ -825,8 +825,10 @@ protected void compareAndUpdate(Context c, Item item, String[] fromCSV, boolean addRelationships(c, item, element, values); } else { itemService.clearMetadata(c, item, schema, element, qualifier, language); - itemService.addMetadata(c, item, schema, element, qualifier, - language, values, authorities, confidences); + if (!values.isEmpty()) { + itemService.addMetadata(c, item, schema, element, qualifier, + language, values, authorities, confidences); + } itemService.update(c, item); } } @@ -1121,8 +1123,8 @@ protected void add(Context c, String[] fromCSV, String md, BulkEditChange change .getAuthoritySeparator() + dcv.getConfidence(); } - // Add it - if ((value != null) && (!"".equals(value))) { + // Add it, if value is not blank + if (value != null && StringUtils.isNotBlank(value)) { changes.registerAdd(dcv); } } @@ -1651,7 +1653,7 @@ private void validateExpressedRelations(Context c) throws MetadataImportExceptio .getLabel(); } else { // Target item may be archived; check there. - // Add to errors if Realtionship.type cannot be derived + // Add to errors if Relationship.type cannot be derived Item targetItem = null; if (itemService.find(c, UUID.fromString(targetUUID)) != null) { targetItem = itemService.find(c, UUID.fromString(targetUUID)); @@ -1696,7 +1698,7 @@ private void validateExpressedRelations(Context c) throws MetadataImportExceptio validateTypesByTypeByTypeName(c, targetType, originType, typeName, originRow); } else { // Origin item may be archived; check there. - // Add to errors if Realtionship.type cannot be derived. + // Add to errors if Relationship.type cannot be derived. Item originItem = null; if (itemService.find(c, UUID.fromString(targetUUID)) != null) { DSpaceCSVLine dSpaceCSVLine = this.csv.getCSVLines() diff --git a/dspace-api/src/main/java/org/dspace/app/itemexport/ItemExportServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/itemexport/ItemExportServiceImpl.java index a884f9b07564..0ae2283d35be 100644 --- a/dspace-api/src/main/java/org/dspace/app/itemexport/ItemExportServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/app/itemexport/ItemExportServiceImpl.java @@ -31,8 +31,8 @@ import java.util.UUID; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; -import javax.mail.MessagingException; +import jakarta.mail.MessagingException; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.dspace.app.itemexport.service.ItemExportService; @@ -490,7 +490,7 @@ public void exportAsZip(Context context, Iterator items, File wkDir = new File(workDir); if (!wkDir.exists() && !wkDir.mkdirs()) { - logError("Unable to create working direcory"); + logError("Unable to create working directory"); } File dnDir = new File(destDirName); @@ -498,11 +498,18 @@ public void exportAsZip(Context context, Iterator items, logError("Unable to create destination directory"); } - // export the items using normal export method - exportItem(context, items, workDir, seqStart, migrate, excludeBitstreams); + try { + // export the items using normal export method (this exports items to our workDir) + exportItem(context, items, workDir, seqStart, migrate, excludeBitstreams); - // now zip up the export directory created above - zip(workDir, destDirName + System.getProperty("file.separator") + zipFileName); + // now zip up the workDir directory created above + zip(workDir, destDirName + System.getProperty("file.separator") + zipFileName); + } finally { + // Cleanup workDir created above, if it still exists + if (wkDir.exists()) { + deleteDirectory(wkDir); + } + } } @Override @@ -718,7 +725,7 @@ public void run() { try { emailErrorMessage(eperson, e1.getMessage()); } catch (Exception e) { - // wont throw here + // won't throw here } throw new IllegalStateException(e1); } finally { diff --git a/dspace-api/src/main/java/org/dspace/app/itemexport/service/ItemExportService.java b/dspace-api/src/main/java/org/dspace/app/itemexport/service/ItemExportService.java index 6ec1027709bb..c28ec70d6fe9 100644 --- a/dspace-api/src/main/java/org/dspace/app/itemexport/service/ItemExportService.java +++ b/dspace-api/src/main/java/org/dspace/app/itemexport/service/ItemExportService.java @@ -11,8 +11,8 @@ import java.util.Date; import java.util.Iterator; import java.util.List; -import javax.mail.MessagingException; +import jakarta.mail.MessagingException; import org.dspace.content.DSpaceObject; import org.dspace.content.Item; import org.dspace.core.Context; @@ -69,7 +69,7 @@ public void exportAsZip(Context context, Iterator items, boolean excludeBitstreams) throws Exception; /** - * Convenience methot to create export a single Community, Collection, or + * Convenience method to create export a single Community, Collection, or * Item * * @param dso - the dspace object to export @@ -93,7 +93,7 @@ public void createDownloadableExport(List dsObjects, Context context, boolean migrate) throws Exception; /** - * Convenience methot to create export a single Community, Collection, or + * Convenience method to create export a single Community, Collection, or * Item * * @param dso - the dspace object to export @@ -156,7 +156,7 @@ public String getExportDownloadDirectory(EPerson ePerson) public String getExportWorkDirectory() throws Exception; /** - * Used to read the export archived. Inteded for download. + * Used to read the export archived. Intended for download. * * @param fileName the name of the file to download * @param eperson the eperson requesting the download @@ -233,7 +233,7 @@ public List getExportsAvailable(EPerson eperson) /** * Since the archive is created in a new thread we are unable to communicate - * with calling method about success or failure. We accomplis this + * with calling method about success or failure. We accomplish this * communication with email instead. Send a success email once the export * archive is complete and ready for download * @@ -248,7 +248,7 @@ public void emailSuccessMessage(Context context, EPerson eperson, /** * Since the archive is created in a new thread we are unable to communicate - * with calling method about success or failure. We accomplis this + * with calling method about success or failure. We accomplish this * communication with email instead. Send an error email if the export * archive fails * diff --git a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportServiceImpl.java index 255f4bdcbb15..01859c4f6b8a 100644 --- a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportServiceImpl.java @@ -46,7 +46,6 @@ import java.util.UUID; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; -import javax.mail.MessagingException; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; @@ -56,6 +55,7 @@ import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; +import jakarta.mail.MessagingException; import org.apache.commons.collections4.ComparatorUtils; import org.apache.commons.io.FileDeleteStrategy; import org.apache.commons.io.FileUtils; @@ -1743,7 +1743,8 @@ protected void processOptions(Context c, Item myItem, List options) } else { logInfo("\tSetting special permissions for " + bitstreamName); - setPermission(c, myGroup, actionID, bs); + String rpType = useWorkflow ? ResourcePolicy.TYPE_SUBMISSION : ResourcePolicy.TYPE_INHERITED; + setPermission(c, myGroup, rpType, actionID, bs); } } @@ -1801,24 +1802,25 @@ protected void processOptions(Context c, Item myItem, List options) * * @param c DSpace Context * @param g Dspace Group + * @param rpType resource policy type string * @param actionID action identifier * @param bs Bitstream * @throws SQLException if database error * @throws AuthorizeException if authorization error * @see org.dspace.core.Constants */ - protected void setPermission(Context c, Group g, int actionID, Bitstream bs) + protected void setPermission(Context c, Group g, String rpType, int actionID, Bitstream bs) throws SQLException, AuthorizeException { if (!isTest) { // remove the default policy authorizeService.removeAllPolicies(c, bs); // add the policy - ResourcePolicy rp = resourcePolicyService.create(c); + ResourcePolicy rp = resourcePolicyService.create(c, null, g); rp.setdSpaceObject(bs); rp.setAction(actionID); - rp.setGroup(g); + rp.setRpType(rpType); resourcePolicyService.update(c, rp); } else { @@ -1957,58 +1959,57 @@ public String unzip(File zipfile, String destDir) throws IOException { try { while (entries.hasMoreElements()) { entry = entries.nextElement(); + String entryName = entry.getName(); + File outFile = new File(zipDir + entryName); + // Verify that this file/directory will be extracted into our zipDir (and not somewhere else!) + if (!outFile.toPath().normalize().startsWith(zipDir)) { + throw new IOException("Bad zip entry: '" + entryName + + "' in file '" + zipfile.getAbsolutePath() + "'!" + + " Cannot process this file or directory."); + } if (entry.isDirectory()) { - if (!new File(zipDir + entry.getName()).mkdirs()) { + if (!outFile.mkdirs()) { logError("Unable to create contents directory: " + zipDir + entry.getName()); } } else { - String entryName = entry.getName(); - File outFile = new File(zipDir + entryName); - // Verify that this file will be extracted into our zipDir (and not somewhere else!) - if (!outFile.toPath().normalize().startsWith(zipDir)) { - throw new IOException("Bad zip entry: '" + entryName - + "' in file '" + zipfile.getAbsolutePath() + "'!" - + " Cannot process this file."); - } else { - logInfo("Extracting file: " + entryName); + logInfo("Extracting file: " + entryName); - int index = entryName.lastIndexOf('/'); - if (index == -1) { - // Was it created on Windows instead? - index = entryName.lastIndexOf('\\'); + int index = entryName.lastIndexOf('/'); + if (index == -1) { + // Was it created on Windows instead? + index = entryName.lastIndexOf('\\'); + } + if (index > 0) { + File dir = new File(zipDir + entryName.substring(0, index)); + if (!dir.exists() && !dir.mkdirs()) { + logError("Unable to create directory: " + dir.getAbsolutePath()); } - if (index > 0) { - File dir = new File(zipDir + entryName.substring(0, index)); - if (!dir.exists() && !dir.mkdirs()) { - logError("Unable to create directory: " + dir.getAbsolutePath()); - } - //Entries could have too many directories, and we need to adjust the sourcedir - // file1.zip (SimpleArchiveFormat / item1 / contents|dublin_core|... - // SimpleArchiveFormat / item2 / contents|dublin_core|... - // or - // file2.zip (item1 / contents|dublin_core|... - // item2 / contents|dublin_core|... - - //regex supports either windows or *nix file paths - String[] entryChunks = entryName.split("/|\\\\"); - if (entryChunks.length > 2) { - if (StringUtils.equals(sourceDirForZip, sourcedir)) { - sourceDirForZip = sourcedir + "/" + entryChunks[0]; - } + //Entries could have too many directories, and we need to adjust the sourcedir + // file1.zip (SimpleArchiveFormat / item1 / contents|dublin_core|... + // SimpleArchiveFormat / item2 / contents|dublin_core|... + // or + // file2.zip (item1 / contents|dublin_core|... + // item2 / contents|dublin_core|... + + //regex supports either windows or *nix file paths + String[] entryChunks = entryName.split("/|\\\\"); + if (entryChunks.length > 2) { + if (StringUtils.equals(sourceDirForZip, sourcedir)) { + sourceDirForZip = sourcedir + "/" + entryChunks[0]; } } - byte[] buffer = new byte[1024]; - int len; - InputStream in = zf.getInputStream(entry); - BufferedOutputStream out = new BufferedOutputStream( - new FileOutputStream(outFile)); - while ((len = in.read(buffer)) >= 0) { - out.write(buffer, 0, len); - } - in.close(); - out.close(); } + byte[] buffer = new byte[1024]; + int len; + InputStream in = zf.getInputStream(entry); + BufferedOutputStream out = new BufferedOutputStream( + new FileOutputStream(outFile)); + while ((len = in.read(buffer)) >= 0) { + out.write(buffer, 0, len); + } + in.close(); + out.close(); } } } finally { @@ -2209,7 +2210,7 @@ public void run() { emailErrorMessage(eperson, exceptionString); throw new Exception(e.getMessage()); } catch (Exception e2) { - // wont throw here + // won't throw here } } finally { // Make sure the database connection gets closed in all conditions. @@ -2233,7 +2234,7 @@ public void emailSuccessMessage(Context context, EPerson eperson, String fileName) throws MessagingException { try { Locale supportedLocale = I18nUtil.getEPersonLocale(eperson); - Email email = Email.getEmail(I18nUtil.getEmailFilename(supportedLocale, "bte_batch_import_success")); + Email email = Email.getEmail(I18nUtil.getEmailFilename(supportedLocale, "batch_import_success")); email.addRecipient(eperson.getEmail()); email.addArgument(fileName); @@ -2249,7 +2250,7 @@ public void emailErrorMessage(EPerson eperson, String error) logError("An error occurred during item import, the user will be notified. " + error); try { Locale supportedLocale = I18nUtil.getEPersonLocale(eperson); - Email email = Email.getEmail(I18nUtil.getEmailFilename(supportedLocale, "bte_batch_import_error")); + Email email = Email.getEmail(I18nUtil.getEmailFilename(supportedLocale, "batch_import_error")); email.addRecipient(eperson.getEmail()); email.addArgument(error); email.addArgument(configurationService.getProperty("dspace.ui.url") + "/feedback"); diff --git a/dspace-api/src/main/java/org/dspace/app/itemimport/service/ItemImportService.java b/dspace-api/src/main/java/org/dspace/app/itemimport/service/ItemImportService.java index e99ece31b9bb..738991a839cd 100644 --- a/dspace-api/src/main/java/org/dspace/app/itemimport/service/ItemImportService.java +++ b/dspace-api/src/main/java/org/dspace/app/itemimport/service/ItemImportService.java @@ -10,8 +10,8 @@ import java.io.File; import java.io.IOException; import java.util.List; -import javax.mail.MessagingException; +import jakarta.mail.MessagingException; import org.dspace.app.itemimport.BatchUpload; import org.dspace.content.Collection; import org.dspace.core.Context; @@ -121,7 +121,7 @@ public void emailSuccessMessage(Context context, EPerson eperson, /** * If a batch import is done in a new thread we are unable to communicate - * with calling method about success or failure. We accomplis this + * with calling method about success or failure. We accomplish this * communication with email instead. Send an error email if the batch * import fails * diff --git a/dspace-api/src/main/java/org/dspace/app/itemupdate/DeleteBitstreamsAction.java b/dspace-api/src/main/java/org/dspace/app/itemupdate/DeleteBitstreamsAction.java index cb5dcfb75dc0..65157f5207e7 100644 --- a/dspace-api/src/main/java/org/dspace/app/itemupdate/DeleteBitstreamsAction.java +++ b/dspace-api/src/main/java/org/dspace/app/itemupdate/DeleteBitstreamsAction.java @@ -70,16 +70,19 @@ public void execute(Context context, ItemArchive itarch, boolean isTest, } } - if (alterProvenance) { + if (alterProvenance && !bundles.isEmpty()) { DtoMetadata dtom = DtoMetadata.create("dc.description.provenance", "en", ""); String append = "Bitstream " + bs.getName() + " deleted on " + DCDate .getCurrent() + "; "; - Item item = bundles.iterator().next().getItems().iterator().next(); - ItemUpdate.pr("Append provenance with: " + append); + List items = bundles.iterator().next().getItems(); + if (!items.isEmpty()) { + Item item = items.iterator().next(); + ItemUpdate.pr("Append provenance with: " + append); - if (!isTest) { - MetadataUtilities.appendMetadata(context, item, dtom, false, append); + if (!isTest) { + MetadataUtilities.appendMetadata(context, item, dtom, false, append); + } } } } diff --git a/dspace-api/src/main/java/org/dspace/app/itemupdate/ItemArchive.java b/dspace-api/src/main/java/org/dspace/app/itemupdate/ItemArchive.java index 26de45caf77e..a3dead0574f9 100644 --- a/dspace-api/src/main/java/org/dspace/app/itemupdate/ItemArchive.java +++ b/dspace-api/src/main/java/org/dspace/app/itemupdate/ItemArchive.java @@ -217,7 +217,7 @@ private Item itemFromHandleInput(Context context) throws SQLException, Exception { DtoMetadata dtom = getMetadataField("dc.identifier.uri"); if (dtom == null) { - throw new Exception("No dc.identier.uri field found for handle"); + throw new Exception("No dc.identifier.uri field found for handle"); } this.addUndoMetadataField(dtom); //seed the undo list with the uri diff --git a/dspace-api/src/main/java/org/dspace/app/itemupdate/ThumbnailBitstreamFilter.java b/dspace-api/src/main/java/org/dspace/app/itemupdate/ThumbnailBitstreamFilter.java index 2a8f9ac20028..b6000778877c 100644 --- a/dspace-api/src/main/java/org/dspace/app/itemupdate/ThumbnailBitstreamFilter.java +++ b/dspace-api/src/main/java/org/dspace/app/itemupdate/ThumbnailBitstreamFilter.java @@ -10,7 +10,7 @@ import java.util.Properties; /** - * Bitstream filter targetting the THUMBNAIL bundle + * Bitstream filter targeting the THUMBNAIL bundle */ public class ThumbnailBitstreamFilter extends BitstreamFilterByBundleName { diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/ItemFilter.java b/dspace-api/src/main/java/org/dspace/app/ldn/ItemFilter.java new file mode 100644 index 000000000000..44dd5389d3de --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/ldn/ItemFilter.java @@ -0,0 +1,30 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.ldn; + +/** + * model class for the item filters configured into item-filters.xml + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + */ +public class ItemFilter { + + private String id; + + public ItemFilter(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } +} diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/LDNMessageConsumer.java b/dspace-api/src/main/java/org/dspace/app/ldn/LDNMessageConsumer.java new file mode 100644 index 000000000000..210aaa6c9c97 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/ldn/LDNMessageConsumer.java @@ -0,0 +1,243 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.ldn; + +import static java.lang.String.format; + +import java.io.IOException; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Locale; +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.dspace.app.ldn.factory.NotifyServiceFactory; +import org.dspace.app.ldn.model.Notification; +import org.dspace.app.ldn.service.LDNMessageService; +import org.dspace.app.ldn.service.NotifyPatternToTriggerService; +import org.dspace.app.ldn.service.NotifyServiceInboundPatternService; +import org.dspace.content.Bitstream; +import org.dspace.content.BitstreamFormat; +import org.dspace.content.Bundle; +import org.dspace.content.Item; +import org.dspace.content.MetadataValue; +import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.content.logic.LogicalStatement; +import org.dspace.content.service.BitstreamService; +import org.dspace.content.service.ItemService; +import org.dspace.core.Constants; +import org.dspace.core.Context; +import org.dspace.core.I18nUtil; +import org.dspace.core.LDN; +import org.dspace.event.Consumer; +import org.dspace.event.Event; +import org.dspace.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; +import org.dspace.utils.DSpace; +import org.dspace.web.ContextUtil; + +/** + * class for creating a new LDN Messages of installed item + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + */ +public class LDNMessageConsumer implements Consumer { + + private NotifyPatternToTriggerService notifyPatternToTriggerService; + private NotifyServiceInboundPatternService inboundPatternService; + private LDNMessageService ldnMessageService; + private ConfigurationService configurationService; + private ItemService itemService; + private BitstreamService bitstreamService; + + @Override + public void initialize() throws Exception { + notifyPatternToTriggerService = NotifyServiceFactory.getInstance().getNotifyPatternToTriggerService(); + ldnMessageService = NotifyServiceFactory.getInstance().getLDNMessageService(); + configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); + itemService = ContentServiceFactory.getInstance().getItemService(); + bitstreamService = ContentServiceFactory.getInstance().getBitstreamService(); + inboundPatternService = NotifyServiceFactory.getInstance().getNotifyServiceInboundPatternService(); + } + + @Override + public void consume(Context context, Event event) throws Exception { + + if (event.getSubjectType() != Constants.ITEM || + event.getEventType() != Event.INSTALL) { + return; + } + + Item item = (Item) event.getSubject(context); + createManualLDNMessages(context, item); + createAutomaticLDNMessages(context, item); + } + + private void createManualLDNMessages(Context context, Item item) throws SQLException, JsonProcessingException { + List patternsToTrigger = + notifyPatternToTriggerService.findByItem(context, item); + + for (NotifyPatternToTrigger patternToTrigger : patternsToTrigger) { + createLDNMessage(context,patternToTrigger.getItem(), + patternToTrigger.getNotifyService(), patternToTrigger.getPattern()); + } + } + + private void createAutomaticLDNMessages(Context context, Item item) throws SQLException, JsonProcessingException { + + List inboundPatterns = inboundPatternService.findAutomaticPatterns(context); + + for (NotifyServiceInboundPattern inboundPattern : inboundPatterns) { + if (StringUtils.isEmpty(inboundPattern.getConstraint()) || + evaluateFilter(context, item, inboundPattern.getConstraint())) { + createLDNMessage(context, item, inboundPattern.getNotifyService(), inboundPattern.getPattern()); + } + } + } + + private boolean evaluateFilter(Context context, Item item, String constraint) { + LogicalStatement filter = + new DSpace().getServiceManager().getServiceByName(constraint, LogicalStatement.class); + + return filter != null && filter.getResult(context, item); + } + + private void createLDNMessage(Context context, Item item, NotifyServiceEntity service, String pattern) + throws SQLException, JsonMappingException, JsonProcessingException { + + LDN ldn = getLDNMessage(pattern); + LDNMessageEntity ldnMessage = + ldnMessageService.create(context, format("urn:uuid:%s", UUID.randomUUID())); + + ldnMessage.setObject(item); + ldnMessage.setTarget(service); + ldnMessage.setQueueStatus(LDNMessageEntity.QUEUE_STATUS_QUEUED); + ldnMessage.setQueueTimeout(new Date()); + + appendGeneratedMessage(ldn, ldnMessage, pattern); + + ObjectMapper mapper = new ObjectMapper(); + Notification notification = mapper.readValue(ldnMessage.getMessage(), Notification.class); + ldnMessage.setType(StringUtils.joinWith(",", notification.getType())); + + ArrayList notificationTypeArrayList = new ArrayList(notification.getType()); + // sorting the list + Collections.sort(notificationTypeArrayList); + ldnMessage.setActivityStreamType(notificationTypeArrayList.get(0)); + ldnMessage.setCoarNotifyType(notificationTypeArrayList.get(1)); + + ldnMessageService.update(context, ldnMessage); + } + + private LDN getLDNMessage(String pattern) { + try { + return LDN.getLDNMessage(I18nUtil.getLDNFilename(Locale.getDefault(), pattern)); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private void appendGeneratedMessage(LDN ldn, LDNMessageEntity ldnMessage, String pattern) { + Item item = (Item) ldnMessage.getObject(); + ldn.addArgument(getUiUrl()); + ldn.addArgument(configurationService.getProperty("ldn.notify.inbox")); + ldn.addArgument(configurationService.getProperty("dspace.name")); + ldn.addArgument(Objects.requireNonNullElse(ldnMessage.getTarget().getUrl(), "")); + ldn.addArgument(Objects.requireNonNullElse(ldnMessage.getTarget().getLdnUrl(), "")); + ldn.addArgument(getUiUrl() + "/handle/" + ldnMessage.getObject().getHandle()); + ldn.addArgument(getIdentifierUri(item)); + ldn.addArgument(generateBitstreamDownloadUrl(item)); + ldn.addArgument(getBitstreamMimeType(findPrimaryBitstream(item))); + ldn.addArgument(ldnMessage.getID()); + ldn.addArgument(getRelationUri(item)); + ldn.addArgument("http://purl.org/vocab/frbr/core#supplement"); + ldn.addArgument(format("urn:uuid:%s", UUID.randomUUID())); + + ldnMessage.setMessage(ldn.generateLDNMessage()); + } + + private String getUiUrl() { + return configurationService.getProperty("dspace.ui.url"); + } + + private String getIdentifierUri(Item item) { + return itemService.getMetadataByMetadataString(item, "dc.identifier.uri") + .stream() + .findFirst() + .map(MetadataValue::getValue) + .orElse(""); + } + + private String getRelationUri(Item item) { + String relationMetadata = configurationService.getProperty("ldn.notify.relation.metadata", "dc.relation"); + return itemService.getMetadataByMetadataString(item, relationMetadata) + .stream() + .findFirst() + .map(MetadataValue::getValue) + .orElse(""); + } + + private String generateBitstreamDownloadUrl(Item item) { + String uiUrl = getUiUrl(); + return findPrimaryBitstream(item) + .map(bs -> uiUrl + "/bitstreams/" + bs.getID() + "/download") + .orElse(""); + } + + private Optional findPrimaryBitstream(Item item) { + List bundles = item.getBundles(Constants.CONTENT_BUNDLE_NAME); + return bundles.stream() + .findFirst() + .map(Bundle::getPrimaryBitstream) + .or(() -> bundles.stream() + .findFirst() + .flatMap(bundle -> CollectionUtils.isNotEmpty(bundle.getBitstreams()) + ? Optional.of(bundle.getBitstreams().get(0)) + : Optional.empty())); + } + + private String getBitstreamMimeType(Optional bitstream) { + return bitstream.map(bs -> { + try { + Context context = ContextUtil.obtainCurrentRequestContext(); + BitstreamFormat bitstreamFormat = bs.getFormat(context); + if (bitstreamFormat.getShortDescription().equals("Unknown")) { + return getUserFormatMimeType(bs); + } + return bitstreamFormat.getMIMEType(); + } catch (SQLException e) { + throw new RuntimeException(e); + } + }).orElse(""); + } + + private String getUserFormatMimeType(Bitstream bitstream) { + return bitstreamService.getMetadataFirstValue(bitstream, + "dc", "format", "mimetype", Item.ANY); + } + + @Override + public void end(Context ctx) throws Exception { + + } + + @Override + public void finish(Context ctx) throws Exception { + + } + +} diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/LDNMessageEntity.java b/dspace-api/src/main/java/org/dspace/app/ldn/LDNMessageEntity.java new file mode 100644 index 000000000000..27257455e0ce --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/ldn/LDNMessageEntity.java @@ -0,0 +1,319 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.ldn; + +import java.lang.reflect.Field; +import java.util.Date; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; +import org.dspace.content.DSpaceObject; +import org.dspace.core.ReloadableEntity; + +/** + * Class representing ldnMessages stored in the DSpace system and, when locally resolvable, + * some information are stored as dedicated attributes. + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + */ +@Entity +@Table(name = "ldn_message") +public class LDNMessageEntity implements ReloadableEntity { + + /** + * LDN messages interact with a fictitious queue. Scheduled tasks manage the queue. + */ + + /* + * Notification Type constants + */ + public static final String TYPE_INCOMING = "Incoming"; + public static final String TYPE_OUTGOING = "Outgoing"; + + /** + * Message must not be processed. + */ + public static final Integer QUEUE_STATUS_UNTRUSTED_IP = 0; + + /** + * Message queued, it has to be elaborated. + */ + public static final Integer QUEUE_STATUS_QUEUED = 1; + + /** + * Message has been taken from the queue and it's elaboration is in progress. + */ + public static final Integer QUEUE_STATUS_PROCESSING = 2; + + /** + * Message has been correctly elaborated. + */ + public static final Integer QUEUE_STATUS_PROCESSED = 3; + + /** + * Message has not been correctly elaborated - despite more than "ldn.processor.max.attempts" retryies + */ + public static final Integer QUEUE_STATUS_FAILED = 4; + + /** + * Message must not be processed + */ + public static final Integer QUEUE_STATUS_UNTRUSTED = 5; + + /** + * Message is not processed since action is not mapped + */ + public static final Integer QUEUE_STATUS_UNMAPPED_ACTION = 6; + + /** + * Message queued for retry, it has to be elaborated. + */ + public static final Integer QUEUE_STATUS_QUEUED_FOR_RETRY = 7; + + @Id + private String id; + + @ManyToOne + @JoinColumn(name = "object", referencedColumnName = "uuid") + private DSpaceObject object; + + @Column(name = "message", columnDefinition = "text") + private String message; + + @Column(name = "type") + private String type; + + @Column(name = "queue_status") + private Integer queueStatus; + + @Column(name = "queue_attempts") + private Integer queueAttempts = 0; + + @Temporal(TemporalType.TIMESTAMP) + @Column(name = "queue_last_start_time") + private Date queueLastStartTime = null; + + @Temporal(TemporalType.TIMESTAMP) + @Column(name = "queue_timeout") + private Date queueTimeout = null; + + @ManyToOne + @JoinColumn(name = "origin", referencedColumnName = "id") + private NotifyServiceEntity origin; + + @ManyToOne + @JoinColumn(name = "target", referencedColumnName = "id") + private NotifyServiceEntity target; + + @ManyToOne + @JoinColumn(name = "inReplyTo", referencedColumnName = "id") + private LDNMessageEntity inReplyTo; + + @ManyToOne + @JoinColumn(name = "context", referencedColumnName = "uuid") + private DSpaceObject context; + + @Column(name = "activity_stream_type") + private String activityStreamType; + + @Column(name = "coar_notify_type") + private String coarNotifyType; + + @Column(name = "source_ip") + private String sourceIp; + + protected LDNMessageEntity() { + + } + + public LDNMessageEntity(String id) { + this.id = id; + } + + @Override + public String getID() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + /** + * + * @return the DSpace item related to this message + */ + public DSpaceObject getObject() { + return object; + } + + public void setObject(DSpaceObject object) { + this.object = object; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getActivityStreamType() { + return activityStreamType; + } + + public void setActivityStreamType(String activityStreamType) { + this.activityStreamType = activityStreamType; + } + + public String getCoarNotifyType() { + return coarNotifyType; + } + + public void setCoarNotifyType(String coarNotifyType) { + this.coarNotifyType = coarNotifyType; + } + + /** + * + * @return The originator of the activity, typically the service responsible for sending the notification + */ + public NotifyServiceEntity getOrigin() { + return origin; + } + + public void setOrigin(NotifyServiceEntity origin) { + this.origin = origin; + } + + /** + * + * @return The intended destination of the activity, typically the service which consumes the notification + */ + public NotifyServiceEntity getTarget() { + return target; + } + + public void setTarget(NotifyServiceEntity target) { + this.target = target; + } + + /** + * + * @return This property is used when the notification is a direct response to a previous notification; + * contains an {@link org.dspace.app.ldn.LDNMessageEntity#inReplyTo id} + */ + public LDNMessageEntity getInReplyTo() { + return inReplyTo; + } + + public void setInReplyTo(LDNMessageEntity inReplyTo) { + this.inReplyTo = inReplyTo; + } + + /** + * + * @return This identifies another resource which is relevant to understanding the notification + */ + public DSpaceObject getContext() { + return context; + } + + public void setContext(DSpaceObject context) { + this.context = context; + } + + public Integer getQueueStatus() { + return queueStatus; + } + + public void setQueueStatus(Integer queueStatus) { + this.queueStatus = queueStatus; + } + + public Integer getQueueAttempts() { + return queueAttempts; + } + + public void setQueueAttempts(Integer queueAttempts) { + this.queueAttempts = queueAttempts; + } + + public Date getQueueLastStartTime() { + return queueLastStartTime; + } + + public void setQueueLastStartTime(Date queueLastStartTime) { + this.queueLastStartTime = queueLastStartTime; + } + + public Date getQueueTimeout() { + return queueTimeout; + } + + public void setQueueTimeout(Date queueTimeout) { + this.queueTimeout = queueTimeout; + } + + public String getSourceIp() { + return sourceIp; + } + + public void setSourceIp(String sourceIp) { + this.sourceIp = sourceIp; + } + + @Override + public String toString() { + return "LDNMessage id:" + this.getID() + " typed:" + this.getType(); + } + + public static String getNotificationType(LDNMessageEntity ldnMessage) { + if (ldnMessage.getInReplyTo() != null || ldnMessage.getOrigin() != null) { + return TYPE_INCOMING; + } + return TYPE_OUTGOING; + } + + public static String getServiceNameForNotifyServ(NotifyServiceEntity serviceEntity) { + if (serviceEntity != null) { + return serviceEntity.getName(); + } + return "self"; + } + + public static String getQueueStatus(LDNMessageEntity ldnMessage) { + Class cl = LDNMessageEntity.class; + try { + for (Field f : cl.getDeclaredFields()) { + String fieldName = f.getName(); + if (fieldName.startsWith("QUEUE_") && (f.get(null) == ldnMessage.getQueueStatus())) { + return fieldName; + } + } + } catch (IllegalArgumentException | IllegalAccessException e) { + throw new RuntimeException(e); + } + return null; + } +} diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/LDNMessageQueueStatus.java b/dspace-api/src/main/java/org/dspace/app/ldn/LDNMessageQueueStatus.java new file mode 100644 index 000000000000..ad3dd36e69c5 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/ldn/LDNMessageQueueStatus.java @@ -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/ + */ +package org.dspace.app.ldn; + +public enum LDNMessageQueueStatus { + + /** + * Resulting processing status of an LDN Message (aka queue management) + */ + QUEUED, PROCESSING, PROCESSED, FAILED; +} diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/LDNMetadataFields.java b/dspace-api/src/main/java/org/dspace/app/ldn/LDNMetadataFields.java new file mode 100644 index 000000000000..67a87c144c83 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/ldn/LDNMetadataFields.java @@ -0,0 +1,38 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.ldn; + +/** + * Constants for LDN metadata fields + * + * @author Francesco Bacchelli (francesco.bacchelli at 4science.com) + */ +public final class LDNMetadataFields { + + // schema and element are the same for each metadata of LDN coar-notify + public static final String SCHEMA = "coar"; + public static final String ELEMENT = "notify"; + + // qualifiers + public static final String INITIALIZE = "initialize"; + public static final String REQUEST_REVIEW = "requestreview"; + public static final String REQUEST_ENDORSEMENT = "requestendorsement"; + public static final String EXAMINATION = "examination"; + public static final String REFUSED = "refused"; + public static final String REVIEW = "review"; + public static final String ENDORSMENT = "endorsement"; + public static final String RELEASE = "release"; + + /** + * + */ + private LDNMetadataFields() { + + } + +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/LDNQueueExtractor.java b/dspace-api/src/main/java/org/dspace/app/ldn/LDNQueueExtractor.java new file mode 100644 index 000000000000..57a7cdfb07bf --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/ldn/LDNQueueExtractor.java @@ -0,0 +1,54 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.ldn; + +import java.sql.SQLException; + +import org.apache.logging.log4j.Logger; +import org.dspace.app.ldn.factory.LDNMessageServiceFactory; +import org.dspace.app.ldn.service.LDNMessageService; +import org.dspace.core.Context; + +/** + * LDN Message manager: scheduled task invoking extractAndProcessMessageFromQueue() of {@link LDNMessageService} + * + * @author Francesco Bacchelli (francesco.bacchelli at 4science dot it) + */ +public class LDNQueueExtractor { + + private static final LDNMessageService ldnMessageService = LDNMessageServiceFactory.getInstance() + .getLDNMessageService(); + private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(LDNQueueExtractor.class); + + /** + * Default constructor + */ + private LDNQueueExtractor() { + } + + /** + * invokes + * @see org.dspace.app.ldn.service.impl.LDNMessageServiceImpl#extractAndProcessMessageFromQueue(Context) + * to process the oldest ldn messages from the queue. An LdnMessage is processed when is routed to a + * @see org.dspace.app.ldn.processor.LDNProcessor + * Also a +1 is added to the ldnMessage entity + * @see org.dspace.app.ldn.LDNMessageEntity#getQueueAttempts() + * @return the number of processed ldnMessages. + * @throws SQLException + */ + public static int extractMessageFromQueue() throws SQLException { + Context context = new Context(Context.Mode.READ_WRITE); + int processed_messages = ldnMessageService.extractAndProcessMessageFromQueue(context); + if (processed_messages > 0) { + log.info("Processed Messages x" + processed_messages); + } + context.complete(); + return processed_messages; + } + +}; \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/LDNQueueTimeoutChecker.java b/dspace-api/src/main/java/org/dspace/app/ldn/LDNQueueTimeoutChecker.java new file mode 100644 index 000000000000..36a927d672bc --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/ldn/LDNQueueTimeoutChecker.java @@ -0,0 +1,53 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.ldn; + +import java.sql.SQLException; + +import org.apache.logging.log4j.Logger; +import org.dspace.app.ldn.factory.LDNMessageServiceFactory; +import org.dspace.app.ldn.service.LDNMessageService; +import org.dspace.core.Context; + +/** + * LDN Message manager: scheduled task invoking checkQueueMessageTimeout() of {@link LDNMessageService} + * + * @author Francesco Bacchelli (francesco.bacchelli at 4science dot it) + */ +public class LDNQueueTimeoutChecker { + + private static final LDNMessageService ldnMessageService = LDNMessageServiceFactory.getInstance() + .getLDNMessageService(); + private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(LDNQueueTimeoutChecker.class); + + /** + * Default constructor + */ + private LDNQueueTimeoutChecker() { + } + + /** + * invokes + * @see org.dspace.app.ldn.service.impl.LDNMessageServiceImpl#checkQueueMessageTimeout(Context) + * to refresh the queue status of timed-out and in progressing status ldn messages: + * according to their attempts put them back in queue or set their status as failed if maxAttempts + * reached. + * @return the number of managed ldnMessages. + * @throws SQLException + */ + public static int checkQueueMessageTimeout() throws SQLException { + Context context = new Context(Context.Mode.READ_WRITE); + int fixed_messages = 0; + fixed_messages = ldnMessageService.checkQueueMessageTimeout(context); + if (fixed_messages > 0) { + log.info("Managed Messages x" + fixed_messages); + } + context.complete(); + return fixed_messages; + } +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/LDNRouter.java b/dspace-api/src/main/java/org/dspace/app/ldn/LDNRouter.java new file mode 100644 index 000000000000..14957aa503a3 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/ldn/LDNRouter.java @@ -0,0 +1,91 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.ldn; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.Logger; +import org.dspace.app.ldn.processor.LDNProcessor; + +/** + * Linked Data Notification router. + */ +public class LDNRouter { + + private Map, LDNProcessor> incomingProcessors = new HashMap<>(); + private Map, LDNProcessor> outcomingProcessors = new HashMap<>(); + private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(LDNRouter.class); + + /** + * Route notification to processor + * + * @return LDNProcessor processor to process notification, can be null + */ + public LDNProcessor route(LDNMessageEntity ldnMessage) { + if (ldnMessage == null) { + log.warn("A null LDNMessage was received and could not be routed."); + return null; + } + if (StringUtils.isEmpty(ldnMessage.getType())) { + log.warn("LDNMessage " + ldnMessage + " was received. It has no type, so it couldn't be routed."); + return null; + } + Set ldnMessageTypeSet = new HashSet(); + ldnMessageTypeSet.add(ldnMessage.getActivityStreamType()); + ldnMessageTypeSet.add(ldnMessage.getCoarNotifyType()); + + LDNProcessor processor = null; + if (ldnMessage.getTarget() == null) { + processor = incomingProcessors.get(ldnMessageTypeSet); + } else if (ldnMessage.getOrigin() == null) { + processor = outcomingProcessors.get(ldnMessageTypeSet); + } + + return processor; + } + + /** + * Get all incoming routes. + * + * @return Map, LDNProcessor> + */ + public Map, LDNProcessor> getIncomingProcessors() { + return incomingProcessors; + } + + /** + * Set all incoming routes. + * + * @param incomingProcessors + */ + public void setIncomingProcessors(Map, LDNProcessor> incomingProcessors) { + this.incomingProcessors = incomingProcessors; + } + + /** + * Get all outcoming routes. + * + * @return Map, LDNProcessor> + */ + public Map, LDNProcessor> getOutcomingProcessors() { + return outcomingProcessors; + } + + /** + * Set all outcoming routes. + * + * @param outcomingProcessors + */ + public void setOutcomingProcessors(Map, LDNProcessor> outcomingProcessors) { + this.outcomingProcessors = outcomingProcessors; + } +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/NotifyPatternToTrigger.java b/dspace-api/src/main/java/org/dspace/app/ldn/NotifyPatternToTrigger.java new file mode 100644 index 000000000000..da23471a1c66 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/ldn/NotifyPatternToTrigger.java @@ -0,0 +1,82 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.ldn; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.SequenceGenerator; +import jakarta.persistence.Table; +import org.dspace.content.Item; +import org.dspace.core.ReloadableEntity; + +/** + * Database object representing notify patterns to be triggered + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + */ +@Entity +@Table(name = "notifypatterns_to_trigger") +public class NotifyPatternToTrigger implements ReloadableEntity { + + @Id + @Column(name = "id") + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "notifypatterns_to_trigger_id_seq") + @SequenceGenerator(name = "notifypatterns_to_trigger_id_seq", + sequenceName = "notifypatterns_to_trigger_id_seq", + allocationSize = 1) + private Integer id; + + @ManyToOne + @JoinColumn(name = "item_id", referencedColumnName = "uuid") + private Item item; + + @ManyToOne + @JoinColumn(name = "service_id", referencedColumnName = "id") + private NotifyServiceEntity notifyService; + + @Column(name = "pattern") + private String pattern; + + public void setId(Integer id) { + this.id = id; + } + + public Item getItem() { + return item; + } + + public void setItem(Item item) { + this.item = item; + } + + public NotifyServiceEntity getNotifyService() { + return notifyService; + } + + public void setNotifyService(NotifyServiceEntity notifyService) { + this.notifyService = notifyService; + } + + public String getPattern() { + return pattern; + } + + public void setPattern(String pattern) { + this.pattern = pattern; + } + + @Override + public Integer getID() { + return id; + } +} diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/NotifyServiceEntity.java b/dspace-api/src/main/java/org/dspace/app/ldn/NotifyServiceEntity.java new file mode 100644 index 000000000000..c939256b52ba --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/ldn/NotifyServiceEntity.java @@ -0,0 +1,156 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.ldn; + +import java.math.BigDecimal; +import java.util.List; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.OneToMany; +import jakarta.persistence.SequenceGenerator; +import jakarta.persistence.Table; +import org.dspace.core.ReloadableEntity; + +/** + * Database object representing notify services + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + */ +@Entity +@Table(name = "notifyservice") +public class NotifyServiceEntity implements ReloadableEntity { + + @Id + @Column(name = "id") + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "notifyservice_id_seq") + @SequenceGenerator(name = "notifyservice_id_seq", sequenceName = "notifyservice_id_seq", + allocationSize = 1) + private Integer id; + + @Column(name = "name", nullable = false) + private String name; + + @Column(name = "description", columnDefinition = "text") + private String description; + + @Column(name = "url") + private String url; + + @Column(name = "ldn_url") + private String ldnUrl; + + @OneToMany(mappedBy = "notifyService") + private List inboundPatterns; + + @Column(name = "enabled") + private boolean enabled = false; + + @Column(name = "score") + private BigDecimal score; + + @Column(name = "lower_ip") + private String lowerIp; + + @Column(name = "upper_ip") + private String upperIp; + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + /** + * @return URL of an informative website + */ + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + /** + * @return URL of the LDN InBox + */ + public String getLdnUrl() { + return ldnUrl; + } + + public void setLdnUrl(String ldnUrl) { + this.ldnUrl = ldnUrl; + } + + /** + * @return The list of the inbound patterns configuration supported by the service + */ + public List getInboundPatterns() { + return inboundPatterns; + } + + public void setInboundPatterns(List inboundPatterns) { + this.inboundPatterns = inboundPatterns; + } + + @Override + public Integer getID() { + return id; + } + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public BigDecimal getScore() { + return score; + } + + public void setScore(BigDecimal score) { + this.score = score; + } + + public String getLowerIp() { + return lowerIp; + } + + public void setLowerIp(String lowerIp) { + this.lowerIp = lowerIp; + } + + public String getUpperIp() { + return upperIp; + } + + public void setUpperIp(String upperIp) { + this.upperIp = upperIp; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/NotifyServiceInboundPattern.java b/dspace-api/src/main/java/org/dspace/app/ldn/NotifyServiceInboundPattern.java new file mode 100644 index 000000000000..329d6cb11cec --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/ldn/NotifyServiceInboundPattern.java @@ -0,0 +1,103 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.ldn; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.SequenceGenerator; +import jakarta.persistence.Table; +import org.dspace.core.ReloadableEntity; + +/** + * Database object representing notify service inbound patterns. Every {@link org.dspace.app.ldn.NotifyServiceEntity} + * may have inbounds and outbounds. Inbounds are to be sent to the external service. + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + */ +@Entity +@Table(name = "notifyservice_inbound_pattern") +public class NotifyServiceInboundPattern implements ReloadableEntity { + + @Id + @Column(name = "id") + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "notifyservice_inbound_pattern_id_seq") + @SequenceGenerator(name = "notifyservice_inbound_pattern_id_seq", + sequenceName = "notifyservice_inbound_pattern_id_seq", + allocationSize = 1) + private Integer id; + + @ManyToOne + @JoinColumn(name = "service_id", referencedColumnName = "id") + private NotifyServiceEntity notifyService; + + @Column(name = "pattern") + private String pattern; + + @Column(name = "constraint_name") + private String constraint; + + @Column(name = "automatic") + private boolean automatic; + + @Override + public Integer getID() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public NotifyServiceEntity getNotifyService() { + return notifyService; + } + + public void setNotifyService(NotifyServiceEntity notifyService) { + this.notifyService = notifyService; + } + + /** + * @see coar documentation + * @return pattern of the inbound notification + */ + public String getPattern() { + return pattern; + } + + public void setPattern(String pattern) { + this.pattern = pattern; + } + + /** + * @return the condition checked for automatic evaluation + */ + public String getConstraint() { + return constraint; + } + + public void setConstraint(String constraint) { + this.constraint = constraint; + } + + /** + * when true - the notification is automatically when constraints are respected. + * @return the automatic flag + */ + public boolean isAutomatic() { + return automatic; + } + + public void setAutomatic(boolean automatic) { + this.automatic = automatic; + } +} diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/action/LDNAction.java b/dspace-api/src/main/java/org/dspace/app/ldn/action/LDNAction.java new file mode 100644 index 000000000000..b0c895de9958 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/ldn/action/LDNAction.java @@ -0,0 +1,31 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.ldn.action; + +import org.dspace.app.ldn.model.Notification; +import org.dspace.content.Item; +import org.dspace.core.Context; + +/** + * An action that is run after a notification has been processed. + */ +public interface LDNAction { + + /** + * Execute action for provided notification and item corresponding to the + * notification context. + * + *@param context the context + * @param notification the processed notification to perform action against + * @param item the item corresponding to the notification context + * @return ActionStatus the resulting status of the action + * @throws Exception general exception that can be thrown while executing action + */ + public LDNActionStatus execute(Context context, Notification notification, Item item) throws Exception; + +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/action/LDNActionStatus.java b/dspace-api/src/main/java/org/dspace/app/ldn/action/LDNActionStatus.java new file mode 100644 index 000000000000..86f56ed9baab --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/ldn/action/LDNActionStatus.java @@ -0,0 +1,15 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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; + +/** + * Resulting status of an execution of an action. + */ +public enum LDNActionStatus { + CONTINUE, ABORT; +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/action/LDNCorrectionAction.java b/dspace-api/src/main/java/org/dspace/app/ldn/action/LDNCorrectionAction.java new file mode 100644 index 000000000000..5ce3804bcee8 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/ldn/action/LDNCorrectionAction.java @@ -0,0 +1,108 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.ldn.action; + +import java.math.BigDecimal; +import java.sql.SQLException; +import java.util.Date; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.dspace.app.ldn.NotifyServiceEntity; +import org.dspace.app.ldn.model.Notification; +import org.dspace.app.ldn.service.LDNMessageService; +import org.dspace.content.Item; +import org.dspace.content.QAEvent; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.dspace.handle.service.HandleService; +import org.dspace.qaevent.service.QAEventService; +import org.dspace.qaevent.service.dto.NotifyMessageDTO; +import org.dspace.services.ConfigurationService; +import org.springframework.beans.factory.annotation.Autowired; + + +/** + * Implementation for LDN Correction Action. It creates a QA Event according to the LDN Message received * + * @author Francesco Bacchelli (francesco.bacchelli at 4science.it) + * + */ +public class LDNCorrectionAction implements LDNAction { + + private static final Logger log = LogManager.getLogger(LDNEmailAction.class); + + private String qaEventTopic; + + @Autowired + private ConfigurationService configurationService; + @Autowired + protected ItemService itemService; + @Autowired + private QAEventService qaEventService; + @Autowired + private LDNMessageService ldnMessageService; + @Autowired + private HandleService handleService; + + @Override + public LDNActionStatus execute(Context context, Notification notification, Item item) throws Exception { + LDNActionStatus result = LDNActionStatus.ABORT; + String itemName = itemService.getName(item); + QAEvent qaEvent = null; + if (notification.getObject() != null) { + String citeAs = notification.getObject().getIetfCiteAs(); + if (citeAs == null || citeAs.isEmpty()) { + citeAs = notification.getObject().getId(); + } + NotifyMessageDTO message = new NotifyMessageDTO(); + message.setHref(citeAs); + message.setRelationship(notification.getObject().getAsRelationship()); + if (notification.getOrigin() != null) { + message.setServiceId(notification.getOrigin().getId()); + message.setServiceName(notification.getOrigin().getInbox()); + } + BigDecimal score = getScore(context, notification); + double doubleScoreValue = score != null ? score.doubleValue() : 0d; + ObjectMapper mapper = new ObjectMapper(); + qaEvent = new QAEvent(QAEvent.COAR_NOTIFY_SOURCE, + handleService.findHandle(context, item), item.getID().toString(), itemName, + this.getQaEventTopic(), doubleScoreValue, + mapper.writeValueAsString(message), + new Date()); + qaEventService.store(context, qaEvent); + result = LDNActionStatus.CONTINUE; + } + + return result; + } + + private BigDecimal getScore(Context context, Notification notification) throws SQLException { + + if (notification.getOrigin() == null) { + return BigDecimal.ZERO; + } + + NotifyServiceEntity service = ldnMessageService.findNotifyService(context, notification.getOrigin()); + + if (service == null) { + return BigDecimal.ZERO; + } + + return service.getScore(); + } + + public String getQaEventTopic() { + return qaEventTopic; + } + + public void setQaEventTopic(String qaEventTopic) { + this.qaEventTopic = qaEventTopic; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/action/LDNEmailAction.java b/dspace-api/src/main/java/org/dspace/app/ldn/action/LDNEmailAction.java new file mode 100644 index 000000000000..32b115bd07f6 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/ldn/action/LDNEmailAction.java @@ -0,0 +1,155 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.ldn.action; + +import static java.lang.String.format; + +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Calendar; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.dspace.app.ldn.model.Notification; +import org.dspace.content.Item; +import org.dspace.core.Context; +import org.dspace.core.Email; +import org.dspace.core.I18nUtil; +import org.dspace.services.ConfigurationService; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Action to send email to recipients provided in actionSendFilter. The email + * body will be result of templating actionSendFilter. + */ +public class LDNEmailAction implements LDNAction { + + private static final Logger log = LogManager.getLogger(LDNEmailAction.class); + + private final static String DATE_PATTERN = "dd-MM-yyyy HH:mm:ss"; + + @Autowired + private ConfigurationService configurationService; + + /* + * Supported for actionSendFilter are: + * - + * - GROUP: + * - SUBMITTER + */ + private String actionSendFilter; + + // The file name for the requested email + private String actionSendEmailTextFile; + + /** + * Execute sending an email. + * + * Template context parameters: + * + * {0} Service Name + * {1} Item Name + * {2} Service URL + * {3} Item URL + * {4} Submitter's Name + * {5} Date of the received LDN notification + * {6} LDN notification + * {7} Item + * + * @param notification + * @param item + * @return ActionStatus + * @throws Exception + */ + @Override + public LDNActionStatus execute(Context context, Notification notification, Item item) throws Exception { + try { + Locale supportedLocale = I18nUtil.getEPersonLocale(context.getCurrentUser()); + Email email = Email.getEmail(I18nUtil.getEmailFilename(supportedLocale, actionSendEmailTextFile)); + + // Setting recipients email + for (String recipient : retrieveRecipientsEmail(item)) { + email.addRecipient(recipient); + } + + String date = new SimpleDateFormat(DATE_PATTERN).format(Calendar.getInstance().getTime()); + + email.addArgument(notification.getActor().getName()); + email.addArgument(item.getName()); + email.addArgument(notification.getActor().getId()); + email.addArgument(notification.getContext() != null ? + notification.getContext().getId() : notification.getObject().getId()); + email.addArgument(item.getSubmitter().getFullName()); + email.addArgument(date); + email.addArgument(notification); + email.addArgument(item); + + email.send(); + } catch (Exception e) { + log.error("An Error Occurred while sending a notification email", e); + } + + return LDNActionStatus.CONTINUE; + } + + /** + * @return String + */ + public String getActionSendFilter() { + return actionSendFilter; + } + + /** + * @param actionSendFilter + */ + public void setActionSendFilter(String actionSendFilter) { + this.actionSendFilter = actionSendFilter; + } + + /** + * @return String + */ + public String getActionSendEmailTextFile() { + return actionSendEmailTextFile; + } + + /** + * @param actionSendEmailTextFile + */ + public void setActionSendEmailTextFile(String actionSendEmailTextFile) { + this.actionSendEmailTextFile = actionSendEmailTextFile; + } + + /** + * Parses actionSendFilter for reserved tokens and returns list of email + * recipients. + * + * @param item the item which to get submitter email + * @return List list of email recipients + */ + private List retrieveRecipientsEmail(Item item) { + List recipients = new LinkedList(); + + if (actionSendFilter.startsWith("SUBMITTER")) { + recipients.add(item.getSubmitter().getEmail()); + } else if (actionSendFilter.startsWith("GROUP:")) { + String groupName = actionSendFilter.replace("GROUP:", ""); + String property = format("email.%s.list", groupName); + String[] groupEmails = configurationService.getArrayProperty(property); + recipients = Arrays.asList(groupEmails); + } else { + recipients.add(actionSendFilter); + } + + return recipients; + } + +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/action/LDNRelationCorrectionAction.java b/dspace-api/src/main/java/org/dspace/app/ldn/action/LDNRelationCorrectionAction.java new file mode 100644 index 000000000000..f11a42ab2f90 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/ldn/action/LDNRelationCorrectionAction.java @@ -0,0 +1,110 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.ldn.action; + +import java.math.BigDecimal; +import java.sql.SQLException; +import java.util.Date; +import java.util.Set; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.dspace.app.ldn.NotifyServiceEntity; +import org.dspace.app.ldn.model.Notification; +import org.dspace.app.ldn.service.LDNMessageService; +import org.dspace.content.Item; +import org.dspace.content.QAEvent; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.dspace.handle.service.HandleService; +import org.dspace.qaevent.service.QAEventService; +import org.dspace.qaevent.service.dto.NotifyMessageDTO; +import org.dspace.services.ConfigurationService; +import org.springframework.beans.factory.annotation.Autowired; + + +/** + * Implementation for LDN Correction Action. It creates a QA Event according to the LDN Message received * + * @author Francesco Bacchelli (francesco.bacchelli at 4science.it) + * + */ +public class LDNRelationCorrectionAction implements LDNAction { + + private static final Logger log = LogManager.getLogger(LDNEmailAction.class); + + private String qaEventTopic; + + @Autowired + private ConfigurationService configurationService; + @Autowired + protected ItemService itemService; + @Autowired + private QAEventService qaEventService; + @Autowired + private LDNMessageService ldnMessageService; + @Autowired + private HandleService handleService; + + @Override + public LDNActionStatus execute(Context context, Notification notification, Item item) throws Exception { + LDNActionStatus result = LDNActionStatus.ABORT; + String itemName = itemService.getName(item); + QAEvent qaEvent = null; + if (notification.getObject() != null) { + NotifyMessageDTO message = new NotifyMessageDTO(); + if (notification.getType().containsAll(Set.of("Announce", + "coar-notify:RelationshipAction"))) { + message.setHref(notification.getObject().getAsSubject()); + } else { + message.setHref(notification.getObject().getAsObject()); + } + message.setRelationship(notification.getObject().getAsRelationship()); + if (notification.getOrigin() != null) { + message.setServiceId(notification.getOrigin().getId()); + message.setServiceName(notification.getOrigin().getInbox()); + } + BigDecimal score = getScore(context, notification); + double doubleScoreValue = score != null ? score.doubleValue() : 0d; + ObjectMapper mapper = new ObjectMapper(); + qaEvent = new QAEvent(QAEvent.COAR_NOTIFY_SOURCE, + handleService.findHandle(context, item), item.getID().toString(), itemName, + this.getQaEventTopic(), doubleScoreValue, + mapper.writeValueAsString(message), + new Date()); + qaEventService.store(context, qaEvent); + result = LDNActionStatus.CONTINUE; + } + + return result; + } + + private BigDecimal getScore(Context context, Notification notification) throws SQLException { + + if (notification.getOrigin() == null) { + return BigDecimal.ZERO; + } + + NotifyServiceEntity service = ldnMessageService.findNotifyService(context, notification.getOrigin()); + + if (service == null) { + return BigDecimal.ZERO; + } + + return service.getScore(); + } + + public String getQaEventTopic() { + return qaEventTopic; + } + + public void setQaEventTopic(String qaEventTopic) { + this.qaEventTopic = qaEventTopic; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/action/SendLDNMessageAction.java b/dspace-api/src/main/java/org/dspace/app/ldn/action/SendLDNMessageAction.java new file mode 100644 index 000000000000..c0ecf04304b8 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/ldn/action/SendLDNMessageAction.java @@ -0,0 +1,118 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.ldn.action; + +import java.net.URI; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.http.Header; +import org.apache.http.HttpException; +import org.apache.http.HttpHeaders; +import org.apache.http.HttpStatus; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.dspace.app.ldn.model.Notification; +import org.dspace.content.Item; +import org.dspace.core.Context; + +/** + * Action to send LDN Message + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + */ +public class SendLDNMessageAction implements LDNAction { + + private static final Logger log = LogManager.getLogger(SendLDNMessageAction.class); + + private CloseableHttpClient client = null; + + public SendLDNMessageAction() { + HttpClientBuilder builder = HttpClientBuilder.create(); + client = builder + .disableAutomaticRetries() + .setMaxConnTotal(5) + .build(); + } + + public SendLDNMessageAction(CloseableHttpClient client) { + this(); + if (client != null) { + this.client = client; + } + } + + @Override + public LDNActionStatus execute(Context context, Notification notification, Item item) throws Exception { + //TODO authorization with Bearer token should be supported. + + String url = notification.getTarget().getInbox(); + + HttpPost httpPost = new HttpPost(url); + httpPost.addHeader("Content-Type", "application/ld+json"); + ObjectMapper mapper = new ObjectMapper(); + httpPost.setEntity(new StringEntity(mapper.writeValueAsString(notification), "UTF-8")); + + LDNActionStatus result = LDNActionStatus.ABORT; + // NOTE: Github believes there is a "Potential server-side request forgery due to a user-provided value" + // This is a false positive because the LDN Service URL is configured by the user from DSpace. + // See the frontend configuration at [dspace.ui.url]/admin/ldn/services + try ( + CloseableHttpResponse response = client.execute(httpPost); + ) { + if (isSuccessful(response.getStatusLine().getStatusCode())) { + result = LDNActionStatus.CONTINUE; + } else if (isRedirect(response.getStatusLine().getStatusCode())) { + result = handleRedirect(response, httpPost); + } + } catch (Exception e) { + log.error(e); + } + return result; + } + + private boolean isSuccessful(int statusCode) { + return statusCode == HttpStatus.SC_ACCEPTED || + statusCode == HttpStatus.SC_CREATED; + } + + private boolean isRedirect(int statusCode) { + //org.apache.http.HttpStatus has no enum value for 308! + return statusCode == (HttpStatus.SC_TEMPORARY_REDIRECT + 1) || + statusCode == HttpStatus.SC_TEMPORARY_REDIRECT; + } + + private LDNActionStatus handleRedirect(CloseableHttpResponse oldresponse, + HttpPost request) throws HttpException { + Header[] urls = oldresponse.getHeaders(HttpHeaders.LOCATION); + String url = urls.length > 0 && urls[0] != null ? urls[0].getValue() : null; + if (url == null) { + throw new HttpException("Error following redirect, unable to reach" + + " the correct url."); + } + LDNActionStatus result = LDNActionStatus.ABORT; + try { + request.setURI(new URI(url)); + try ( + CloseableHttpResponse response = client.execute(request); + ) { + if (isSuccessful(response.getStatusLine().getStatusCode())) { + return LDNActionStatus.CONTINUE; + } + } + } catch (Exception e) { + log.error("Error following redirect:", e); + } + + return LDNActionStatus.ABORT; + } +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/dao/LDNMessageDao.java b/dspace-api/src/main/java/org/dspace/app/ldn/dao/LDNMessageDao.java new file mode 100644 index 000000000000..fcbb485acacc --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/ldn/dao/LDNMessageDao.java @@ -0,0 +1,76 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.ldn.dao; + +import java.sql.SQLException; +import java.util.List; + +import org.dspace.app.ldn.LDNMessageEntity; +import org.dspace.content.Item; +import org.dspace.core.Context; +import org.dspace.core.GenericDAO; + +/** + * Database Access Object interface class for the LDNMessage object. + * + * The implementation of this class is responsible for all database calls for + * the LDNMessage object and is autowired by spring + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + */ +public interface LDNMessageDao extends GenericDAO { + + /** + * load the oldest ldn messages considering their {@link org.dspace.app.ldn.LDNMessageEntity#queueLastStartTime} + * @param context + * @param max_attempts consider ldn_message entity with queue_attempts <= max_attempts + * @return ldn message entities to be routed + * @throws SQLException + */ + public List findOldestMessageToProcess(Context context, int max_attempts) throws SQLException; + + /** + * find ldn message entties in processing status and already timed out. + * @param context + * @param max_attempts consider ldn_message entity with queue_attempts <= max_attempts + * @return ldn message entities + * @throws SQLException + */ + public List findProcessingTimedoutMessages(Context context, int max_attempts) throws SQLException; + + /** + * find all ldn messages related to an item + * @param context + * @param item item related to the returned ldn messages + * @param activities involves only this specific group of activities + * @return all ldn messages related to the given item + * @throws SQLException + */ + public List findAllMessagesByItem( + Context context, Item item, String... activities) throws SQLException; + + /** + * find all ldn messages related to an item and to a specific ldn message + * @param context + * @param msg the referring ldn message + * @param item the referring repository item + * @param relatedTypes filter for @see org.dspace.app.ldn.LDNMessageEntity#activityStreamType + * @return all related ldn messages + * @throws SQLException + */ + public List findAllRelatedMessagesByItem( + Context context, LDNMessageEntity msg, Item item, String... relatedTypes) throws SQLException; + + /** + * + * @param context + * @return the list of messages in need to be reprocessed - with queue_status as QUEUE_STATUS_QUEUED_FOR_RETRY + * @throws SQLException + */ + public List findMessagesToBeReprocessed(Context context) throws SQLException; +} diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/dao/NotifyPatternToTriggerDao.java b/dspace-api/src/main/java/org/dspace/app/ldn/dao/NotifyPatternToTriggerDao.java new file mode 100644 index 000000000000..9ecd1b728a4e --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/ldn/dao/NotifyPatternToTriggerDao.java @@ -0,0 +1,49 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.ldn.dao; + +import java.sql.SQLException; +import java.util.List; + +import org.dspace.app.ldn.NotifyPatternToTrigger; +import org.dspace.content.Item; +import org.dspace.core.Context; +import org.dspace.core.GenericDAO; + +/** + * This is the Data Access Object for the {@link NotifyPatternToTrigger} object + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + */ +public interface NotifyPatternToTriggerDao extends GenericDAO { + + /** + * find the NotifyPatternToTrigger matched with the provided item + * + * @param context the context + * @param item the item + * @return the NotifyPatternToTrigger matched the provided item + * @throws SQLException if database error + */ + public List findByItem(Context context, Item item) throws SQLException; + + /** + * find the NotifyPatternToTrigger matched with the provided + * item and pattern + * + * @param context the context + * @param item the item + * @param pattern the pattern + * @return the NotifyPatternToTrigger matched the provided + * item and pattern + * @throws SQLException if database error + */ + public List findByItemAndPattern(Context context, Item item, String pattern) + throws SQLException; + +} diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/dao/NotifyServiceDao.java b/dspace-api/src/main/java/org/dspace/app/ldn/dao/NotifyServiceDao.java new file mode 100644 index 000000000000..9751b3038290 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/ldn/dao/NotifyServiceDao.java @@ -0,0 +1,45 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.ldn.dao; + +import java.sql.SQLException; +import java.util.List; + +import org.dspace.app.ldn.NotifyServiceEntity; +import org.dspace.core.Context; +import org.dspace.core.GenericDAO; + +/** + * This is the Data Access Object for the {@link NotifyServiceEntity} object + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + */ +public interface NotifyServiceDao extends GenericDAO { + /** + * find the NotifyServiceEntity matched with the provided ldnUrl + * + * @param context the context + * @param ldnUrl the ldnUrl + * @return the NotifyServiceEntity matched the provided ldnUrl + * @throws SQLException if database error + */ + public NotifyServiceEntity findByLdnUrl(Context context, String ldnUrl) throws SQLException; + + /** + * find all NotifyServiceEntity matched the provided inbound pattern + * from the related notifyServiceInboundPatterns + * also with 'automatic' equals to false + * + * @param context the context + * @param pattern the ldnUrl + * @return all NotifyServiceEntity matched the provided pattern + * @throws SQLException if database error + */ + public List findManualServicesByInboundPattern(Context context, String pattern) + throws SQLException; +} diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/dao/NotifyServiceInboundPatternDao.java b/dspace-api/src/main/java/org/dspace/app/ldn/dao/NotifyServiceInboundPatternDao.java new file mode 100644 index 000000000000..194d30e79598 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/ldn/dao/NotifyServiceInboundPatternDao.java @@ -0,0 +1,47 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.ldn.dao; + +import java.sql.SQLException; +import java.util.List; + +import org.dspace.app.ldn.NotifyServiceEntity; +import org.dspace.app.ldn.NotifyServiceInboundPattern; +import org.dspace.core.Context; +import org.dspace.core.GenericDAO; + +/** + * This is the Data Access Object for the {@link NotifyServiceInboundPattern} object + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + */ +public interface NotifyServiceInboundPatternDao extends GenericDAO { + + /** + * find all notifyServiceInboundPatterns matched with + * the provided notifyServiceEntity and pattern + * + * @param context the context + * @param notifyServiceEntity the notifyServiceEntity + * @param pattern the pattern + * @return all notifyServiceInboundPatterns matched with + * the provided notifyServiceEntity and pattern + * @throws SQLException if database error + */ + public NotifyServiceInboundPattern findByServiceAndPattern(Context context, + NotifyServiceEntity notifyServiceEntity, + String pattern) throws SQLException; + /** + * find all automatic notifyServiceInboundPatterns + * + * @param context the context + * @return all automatic notifyServiceInboundPatterns + * @throws SQLException if database error + */ + List findAutomaticPatterns(Context context) throws SQLException; +} diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/dao/impl/LDNMessageDaoImpl.java b/dspace-api/src/main/java/org/dspace/app/ldn/dao/impl/LDNMessageDaoImpl.java new file mode 100644 index 000000000000..d811f6d39f34 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/ldn/dao/impl/LDNMessageDaoImpl.java @@ -0,0 +1,169 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.ldn.dao.impl; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; + +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Order; +import jakarta.persistence.criteria.Predicate; +import jakarta.persistence.criteria.Root; +import org.apache.logging.log4j.Logger; +import org.dspace.app.ldn.LDNMessageEntity; +import org.dspace.app.ldn.LDNMessageEntity_; +import org.dspace.app.ldn.dao.LDNMessageDao; +import org.dspace.content.Item; +import org.dspace.core.AbstractHibernateDAO; +import org.dspace.core.Context; + +/** + * Hibernate implementation of the Database Access Object interface class for + * the LDNMessage object. This class is responsible for all database calls for + * the LDNMessage object and is autowired by spring + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + */ +public class LDNMessageDaoImpl extends AbstractHibernateDAO implements LDNMessageDao { + + private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(LDNMessageDaoImpl.class); + + @Override + public List findOldestMessageToProcess(Context context, int max_attempts) throws SQLException { + CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); + CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, LDNMessageEntity.class); + Root root = criteriaQuery.from(LDNMessageEntity.class); + criteriaQuery.select(root); + List andPredicates = new ArrayList<>(3); + andPredicates + .add(criteriaBuilder.equal(root.get(LDNMessageEntity_.queueStatus), LDNMessageEntity.QUEUE_STATUS_QUEUED)); + andPredicates.add(criteriaBuilder.lessThan(root.get(LDNMessageEntity_.queueAttempts), max_attempts)); + andPredicates.add(criteriaBuilder.lessThan(root.get(LDNMessageEntity_.queueTimeout), new Date())); + criteriaQuery.where(criteriaBuilder.and(andPredicates.toArray(new Predicate[] {}))); + List orderList = new LinkedList<>(); + orderList.add(criteriaBuilder.desc(root.get(LDNMessageEntity_.queueAttempts))); + orderList.add(criteriaBuilder.asc(root.get(LDNMessageEntity_.queueLastStartTime))); + criteriaQuery.orderBy(orderList); + List result = list(context, criteriaQuery, false, LDNMessageEntity.class, -1, -1); + if (result == null || result.isEmpty()) { + log.debug("No LDN messages found to be processed"); + } + return result; + } + + @Override + public List findMessagesToBeReprocessed(Context context) throws SQLException { + // looking for LDN Messages to be reprocessed message + CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); + CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, LDNMessageEntity.class); + Root root = criteriaQuery.from(LDNMessageEntity.class); + criteriaQuery.select(root); + List andPredicates = new ArrayList<>(1); + andPredicates + .add(criteriaBuilder.equal(root.get(LDNMessageEntity_.queueStatus), + LDNMessageEntity.QUEUE_STATUS_QUEUED_FOR_RETRY)); + criteriaQuery.where(criteriaBuilder.and(andPredicates.toArray(new Predicate[] {}))); + List orderList = new LinkedList<>(); + orderList.add(criteriaBuilder.desc(root.get(LDNMessageEntity_.queueAttempts))); + orderList.add(criteriaBuilder.asc(root.get(LDNMessageEntity_.queueLastStartTime))); + criteriaQuery.orderBy(orderList); + List result = list(context, criteriaQuery, false, LDNMessageEntity.class, -1, -1); + if (result == null || result.isEmpty()) { + log.debug("No LDN messages found to be processed"); + } + return result; + } + + @Override + public List findProcessingTimedoutMessages(Context context, int max_attempts) + throws SQLException { + CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); + CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, LDNMessageEntity.class); + Root root = criteriaQuery.from(LDNMessageEntity.class); + criteriaQuery.select(root); + List andPredicates = new ArrayList<>(3); + andPredicates.add( + criteriaBuilder.equal(root.get(LDNMessageEntity_.queueStatus), LDNMessageEntity.QUEUE_STATUS_PROCESSING)); + andPredicates.add(criteriaBuilder.lessThanOrEqualTo(root.get(LDNMessageEntity_.queueAttempts), max_attempts)); + andPredicates.add(criteriaBuilder.lessThan(root.get(LDNMessageEntity_.queueTimeout), new Date())); + criteriaQuery.where(criteriaBuilder.and(andPredicates.toArray(new Predicate[] {}))); + List orderList = new LinkedList<>(); + orderList.add(criteriaBuilder.desc(root.get(LDNMessageEntity_.queueAttempts))); + orderList.add(criteriaBuilder.asc(root.get(LDNMessageEntity_.queueLastStartTime))); + criteriaQuery.orderBy(orderList); + List result = list(context, criteriaQuery, false, LDNMessageEntity.class, -1, -1); + if (result == null || result.isEmpty()) { + log.debug("No LDN messages found to be processed"); + } + return result; + } + + @Override + public List findAllRelatedMessagesByItem( + Context context, LDNMessageEntity msg, Item item, String... relatedTypes) throws SQLException { + CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); + CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, LDNMessageEntity.class); + Root root = criteriaQuery.from(LDNMessageEntity.class); + criteriaQuery.select(root); + List andPredicates = new ArrayList<>(); + Predicate relatedtypePredicate = null; + andPredicates.add( + criteriaBuilder.equal(root.get(LDNMessageEntity_.queueStatus), LDNMessageEntity.QUEUE_STATUS_PROCESSED)); + andPredicates.add( + criteriaBuilder.isNull(root.get(LDNMessageEntity_.target))); + andPredicates.add( + criteriaBuilder.equal(root.get(LDNMessageEntity_.inReplyTo), msg)); + if (relatedTypes != null && relatedTypes.length > 0) { + relatedtypePredicate = root.get(LDNMessageEntity_.activityStreamType).in(relatedTypes); + andPredicates.add(relatedtypePredicate); + } + criteriaQuery.where(criteriaBuilder.and(andPredicates.toArray(new Predicate[] {}))); + List orderList = new LinkedList<>(); + orderList.add(criteriaBuilder.asc(root.get(LDNMessageEntity_.queueLastStartTime))); + orderList.add(criteriaBuilder.desc(root.get(LDNMessageEntity_.queueAttempts))); + criteriaQuery.orderBy(orderList); + List result = list(context, criteriaQuery, false, LDNMessageEntity.class, -1, -1); + if (result == null || result.isEmpty()) { + log.debug("No LDN messages ACK found to be processed"); + } + return result; + } + + @Override + public List findAllMessagesByItem( + Context context, Item item, String... activities) throws SQLException { + CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); + CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, LDNMessageEntity.class); + Root root = criteriaQuery.from(LDNMessageEntity.class); + criteriaQuery.select(root); + List andPredicates = new ArrayList<>(); + Predicate activityPredicate = null; + andPredicates.add( + criteriaBuilder.equal(root.get(LDNMessageEntity_.queueStatus), LDNMessageEntity.QUEUE_STATUS_PROCESSED)); + andPredicates.add( + criteriaBuilder.equal(root.get(LDNMessageEntity_.object), item)); + if (activities != null && activities.length > 0) { + activityPredicate = root.get(LDNMessageEntity_.activityStreamType).in(activities); + andPredicates.add(activityPredicate); + } + criteriaQuery.where(criteriaBuilder.and(andPredicates.toArray(new Predicate[] {}))); + List orderList = new LinkedList<>(); + orderList.add(criteriaBuilder.asc(root.get(LDNMessageEntity_.queueLastStartTime))); + orderList.add(criteriaBuilder.desc(root.get(LDNMessageEntity_.queueAttempts))); + criteriaQuery.orderBy(orderList); + List result = list(context, criteriaQuery, false, LDNMessageEntity.class, -1, -1); + if (result == null || result.isEmpty()) { + log.debug("No LDN messages found"); + } + return result; + } +} diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/dao/impl/NotifyPatternToTriggerDaoImpl.java b/dspace-api/src/main/java/org/dspace/app/ldn/dao/impl/NotifyPatternToTriggerDaoImpl.java new file mode 100644 index 000000000000..53cbeabe005a --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/ldn/dao/impl/NotifyPatternToTriggerDaoImpl.java @@ -0,0 +1,58 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.ldn.dao.impl; + +import java.sql.SQLException; +import java.util.List; + +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Root; +import org.dspace.app.ldn.NotifyPatternToTrigger; +import org.dspace.app.ldn.NotifyPatternToTrigger_; +import org.dspace.app.ldn.dao.NotifyPatternToTriggerDao; +import org.dspace.content.Item; +import org.dspace.core.AbstractHibernateDAO; +import org.dspace.core.Context; + +/** + * Implementation of {@link NotifyPatternToTriggerDao}. + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + */ +public class NotifyPatternToTriggerDaoImpl extends AbstractHibernateDAO + implements NotifyPatternToTriggerDao { + + @Override + public List findByItem(Context context, Item item) + throws SQLException { + CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); + CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, NotifyPatternToTrigger.class); + Root notifyServiceEntityRoot = criteriaQuery.from(NotifyPatternToTrigger.class); + criteriaQuery.select(notifyServiceEntityRoot); + criteriaQuery.where(criteriaBuilder.equal( + notifyServiceEntityRoot.get(NotifyPatternToTrigger_.item), item)); + return list(context, criteriaQuery, false, NotifyPatternToTrigger.class, -1, -1); + } + @Override + public List findByItemAndPattern(Context context, Item item, String pattern) + throws SQLException { + CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); + CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, NotifyPatternToTrigger.class); + Root notifyServiceEntityRoot = criteriaQuery.from(NotifyPatternToTrigger.class); + criteriaQuery.select(notifyServiceEntityRoot); + criteriaQuery.where(criteriaBuilder.and( + criteriaBuilder.equal( + notifyServiceEntityRoot.get(NotifyPatternToTrigger_.item), item), + criteriaBuilder.equal( + notifyServiceEntityRoot.get(NotifyPatternToTrigger_.pattern), pattern) + )); + return list(context, criteriaQuery, false, NotifyPatternToTrigger.class, -1, -1); + } + +} diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/dao/impl/NotifyServiceDaoImpl.java b/dspace-api/src/main/java/org/dspace/app/ldn/dao/impl/NotifyServiceDaoImpl.java new file mode 100644 index 000000000000..bb4cf791da27 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/ldn/dao/impl/NotifyServiceDaoImpl.java @@ -0,0 +1,62 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.ldn.dao.impl; + +import java.sql.SQLException; +import java.util.List; + +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Join; +import jakarta.persistence.criteria.Root; +import org.dspace.app.ldn.NotifyServiceEntity; +import org.dspace.app.ldn.NotifyServiceEntity_; +import org.dspace.app.ldn.NotifyServiceInboundPattern; +import org.dspace.app.ldn.NotifyServiceInboundPattern_; +import org.dspace.app.ldn.dao.NotifyServiceDao; +import org.dspace.core.AbstractHibernateDAO; +import org.dspace.core.Context; + +/** + * Implementation of {@link NotifyServiceDao}. + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + */ +public class NotifyServiceDaoImpl extends AbstractHibernateDAO implements NotifyServiceDao { + + @Override + public NotifyServiceEntity findByLdnUrl(Context context, String ldnUrl) throws SQLException { + CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); + CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, NotifyServiceEntity.class); + Root notifyServiceEntityRoot = criteriaQuery.from(NotifyServiceEntity.class); + criteriaQuery.select(notifyServiceEntityRoot); + criteriaQuery.where(criteriaBuilder.equal( + notifyServiceEntityRoot.get(NotifyServiceEntity_.ldnUrl), ldnUrl)); + return uniqueResult(context, criteriaQuery, false, NotifyServiceEntity.class); + } + + @Override + public List findManualServicesByInboundPattern(Context context, String pattern) + throws SQLException { + CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); + CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, NotifyServiceEntity.class); + Root notifyServiceEntityRoot = criteriaQuery.from(NotifyServiceEntity.class); + + Join notifyServiceInboundPatternJoin = + notifyServiceEntityRoot.join(NotifyServiceEntity_.inboundPatterns); + + criteriaQuery.select(notifyServiceEntityRoot); + criteriaQuery.where(criteriaBuilder.and( + criteriaBuilder.equal( + notifyServiceInboundPatternJoin.get(NotifyServiceInboundPattern_.pattern), pattern), + criteriaBuilder.equal( + notifyServiceInboundPatternJoin.get(NotifyServiceInboundPattern_.automatic), false))); + + return list(context, criteriaQuery, false, NotifyServiceEntity.class, -1, -1); + } +} diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/dao/impl/NotifyServiceInboundPatternDaoImpl.java b/dspace-api/src/main/java/org/dspace/app/ldn/dao/impl/NotifyServiceInboundPatternDaoImpl.java new file mode 100644 index 000000000000..dc3dc1c74491 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/ldn/dao/impl/NotifyServiceInboundPatternDaoImpl.java @@ -0,0 +1,59 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.ldn.dao.impl; + +import java.sql.SQLException; +import java.util.List; + +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Root; +import org.dspace.app.ldn.NotifyServiceEntity; +import org.dspace.app.ldn.NotifyServiceInboundPattern; +import org.dspace.app.ldn.NotifyServiceInboundPattern_; +import org.dspace.app.ldn.dao.NotifyServiceInboundPatternDao; +import org.dspace.core.AbstractHibernateDAO; +import org.dspace.core.Context; + +/** + * Implementation of {@link NotifyServiceInboundPatternDao}. + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + */ +public class NotifyServiceInboundPatternDaoImpl + extends AbstractHibernateDAO implements NotifyServiceInboundPatternDao { + + @Override + public NotifyServiceInboundPattern findByServiceAndPattern(Context context, NotifyServiceEntity notifyServiceEntity, + String pattern) throws SQLException { + CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); + CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, NotifyServiceInboundPattern.class); + Root inboundPatternRoot = criteriaQuery.from(NotifyServiceInboundPattern.class); + criteriaQuery.select(inboundPatternRoot); + criteriaQuery.where(criteriaBuilder.and( + criteriaBuilder.equal( + inboundPatternRoot.get(NotifyServiceInboundPattern_.notifyService), notifyServiceEntity), + criteriaBuilder.equal( + inboundPatternRoot.get(NotifyServiceInboundPattern_.pattern), pattern) + )); + return uniqueResult(context, criteriaQuery, false, NotifyServiceInboundPattern.class); + } + + @Override + public List findAutomaticPatterns(Context context) throws SQLException { + CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); + CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, NotifyServiceInboundPattern.class); + Root inboundPatternRoot = criteriaQuery.from(NotifyServiceInboundPattern.class); + criteriaQuery.select(inboundPatternRoot); + criteriaQuery.where( + criteriaBuilder.equal( + inboundPatternRoot.get(NotifyServiceInboundPattern_.automatic), true) + ); + return list(context, criteriaQuery, false, NotifyServiceInboundPattern.class, -1, -1); + } +} diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/factory/LDNMessageServiceFactory.java b/dspace-api/src/main/java/org/dspace/app/ldn/factory/LDNMessageServiceFactory.java new file mode 100644 index 000000000000..bbf521123bca --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/ldn/factory/LDNMessageServiceFactory.java @@ -0,0 +1,29 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.ldn.factory; + +import org.dspace.app.ldn.service.LDNMessageService; +import org.dspace.services.factory.DSpaceServicesFactory; + +/** + * Abstract factory to get services for the NotifyService package, + * use NotifyServiceFactory.getInstance() to retrieve an implementation + * + * @author Francesco Bacchelli (francesco.bacchelli at 4science.com) + */ +public abstract class LDNMessageServiceFactory { + + public abstract LDNMessageService getLDNMessageService(); + + public static LDNMessageServiceFactory getInstance() { + return DSpaceServicesFactory.getInstance() + .getServiceManager() + .getServiceByName("ldnMessageServiceFactory", + LDNMessageServiceFactory.class); + } +} diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/factory/LDNMessageServiceFactoryImpl.java b/dspace-api/src/main/java/org/dspace/app/ldn/factory/LDNMessageServiceFactoryImpl.java new file mode 100644 index 000000000000..a001ece04069 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/ldn/factory/LDNMessageServiceFactoryImpl.java @@ -0,0 +1,29 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.ldn.factory; + +import org.dspace.app.ldn.service.LDNMessageService; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Factory implementation to get services for the notifyservices package, use + * NotifyServiceFactory.getInstance() to retrieve an implementation + * + * @author Francesco Bacchelli (francesco.bacchelli at 4science.com) + */ +public class LDNMessageServiceFactoryImpl extends LDNMessageServiceFactory { + + @Autowired(required = true) + private LDNMessageService ldnMessageService; + + @Override + public LDNMessageService getLDNMessageService() { + return ldnMessageService; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/factory/LDNRouterFactory.java b/dspace-api/src/main/java/org/dspace/app/ldn/factory/LDNRouterFactory.java new file mode 100644 index 000000000000..4b0f107d2498 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/ldn/factory/LDNRouterFactory.java @@ -0,0 +1,29 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.ldn.factory; + +import org.dspace.app.ldn.LDNRouter; +import org.dspace.services.factory.DSpaceServicesFactory; + +/** + * Abstract factory to get services for the ldn package, use + * LDNRouterFactory.getInstance() to retrieve an implementation + * + * @author Francesco Bacchelli (francesco.bacchelli at 4science.com) + */ +public abstract class LDNRouterFactory { + + public abstract LDNRouter getLDNRouter(); + + public static LDNRouterFactory getInstance() { + return DSpaceServicesFactory.getInstance() + .getServiceManager() + .getServiceByName("ldnRouter", + LDNRouterFactory.class); + } +} diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/factory/LDNRouterFactoryImpl.java b/dspace-api/src/main/java/org/dspace/app/ldn/factory/LDNRouterFactoryImpl.java new file mode 100644 index 000000000000..f411b9d935d0 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/ldn/factory/LDNRouterFactoryImpl.java @@ -0,0 +1,28 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.ldn.factory; + +import org.dspace.app.ldn.LDNRouter; +import org.springframework.beans.factory.annotation.Autowired; +/** + * Factory implementation to get services for the ldn package, + * use ldnRouter spring bean instance to retrieve an implementation + * + * @author Francesco Bacchelli (mohamed.eskander at 4science.com) + */ +public class LDNRouterFactoryImpl extends LDNRouterFactory { + + @Autowired(required = true) + private LDNRouter ldnRouter; + + @Override + public LDNRouter getLDNRouter() { + return ldnRouter; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/factory/NotifyServiceFactory.java b/dspace-api/src/main/java/org/dspace/app/ldn/factory/NotifyServiceFactory.java new file mode 100644 index 000000000000..ea488ca25031 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/ldn/factory/NotifyServiceFactory.java @@ -0,0 +1,36 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.ldn.factory; + +import org.dspace.app.ldn.service.LDNMessageService; +import org.dspace.app.ldn.service.NotifyPatternToTriggerService; +import org.dspace.app.ldn.service.NotifyService; +import org.dspace.app.ldn.service.NotifyServiceInboundPatternService; +import org.dspace.services.factory.DSpaceServicesFactory; + +/** + * Abstract factory to get services for the NotifyService package, + * use NotifyServiceFactory.getInstance() to retrieve an implementation + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + */ +public abstract class NotifyServiceFactory { + + public abstract NotifyService getNotifyService(); + + public abstract NotifyServiceInboundPatternService getNotifyServiceInboundPatternService(); + + public abstract NotifyPatternToTriggerService getNotifyPatternToTriggerService(); + + public abstract LDNMessageService getLDNMessageService(); + + public static NotifyServiceFactory getInstance() { + return DSpaceServicesFactory.getInstance().getServiceManager().getServiceByName( + "notifyServiceFactory", NotifyServiceFactory.class); + } +} diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/factory/NotifyServiceFactoryImpl.java b/dspace-api/src/main/java/org/dspace/app/ldn/factory/NotifyServiceFactoryImpl.java new file mode 100644 index 000000000000..84e15ee261a2 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/ldn/factory/NotifyServiceFactoryImpl.java @@ -0,0 +1,56 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.ldn.factory; + +import org.dspace.app.ldn.service.LDNMessageService; +import org.dspace.app.ldn.service.NotifyPatternToTriggerService; +import org.dspace.app.ldn.service.NotifyService; +import org.dspace.app.ldn.service.NotifyServiceInboundPatternService; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Factory implementation to get services for the notifyservices package, + * use NotifyServiceFactory.getInstance() to retrieve an implementation + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + */ +public class NotifyServiceFactoryImpl extends NotifyServiceFactory { + + @Autowired(required = true) + private NotifyService notifyService; + + @Autowired(required = true) + private NotifyServiceInboundPatternService notifyServiceInboundPatternService; + + @Autowired(required = true) + private NotifyPatternToTriggerService notifyPatternToTriggerService; + + @Autowired(required = true) + private LDNMessageService ldnMessageService; + + @Override + public NotifyService getNotifyService() { + return notifyService; + } + + @Override + public NotifyServiceInboundPatternService getNotifyServiceInboundPatternService() { + return notifyServiceInboundPatternService; + } + + @Override + public NotifyPatternToTriggerService getNotifyPatternToTriggerService() { + return notifyPatternToTriggerService; + } + + @Override + public LDNMessageService getLDNMessageService() { + return ldnMessageService; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/model/Actor.java b/dspace-api/src/main/java/org/dspace/app/ldn/model/Actor.java new file mode 100644 index 000000000000..a81cc3f8008a --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/ldn/model/Actor.java @@ -0,0 +1,41 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.ldn.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * used to map @see org.dspace.app.ldn.model.Notification + */ +public class Actor extends Base { + + @JsonProperty("name") + private String name; + + /** + * + */ + public Actor() { + super(); + } + + /** + * @return String + */ + public String getName() { + return name; + } + + /** + * @param name + */ + public void setName(String name) { + this.name = name; + } + +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/model/Base.java b/dspace-api/src/main/java/org/dspace/app/ldn/model/Base.java new file mode 100644 index 000000000000..6ddaae110e1f --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/ldn/model/Base.java @@ -0,0 +1,115 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.ldn.model; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * used to map @see org.dspace.app.ldn.model.Notification + */ +@JsonInclude(Include.NON_EMPTY) +@JsonIgnoreProperties(ignoreUnknown = true) +public class Base { + + @JsonProperty("id") + private String id; + + @JsonProperty("type") + private Set type; + + /** + * + */ + public Base() { + type = new HashSet<>(); + } + + /** + * @return String + */ + public String getId() { + return id; + } + + /** + * @param id + */ + public void setId(String id) { + this.id = id; + } + + /** + * @return Set + */ + public Set getType() { + return type; + } + + /** + * @param type + */ + public void setType(java.lang.Object type) { + if (type instanceof String) { + this.type.add((String) type); + } else if (type instanceof Collection) { + this.type.addAll((Collection) type); + } + } + + /** + * @param type + */ + public void addType(String type) { + this.type.add(type); + } + + /** + * @return int + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((id == null) ? 0 : id.hashCode()); + return result; + } + + /** + * @param obj + * @return boolean + */ + @Override + public boolean equals(java.lang.Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Base other = (Base) obj; + if (id == null) { + if (other.id != null) { + return false; + } + } else if (!id.equals(other.id)) { + return false; + } + return true; + } + +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/model/Citation.java b/dspace-api/src/main/java/org/dspace/app/ldn/model/Citation.java new file mode 100644 index 000000000000..7abe5c8ef44a --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/ldn/model/Citation.java @@ -0,0 +1,59 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.ldn.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + + +/** + * used to map @see org.dspace.app.ldn.model.Notification + */ +public class Citation extends Base { + + @JsonProperty("ietf:cite-as") + private String ietfCiteAs; + + @JsonProperty("ietf:item") + private Url url; + + /** + * + */ + public Citation() { + super(); + } + + /** + * @return String + */ + public String getIetfCiteAs() { + return ietfCiteAs; + } + + /** + * @param ietfCiteAs + */ + public void setIetfCiteAs(String ietfCiteAs) { + this.ietfCiteAs = ietfCiteAs; + } + + /** + * @return Url + */ + public Url getUrl() { + return url; + } + + /** + * @param url + */ + public void setUrl(Url url) { + this.url = url; + } + +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/model/Context.java b/dspace-api/src/main/java/org/dspace/app/ldn/model/Context.java new file mode 100644 index 000000000000..78fe37341697 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/ldn/model/Context.java @@ -0,0 +1,61 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.ldn.model; + +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonProperty; + + +/** + * used to map @see org.dspace.app.ldn.model.Notification + */ +public class Context extends Citation { + + @JsonProperty("IsSupplementedBy") + private List isSupplementedBy; + + @JsonProperty("IsSupplementTo") + private List isSupplementTo; + + /** + * + */ + public Context() { + super(); + } + + /** + * @return List + */ + public List getIsSupplementedBy() { + return isSupplementedBy; + } + + /** + * @param isSupplementedBy + */ + public void setIsSupplementedBy(List isSupplementedBy) { + this.isSupplementedBy = isSupplementedBy; + } + + /** + * @return List + */ + public List getIsSupplementTo() { + return isSupplementTo; + } + + /** + * @param isSupplementTo + */ + public void setIsSupplementTo(List isSupplementTo) { + this.isSupplementTo = isSupplementTo; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/model/Notification.java b/dspace-api/src/main/java/org/dspace/app/ldn/model/Notification.java new file mode 100644 index 000000000000..52bc9840f42d --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/ldn/model/Notification.java @@ -0,0 +1,159 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.ldn.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +/** + * the json object from witch @see org.dspace.app.ldn.LDNMessageEntity are created. + * see official coar doc + */ +@JsonPropertyOrder(value = { + "@context", + "id", + "type", + "actor", + "context", + "object", + "origin", + "target", + "inReplyTo" +}) +public class Notification extends Base { + + @JsonProperty("@context") + private String[] c = new String[] { + "https://purl.org/coar/notify", + "https://www.w3.org/ns/activitystreams" + }; + + @JsonProperty("actor") + private Actor actor; + + @JsonProperty("context") + private Context context; + + @JsonProperty("object") + private Object object; + + @JsonProperty("origin") + private Service origin; + + @JsonProperty("target") + private Service target; + + @JsonProperty("inReplyTo") + private String inReplyTo; + + /** + * + */ + public Notification() { + super(); + } + + /** + * @return String[] + */ + public String[] getC() { + return c; + } + + /** + * @param c + */ + public void setC(String[] c) { + this.c = c; + } + + /** + * @return Actor + */ + public Actor getActor() { + return actor; + } + + /** + * @param actor + */ + public void setActor(Actor actor) { + this.actor = actor; + } + + /** + * @return Context + */ + public Context getContext() { + return context; + } + + /** + * @param context + */ + public void setContext(Context context) { + this.context = context; + } + + /** + * @return Object + */ + public Object getObject() { + return object; + } + + /** + * @param object + */ + public void setObject(Object object) { + this.object = object; + } + + /** + * @return Service + */ + public Service getOrigin() { + return origin; + } + + /** + * @param origin + */ + public void setOrigin(Service origin) { + this.origin = origin; + } + + /** + * @return Service + */ + public Service getTarget() { + return target; + } + + /** + * @param target + */ + public void setTarget(Service target) { + this.target = target; + } + + /** + * @return String + */ + public String getInReplyTo() { + return inReplyTo; + } + + /** + * @param inReplyTo + */ + public void setInReplyTo(String inReplyTo) { + this.inReplyTo = inReplyTo; + } + +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/model/NotifyRequestStatus.java b/dspace-api/src/main/java/org/dspace/app/ldn/model/NotifyRequestStatus.java new file mode 100644 index 000000000000..8333eae91024 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/ldn/model/NotifyRequestStatus.java @@ -0,0 +1,63 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.ldn.model; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +@JsonPropertyOrder(value = { + "itemuuid", + "notifyStatus" +}) + +/** + * item requests of LDN messages of type + * + * "Offer", "coar-notify:EndorsementAction" + * "Offer", "coar-notify:IngestAction" + * "Offer", "coar-notify:ReviewAction" + * + * and their acknowledgements - if any + * + * @author Francesco Bacchelli (francesco.bacchelli at 4science dot it) + */ +public class NotifyRequestStatus extends Base { + + private UUID itemUuid; + + private List notifyStatus; + + public NotifyRequestStatus() { + super(); + this.notifyStatus = new ArrayList(); + } + + public UUID getItemUuid() { + return itemUuid; + } + + public void setItemUuid(UUID itemUuid) { + this.itemUuid = itemUuid; + } + + public void addRequestStatus(RequestStatus rs) { + this.notifyStatus.add(rs); + } + + public List getNotifyStatus() { + return notifyStatus; + } + + public void setNotifyStatus(List notifyStatus) { + this.notifyStatus = notifyStatus; + } + +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/model/NotifyRequestStatusEnum.java b/dspace-api/src/main/java/org/dspace/app/ldn/model/NotifyRequestStatusEnum.java new file mode 100644 index 000000000000..437c624f84d8 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/ldn/model/NotifyRequestStatusEnum.java @@ -0,0 +1,18 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.ldn.model; +/** + * REQUESTED means acknowledgements not received yet + * ACCEPTED means acknowledgements of "Accept" type received + * REJECTED means ack of "TentativeReject" type received + * + * @author Francesco Bacchelli (francesco.bacchelli at 4science.com) + */ +public enum NotifyRequestStatusEnum { + REJECTED, ACCEPTED, REQUESTED +} diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/model/Object.java b/dspace-api/src/main/java/org/dspace/app/ldn/model/Object.java new file mode 100644 index 000000000000..8913af47dae1 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/ldn/model/Object.java @@ -0,0 +1,74 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.ldn.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * used to map @see org.dspace.app.ldn.model.Notification + */ +public class Object extends Citation { + + @JsonProperty("as:object") + private String asObject; + + @JsonProperty("as:relationship") + private String asRelationship; + + @JsonProperty("as:subject") + private String asSubject; + + @JsonProperty("sorg:name") + private String title; + + /** + * + */ + public Object() { + super(); + } + + /** + * @return String + */ + public String getTitle() { + return title; + } + + /** + * @param title + */ + public void setTitle(String title) { + this.title = title; + } + + public String getAsObject() { + return asObject; + } + + public void setAsObject(String asObject) { + this.asObject = asObject; + } + + public String getAsRelationship() { + return asRelationship; + } + + public void setAsRelationship(String asRelationship) { + this.asRelationship = asRelationship; + } + + public String getAsSubject() { + return asSubject; + } + + public void setAsSubject(String asSubject) { + this.asSubject = asSubject; + } + +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/model/RequestStatus.java b/dspace-api/src/main/java/org/dspace/app/ldn/model/RequestStatus.java new file mode 100644 index 000000000000..e33bc3eeb7d5 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/ldn/model/RequestStatus.java @@ -0,0 +1,47 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.ldn.model; + +/** + * Information about the Offer and Acknowledgements targeting a specified Item + * + * @author Francesco Bacchelli (francesco.bacchelli at 4science.com) + */ +public class RequestStatus { + + private String serviceName; + private String serviceUrl; + private String offerType; + private NotifyRequestStatusEnum status; + + public String getServiceName() { + return serviceName; + } + public void setServiceName(String serviceName) { + this.serviceName = serviceName; + } + public String getServiceUrl() { + return serviceUrl; + } + public void setServiceUrl(String serviceUrl) { + this.serviceUrl = serviceUrl; + } + public NotifyRequestStatusEnum getStatus() { + return status; + } + public void setStatus(NotifyRequestStatusEnum status) { + this.status = status; + } + public String getOfferType() { + return offerType; + } + public void setOfferType(String offerType) { + this.offerType = offerType; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/model/Service.java b/dspace-api/src/main/java/org/dspace/app/ldn/model/Service.java new file mode 100644 index 000000000000..cdd3ba5bb536 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/ldn/model/Service.java @@ -0,0 +1,41 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.ldn.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * used to map @see org.dspace.app.ldn.model.Notification + */ +public class Service extends Base { + + @JsonProperty("inbox") + private String inbox; + + /** + * + */ + public Service() { + super(); + } + + /** + * @return String + */ + public String getInbox() { + return inbox; + } + + /** + * @param inbox + */ + public void setInbox(String inbox) { + this.inbox = inbox; + } + +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/model/Url.java b/dspace-api/src/main/java/org/dspace/app/ldn/model/Url.java new file mode 100644 index 000000000000..47093e02e44b --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/ldn/model/Url.java @@ -0,0 +1,41 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.ldn.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * used to map @see org.dspace.app.ldn.model.Citation + */ +public class Url extends Base { + + @JsonProperty("mediaType") + private String mediaType; + + /** + * + */ + public Url() { + super(); + } + + /** + * @return String + */ + public String getMediaType() { + return mediaType; + } + + /** + * @param mediaType + */ + public void setMediaType(String mediaType) { + this.mediaType = mediaType; + } + +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/processor/LDNContextRepeater.java b/dspace-api/src/main/java/org/dspace/app/ldn/processor/LDNContextRepeater.java new file mode 100644 index 000000000000..a5ef0957ff8f --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/ldn/processor/LDNContextRepeater.java @@ -0,0 +1,141 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.ldn.processor; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.dspace.app.ldn.model.Context; +import org.dspace.app.ldn.model.Notification; + +/** + * Context repeater to iterate over array context properties of a received + * notification. The returned notification iterator is a notification with + * context array elements hoisted onto the root of the notification context. + */ +public class LDNContextRepeater { + + private final static Logger log = LogManager.getLogger(LDNContextRepeater.class); + + private final static String CONTEXT = "context"; + + private String repeatOver; + + /** + * @return String + */ + public String getRepeatOver() { + return repeatOver; + } + + /** + * @param repeatOver + */ + public void setRepeatOver(String repeatOver) { + this.repeatOver = repeatOver; + } + + /** + * @param notification + * @return Iterator + */ + public Iterator iterator(Notification notification) { + return new NotificationIterator(notification, repeatOver); + } + + /** + * Private inner class defining the notification iterator. + */ + private class NotificationIterator implements Iterator { + + private final List notifications; + + /** + * Convert notification to JsonNode in order to clone for each context array + * element. Each element is then hoisted to the root of the cloned notification + * context. + * + * @param notification received notification + * @param repeatOver which context property to repeat over + */ + private NotificationIterator(Notification notification, String repeatOver) { + this.notifications = new ArrayList<>(); + + if (Objects.nonNull(repeatOver)) { + ObjectMapper objectMapper = new ObjectMapper(); + + JsonNode notificationNode = objectMapper.valueToTree(notification); + + log.debug("Notification {}", notificationNode); + + JsonNode topContextNode = notificationNode.get(CONTEXT); + if (topContextNode.isNull()) { + log.warn("Notification is missing context"); + return; + } + + JsonNode contextArrayNode = topContextNode.get(repeatOver); + if (contextArrayNode == null || contextArrayNode.isNull()) { + log.error("Notification context {} is not defined", repeatOver); + return; + } + + if (contextArrayNode.isArray()) { + + for (JsonNode contextNode : ((ArrayNode) contextArrayNode)) { + + try { + Context context = objectMapper.treeToValue(contextNode, Context.class); + + Notification copy = objectMapper.treeToValue(notificationNode, Notification.class); + + copy.setContext(context); + + this.notifications.add(copy); + } catch (JsonProcessingException e) { + log.error("Failed to copy notification"); + } + + } + + } else { + log.error("Notification context {} is not an array", repeatOver); + } + + } else { + this.notifications.add(notification); + } + } + + /** + * @return boolean + */ + @Override + public boolean hasNext() { + return !this.notifications.isEmpty(); + } + + /** + * @return Notification + */ + @Override + public Notification next() { + return this.notifications.remove(0); + } + + } + +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/processor/LDNMetadataProcessor.java b/dspace-api/src/main/java/org/dspace/app/ldn/processor/LDNMetadataProcessor.java new file mode 100644 index 000000000000..9782e2504588 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/ldn/processor/LDNMetadataProcessor.java @@ -0,0 +1,231 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.ldn.processor; + +import static java.lang.String.format; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; + +import org.apache.http.HttpStatus; +import org.apache.http.client.HttpResponseException; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.dspace.app.ldn.action.LDNAction; +import org.dspace.app.ldn.action.LDNActionStatus; +import org.dspace.app.ldn.model.Notification; +import org.dspace.app.ldn.service.LDNMessageService; +import org.dspace.app.ldn.utility.LDNUtils; +import org.dspace.content.DSpaceObject; +import org.dspace.content.Item; +import org.dspace.content.service.ItemService; +import org.dspace.core.Constants; +import org.dspace.core.Context; +import org.dspace.handle.service.HandleService; +import org.dspace.services.ConfigurationService; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Linked Data Notification metadata processor for consuming notifications. The + * storage of notification details are within item metadata. + */ +public class LDNMetadataProcessor implements LDNProcessor { + + private final static Logger log = LogManager.getLogger(LDNMetadataProcessor.class); + + @Autowired + private ItemService itemService; + + @Autowired + private LDNMessageService ldnMessageService; + + @Autowired + private ConfigurationService configurationService; + + private static final Set OBJECT_SUBJECT_ITEM_TYPES = Set.of( + "Announce", + "coar-notify:RelationshipAction"); + + private static final Set CONTEXT_ID_ITEM_TYPES = Set.of( + "Announce", + "TentativeReject", + "Accept", + "coar-notify:ReviewAction", + "coar-notify:IngestAction", + "coar-notify:EndorsementAction"); + + private static final Set OBJECT_ID_ITEM_TYPES = Set.of( + "Offer", + "coar-notify:ReviewAction", + "coar-notify:EndorsementAction", + "coar-notify:IngestAction"); + + @Autowired + private HandleService handleService; + + private LDNContextRepeater repeater = new LDNContextRepeater(); + + private List actions = new ArrayList<>(); + + /** + * Initialize velocity engine for templating. + */ + private LDNMetadataProcessor() { + + } + + /** + * Process notification by repeating over context, processing each context + * notification, and running actions post processing. + * + * @param notification received notification + * @throws Exception something went wrong processing the notification + */ + @Override + public void process(Context context, Notification notification) throws Exception { + Item item = lookupItem(context, notification); + runActions(context, notification, item); + } + + /** + * Run all actions defined for the processor. + * + * @param notification current context notification + * @param item associated item + * + * @return ActionStatus result status of running the action + * + * @throws Exception failed execute the action + */ + private LDNActionStatus runActions(Context context, Notification notification, Item item) throws Exception { + LDNActionStatus operation = LDNActionStatus.CONTINUE; + for (LDNAction action : actions) { + log.info("Running action {} for notification {} {}", + action.getClass().getSimpleName(), + notification.getId(), + notification.getType()); + + operation = action.execute(context, notification, item); + if (operation == LDNActionStatus.ABORT) { + break; + } + } + + return operation; + } + + /** + * @return LDNContextRepeater + */ + public LDNContextRepeater getRepeater() { + return repeater; + } + + /** + * @param repeater + */ + public void setRepeater(LDNContextRepeater repeater) { + this.repeater = repeater; + } + + /** + * @return List + */ + public List getActions() { + return actions; + } + + /** + * @param actions + */ + public void setActions(List actions) { + this.actions = actions; + } + + /** + * Lookup associated item to the notification context. If UUID in URL, lookup by + * UUID, else lookup by handle. + * + * @param context current context + * @param notification current context notification + * + * @return Item associated item + * + * @throws SQLException failed to lookup item + * @throws HttpResponseException redirect failure + */ + private Item lookupItem(Context context, Notification notification) throws SQLException, HttpResponseException { + Item item = null; + String url = null; + + if (CONTEXT_ID_ITEM_TYPES.containsAll(notification.getType())) { + url = notification.getContext().getId(); + } else if (OBJECT_ID_ITEM_TYPES.containsAll(notification.getType())) { + url = notification.getObject().getId(); + } else if (OBJECT_SUBJECT_ITEM_TYPES.containsAll(notification.getType())) { + // need to understand if we're sender or receiver + if (ldnMessageService.isTargetCurrent(notification)) { + // this means we're sending the notification + url = notification.getObject().getAsObject(); + // use as:object for sender + } else { + // this means we're receiving the notification + url = notification.getObject().getAsSubject(); + // use as:subject for receiver + } + } + + log.info("Looking up item {}", url); + + item = resolveItemByUrl(context, url, notification); + + return item; + } + + private Item resolveItemByUrl(Context context, String url, Notification notification) + throws SQLException, HttpResponseException { + Item item = null; + if (LDNUtils.hasUUIDInURL(url)) { + UUID uuid = LDNUtils.getUUIDFromURL(url); + + item = itemService.find(context, uuid); + + if (Objects.isNull(item)) { + throw new HttpResponseException(HttpStatus.SC_NOT_FOUND, + format("Item with uuid %s not found", uuid)); + } + return item; + } + String handle = handleService.resolveUrlToHandle(context, url); + + if (Objects.isNull(handle)) { + throw new HttpResponseException(HttpStatus.SC_NOT_FOUND, + format("Handle not found for %s", url)); + } + + DSpaceObject object = handleService.resolveToObject(context, handle); + + if (Objects.isNull(object)) { + throw new HttpResponseException(HttpStatus.SC_NOT_FOUND, + format("Item with handle %s not found", handle)); + } + + if (object.getType() == Constants.ITEM) { + item = (Item) object; + } else { + throw new HttpResponseException(HttpStatus.SC_UNPROCESSABLE_ENTITY, + format("Handle %s does not resolve to an item", handle)); + } + return item; + } + +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/processor/LDNProcessor.java b/dspace-api/src/main/java/org/dspace/app/ldn/processor/LDNProcessor.java new file mode 100644 index 000000000000..279ec5cedc4b --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/ldn/processor/LDNProcessor.java @@ -0,0 +1,25 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.ldn.processor; + +import org.dspace.app.ldn.model.Notification; +import org.dspace.core.Context; + +/** + * Processor interface to allow for custom implementations of process. + */ +public interface LDNProcessor { + + /** + * Process received notification. + * + * @param notification received notification + * @throws Exception something went wrong processing the notification + */ + public void process(Context context, Notification notification) throws Exception; +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/service/LDNMessageService.java b/dspace-api/src/main/java/org/dspace/app/ldn/service/LDNMessageService.java new file mode 100644 index 000000000000..eb18c6a69a70 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/ldn/service/LDNMessageService.java @@ -0,0 +1,165 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.ldn.service; + +import java.sql.SQLException; +import java.util.List; + +import org.dspace.app.ldn.LDNMessageEntity; +import org.dspace.app.ldn.NotifyServiceEntity; +import org.dspace.app.ldn.model.Notification; +import org.dspace.app.ldn.model.NotifyRequestStatus; +import org.dspace.app.ldn.model.Service; +import org.dspace.content.Item; +import org.dspace.core.Context; + +/** + * Service interface class for the {@link LDNMessageEntity} object. + * + * @author Mohamed Eskander (mohamed.eskander at 4science dot it) + */ +public interface LDNMessageService { + + /** + * find the ldn message by id + * + * @param context the context + * @param id the uri + * @return the ldn message by id + * @throws SQLException If something goes wrong in the database + */ + public LDNMessageEntity find(Context context, String id) throws SQLException; + + /** + * find all ldn messages + * + * @param context the context + * @return all ldn messages by id + * @throws SQLException If something goes wrong in the database + */ + public List findAll(Context context) throws SQLException; + + /** + * Creates a new LDNMessage + * + * @param context The DSpace context + * @param id the uri + * @return the created LDN Message + * @throws SQLException If something goes wrong in the database + */ + public LDNMessageEntity create(Context context, String id) throws SQLException; + + /** + * Creates a new LDNMessage + * + * @param context The DSpace context + * @param notification the requested notification + * @param sourceIp the source ip + * @return the created LDN Message + * @throws SQLException If something goes wrong in the database + */ + public LDNMessageEntity create(Context context, Notification notification, String sourceIp) throws SQLException; + + /** + * Update the provided LDNMessage + * + * @param context The DSpace context + * @param ldnMessage the LDNMessage + * @throws SQLException If something goes wrong in the database + */ + public void update(Context context, LDNMessageEntity ldnMessage) throws SQLException; + + /** + * Find the oldest queued LDNMessages that still can be elaborated + * + * @return list of LDN messages + * @param context The DSpace context + * @throws SQLException If something goes wrong in the database + */ + public List findOldestMessagesToProcess(Context context) throws SQLException; + + /** + * Find all messages in the queue with the Processing status but timed-out + * + * @return all the LDN Messages to be fixed on their queue_ attributes + * @param context The DSpace context + * @throws SQLException If something goes wrong in the database + */ + public List findProcessingTimedoutMessages(Context context) throws SQLException; + + /** + * Find all messages in the queue with the Processing status but timed-out and modify their queue_status + * considering the queue_attempts + * + * @return number of messages fixed + * @param context The DSpace context + * @throws SQLException + */ + public int checkQueueMessageTimeout(Context context) throws SQLException; + + /** + * Elaborates the oldest enqueued message + * + * @return number of messages fixed + * @param context The DSpace context + */ + public int extractAndProcessMessageFromQueue(Context context) throws SQLException; + + /** + * find the related notify service entity + * + * @param context the context + * @param service the service + * @return the NotifyServiceEntity + * @throws SQLException if something goes wrong + */ + public NotifyServiceEntity findNotifyService(Context context, Service service) throws SQLException; + + /** + * find the ldn messages of Requests by item uuid + * + * @param context the context + * @param item the item + * @return the item requests object + * @throws SQLException If something goes wrong in the database + */ + public NotifyRequestStatus findRequestsByItem(Context context, Item item) throws SQLException; + + /** + * delete the provided ldn message + * + * @param context the context + * @param ldnMessage the ldn message + * @throws SQLException if something goes wrong + */ + public void delete(Context context, LDNMessageEntity ldnMessage) throws SQLException; + + /** + * find the ldn messages to be reprocessed + * + * @param context the context + * @throws SQLException if something goes wrong + */ + public List findMessagesToBeReprocessed(Context context) throws SQLException; + + /** + * check if IP number is included in the configured ip-range on the Notify + * Service + * + * @param origin the Notify Service entity + * @param sourceIp the ip to evaluate + */ + public boolean isValidIp(NotifyServiceEntity origin, String sourceIp); + + /** + * check if the notification is targeting the current system + * + * @param notification the LDN Message entity + */ + boolean isTargetCurrent(Notification notification); +} diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/service/NotifyPatternToTriggerService.java b/dspace-api/src/main/java/org/dspace/app/ldn/service/NotifyPatternToTriggerService.java new file mode 100644 index 000000000000..c2c3de7e06d5 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/ldn/service/NotifyPatternToTriggerService.java @@ -0,0 +1,84 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.ldn.service; + +import java.sql.SQLException; +import java.util.List; + +import org.dspace.app.ldn.NotifyPatternToTrigger; +import org.dspace.content.Item; +import org.dspace.core.Context; + +/** + * Service interface class for the {@link NotifyPatternToTrigger} object. + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + */ +public interface NotifyPatternToTriggerService { + + /** + * find all notify patterns to be triggered + * + * @param context the context + * @return all notify patterns to be trigger + * @throws SQLException if database error + */ + public List findAll(Context context) throws SQLException; + + /** + * find list of Notify Patterns To be Triggered by item + * + * @param context the context + * @param item the item of NotifyPatternToTrigger + * @return the matched NotifyPatternToTrigger list by item + * @throws SQLException if database error + */ + public List findByItem(Context context, Item item) + throws SQLException; + + /** + * find list of Notify Patterns To be Triggered by item and pattern + * + * @param context the context + * @param item the item of NotifyPatternToTrigger + * @param pattern the pattern of NotifyPatternToTrigger + * + * @return the matched NotifyPatternToTrigger list by item and pattern + * @throws SQLException if database error + */ + public List findByItemAndPattern(Context context, Item item, String pattern) + throws SQLException; + + /** + * create new notifyPatternToTrigger + * + * @param context the context + * @return the created NotifyPatternToTrigger + * @throws SQLException if database error + */ + public NotifyPatternToTrigger create(Context context) throws SQLException; + + /** + * update the provided notifyPatternToTrigger + * + * @param context the context + * @param notifyPatternToTrigger the notifyPatternToTrigger + * @throws SQLException if database error + */ + public void update(Context context, NotifyPatternToTrigger notifyPatternToTrigger) throws SQLException; + + /** + * delete the provided notifyPatternToTrigger + * + * @param context the context + * @param notifyPatternToTrigger the notifyPatternToTrigger + * @throws SQLException if database error + */ + public void delete(Context context, NotifyPatternToTrigger notifyPatternToTrigger) throws SQLException; + +} diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/service/NotifyService.java b/dspace-api/src/main/java/org/dspace/app/ldn/service/NotifyService.java new file mode 100644 index 000000000000..e6ac0d63b438 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/ldn/service/NotifyService.java @@ -0,0 +1,92 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.ldn.service; + +import java.sql.SQLException; +import java.util.List; + +import org.dspace.app.ldn.NotifyServiceEntity; +import org.dspace.core.Context; + +/** + * Service interface class for the {@link NotifyServiceEntity} object. + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + */ +public interface NotifyService { + + /** + * find all notify service entities + * + * @param context the context + * @return all notify service entities + * @throws SQLException if database error + */ + public List findAll(Context context) throws SQLException; + + /** + * find one NotifyServiceEntity by id + * + * @param context the context + * @param id the id of NotifyServiceEntity + * @return the matched NotifyServiceEntity by id + * @throws SQLException if database error + */ + public NotifyServiceEntity find(Context context, Integer id) throws SQLException; + + /** + * create new notifyServiceEntity + * + * @param context the context + * @param name name of the service + * @return the created NotifyServiceEntity + * @throws SQLException if database error + */ + public NotifyServiceEntity create(Context context, String name) throws SQLException; + + /** + * update the provided notifyServiceEntity + * + * @param context the context + * @param notifyServiceEntity the notifyServiceEntity + * @throws SQLException if database error + */ + public void update(Context context, NotifyServiceEntity notifyServiceEntity) throws SQLException; + + /** + * delete the provided notifyServiceEntity + * + * @param context the context + * @param notifyServiceEntity the notifyServiceEntity + * @throws SQLException if database error + */ + public void delete(Context context, NotifyServiceEntity notifyServiceEntity) throws SQLException; + + /** + * find the NotifyServiceEntity matched with the provided ldnUrl + * + * @param context the context + * @param ldnUrl the ldnUrl + * @return the NotifyServiceEntity matched the provided ldnUrl + * @throws SQLException if database error + */ + public NotifyServiceEntity findByLdnUrl(Context context, String ldnUrl) throws SQLException; + + /** + * find all NotifyServiceEntity matched the provided inbound pattern + * from its related notifyServiceInboundPatterns + * also with 'automatic' equals to false + * + * @param context the context + * @param pattern the ldnUrl + * @return all NotifyServiceEntity matched the provided pattern + * @throws SQLException if database error + */ + public List findManualServicesByInboundPattern(Context context, String pattern) + throws SQLException; +} diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/service/NotifyServiceInboundPatternService.java b/dspace-api/src/main/java/org/dspace/app/ldn/service/NotifyServiceInboundPatternService.java new file mode 100644 index 000000000000..8cd92d45dd30 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/ldn/service/NotifyServiceInboundPatternService.java @@ -0,0 +1,76 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.ldn.service; + +import java.sql.SQLException; +import java.util.List; + +import org.dspace.app.ldn.NotifyServiceEntity; +import org.dspace.app.ldn.NotifyServiceInboundPattern; +import org.dspace.core.Context; + +/** + * Service interface class for the {@link NotifyServiceInboundPattern} object. + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + */ +public interface NotifyServiceInboundPatternService { + + /** + * find all notifyServiceInboundPatterns matched with + * the provided notifyServiceEntity and pattern + * + * @param context the context + * @param notifyServiceEntity the notifyServiceEntity + * @param pattern the pattern + * @return all notifyServiceInboundPatterns matched with + * the provided notifyServiceEntity and pattern + * @throws SQLException if database error + */ + public NotifyServiceInboundPattern findByServiceAndPattern(Context context, + NotifyServiceEntity notifyServiceEntity, + String pattern) throws SQLException; + + /** + * find all automatic notifyServiceInboundPatterns + * + * @param context the context + * @return all automatic notifyServiceInboundPatterns + * @throws SQLException if database error + */ + public List findAutomaticPatterns(Context context) throws SQLException; + + /** + * create new notifyServiceInboundPattern + * + * @param context the context + * @param notifyServiceEntity the notifyServiceEntity + * @return the created notifyServiceInboundPattern + * @throws SQLException if database error + */ + public NotifyServiceInboundPattern create(Context context, NotifyServiceEntity notifyServiceEntity) + throws SQLException; + + /** + * update the provided notifyServiceInboundPattern + * + * @param context the context + * @param inboundPattern the notifyServiceInboundPattern + * @throws SQLException if database error + */ + public void update(Context context, NotifyServiceInboundPattern inboundPattern) throws SQLException; + + /** + * delete the provided notifyServiceInboundPattern + * + * @param context the context + * @param inboundPattern the notifyServiceInboundPattern + * @throws SQLException if database error + */ + public void delete(Context context, NotifyServiceInboundPattern inboundPattern) throws SQLException; +} diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/service/impl/LDNMessageServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/ldn/service/impl/LDNMessageServiceImpl.java new file mode 100644 index 000000000000..15f07a556112 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/ldn/service/impl/LDNMessageServiceImpl.java @@ -0,0 +1,404 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.ldn.service.impl; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.gson.JsonSyntaxException; +import org.apache.commons.lang.time.DateUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.Logger; +import org.dspace.app.ldn.LDNMessageEntity; +import org.dspace.app.ldn.LDNRouter; +import org.dspace.app.ldn.NotifyServiceEntity; +import org.dspace.app.ldn.dao.LDNMessageDao; +import org.dspace.app.ldn.dao.NotifyServiceDao; +import org.dspace.app.ldn.model.Notification; +import org.dspace.app.ldn.model.NotifyRequestStatus; +import org.dspace.app.ldn.model.NotifyRequestStatusEnum; +import org.dspace.app.ldn.model.RequestStatus; +import org.dspace.app.ldn.model.Service; +import org.dspace.app.ldn.processor.LDNProcessor; +import org.dspace.app.ldn.service.LDNMessageService; +import org.dspace.app.ldn.utility.LDNUtils; +import org.dspace.content.DSpaceObject; +import org.dspace.content.Item; +import org.dspace.content.service.ItemService; +import org.dspace.core.Constants; +import org.dspace.core.Context; +import org.dspace.discovery.indexobject.IndexableLDNNotification; +import org.dspace.event.Event; +import org.dspace.handle.service.HandleService; +import org.dspace.services.ConfigurationService; +import org.springframework.beans.factory.annotation.Autowired; + + +/** + * Implementation of {@link LDNMessageService} + * + * @author Mohamed Eskander (mohamed.eskander at 4science dot it) + */ +public class LDNMessageServiceImpl implements LDNMessageService { + + @Autowired(required = true) + private LDNMessageDao ldnMessageDao; + @Autowired(required = true) + private NotifyServiceDao notifyServiceDao; + @Autowired(required = true) + private ConfigurationService configurationService; + @Autowired(required = true) + private HandleService handleService; + @Autowired(required = true) + private ItemService itemService; + @Autowired(required = true) + private LDNRouter ldnRouter; + + private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(LDNMessageServiceImpl.class); + private static final String LDN_ID_PREFIX = "urn:uuid:"; + + protected LDNMessageServiceImpl() { + + } + + @Override + public LDNMessageEntity find(Context context, String id) throws SQLException { + + if (id == null) { + return null; + } + + id = id.startsWith(LDN_ID_PREFIX) ? id : LDN_ID_PREFIX + id; + return ldnMessageDao.findByID(context, LDNMessageEntity.class, id); + } + + @Override + public List findAll(Context context) throws SQLException { + return ldnMessageDao.findAll(context, LDNMessageEntity.class); + } + + @Override + public LDNMessageEntity create(Context context, String id) throws SQLException { + LDNMessageEntity result = ldnMessageDao.findByID(context, LDNMessageEntity.class, id); + if (result != null) { + throw new SQLException("Duplicate LDN Message ID [" + id + "] detected. This message is rejected."); + } + return ldnMessageDao.create(context, new LDNMessageEntity(id)); + } + + @Override + public LDNMessageEntity create(Context context, Notification notification, String sourceIp) throws SQLException { + LDNMessageEntity ldnMessage = create(context, notification.getId()); + DSpaceObject obj = findDspaceObjectByUrl(context, notification.getObject().getId()); + if (obj == null) { + if (isTargetCurrent(notification)) { + // this means we're sending the notification + obj = findDspaceObjectByUrl(context, notification.getObject().getAsObject()); + // use as:object for sender + } else { + // this means we're receiving the notification + obj = findDspaceObjectByUrl(context, notification.getObject().getAsSubject()); + // use as:subject for receiver + } + } + ldnMessage.setObject(obj); + if (null != notification.getContext()) { + ldnMessage.setContext(findDspaceObjectByUrl(context, notification.getContext().getId())); + } + ldnMessage.setOrigin(findNotifyService(context, notification.getOrigin())); + ldnMessage.setInReplyTo(find(context, notification.getInReplyTo())); + ObjectMapper mapper = new ObjectMapper(); + String message = null; + try { + message = mapper.writeValueAsString(notification); + ldnMessage.setMessage(message); + } catch (JsonProcessingException e) { + log.error("Notification json can't be correctly processed " + + "and stored inside the LDN Message Entity" + ldnMessage); + log.error(e); + } + ldnMessage.setType(StringUtils.joinWith(",", notification.getType())); + Set notificationType = notification.getType(); + if (notificationType == null) { + log.error("Notification has no notificationType attribute! " + notification); + return null; + } + ArrayList notificationTypeArrayList = new ArrayList(notificationType); + // sorting the list + Collections.sort(notificationTypeArrayList); + ldnMessage.setActivityStreamType(notificationTypeArrayList.get(0)); + if (notificationTypeArrayList.size() > 1) { + ldnMessage.setCoarNotifyType(notificationTypeArrayList.get(1)); + } + ldnMessage.setQueueStatus(LDNMessageEntity.QUEUE_STATUS_QUEUED); + ldnMessage.setSourceIp(sourceIp); + if (ldnMessage.getOrigin() == null && !"Offer".equalsIgnoreCase(ldnMessage.getActivityStreamType())) { + ldnMessage.setQueueStatus(LDNMessageEntity.QUEUE_STATUS_UNTRUSTED); + } else { + + boolean ipCheckRangeEnabled = configurationService.getBooleanProperty("ldn.ip-range.enabled", true); + if (ipCheckRangeEnabled && !isValidIp(ldnMessage.getOrigin(), sourceIp)) { + ldnMessage.setQueueStatus(LDNMessageEntity.QUEUE_STATUS_UNTRUSTED_IP); + } + } + ldnMessage.setQueueTimeout(new Date()); + + update(context, ldnMessage); + return ldnMessage; + } + + @Override + public boolean isValidIp(NotifyServiceEntity origin, String sourceIp) { + + String lowerIp = origin.getLowerIp(); + String upperIp = origin.getUpperIp(); + + try { + InetAddress ip = InetAddress.getByName(sourceIp); + InetAddress lowerBoundAddress = InetAddress.getByName(lowerIp); + InetAddress upperBoundAddress = InetAddress.getByName(upperIp); + + long ipLong = ipToLong(ip); + long lowerBoundLong = ipToLong(lowerBoundAddress); + long upperBoundLong = ipToLong(upperBoundAddress); + + return ipLong >= lowerBoundLong && ipLong <= upperBoundLong; + } catch (UnknownHostException e) { + return false; + } + } + + private long ipToLong(InetAddress ip) { + byte[] octets = ip.getAddress(); + long result = 0; + for (byte octet : octets) { + result <<= 8; + result |= octet & 0xff; + } + return result; + } + + @Override + public void update(Context context, LDNMessageEntity ldnMessage) throws SQLException { + // move the queue_status from UNTRUSTED to QUEUED if origin is a known NotifyService + if (ldnMessage.getOrigin() != null && + LDNMessageEntity.QUEUE_STATUS_UNTRUSTED.compareTo(ldnMessage.getQueueStatus()) == 0) { + ldnMessage.setQueueStatus(LDNMessageEntity.QUEUE_STATUS_QUEUED); + } + ldnMessageDao.save(context, ldnMessage); + UUID notificationUUID = UUID.fromString(ldnMessage.getID().replace(LDN_ID_PREFIX, "")); + ArrayList identifiersList = new ArrayList(); + identifiersList.add(ldnMessage.getID()); + context.addEvent( + new Event(Event.MODIFY, Constants.LDN_MESSAGE, + notificationUUID, + IndexableLDNNotification.TYPE, identifiersList)); + } + + private DSpaceObject findDspaceObjectByUrl(Context context, String url) throws SQLException { + String dspaceUrl = configurationService.getProperty("dspace.ui.url") + "/handle/"; + + if (StringUtils.startsWith(url, dspaceUrl)) { + return handleService.resolveToObject(context, url.substring(dspaceUrl.length())); + } + + String handleResolver = configurationService.getProperty("handle.canonical.prefix", "https://hdl.handle.net/"); + if (StringUtils.startsWith(url, handleResolver)) { + return handleService.resolveToObject(context, url.substring(handleResolver.length())); + } + + dspaceUrl = configurationService.getProperty("dspace.ui.url") + "/items/"; + if (StringUtils.startsWith(url, dspaceUrl)) { + return itemService.find(context, UUID.fromString(url.substring(dspaceUrl.length()))); + } + + return null; + } + + public NotifyServiceEntity findNotifyService(Context context, Service service) throws SQLException { + return notifyServiceDao.findByLdnUrl(context, service.getInbox()); + } + + @Override + public List findOldestMessagesToProcess(Context context) throws SQLException { + List result = null; + int max_attempts = configurationService.getIntProperty("ldn.processor.max.attempts"); + result = ldnMessageDao.findOldestMessageToProcess(context, max_attempts); + return result; + } + + @Override + public List findMessagesToBeReprocessed(Context context) throws SQLException { + List result = null; + result = ldnMessageDao.findMessagesToBeReprocessed(context); + return result; + } + + @Override + public List findProcessingTimedoutMessages(Context context) throws SQLException { + List result = null; + int max_attempts = configurationService.getIntProperty("ldn.processor.max.attempts"); + result = ldnMessageDao.findProcessingTimedoutMessages(context, max_attempts); + return result; + } + + @Override + public int extractAndProcessMessageFromQueue(Context context) throws SQLException { + int count = 0; + int timeoutInMinutes = configurationService.getIntProperty("ldn.processor.queue.msg.timeout", 60); + + List messages = findOldestMessagesToProcess(context); + messages.addAll(findMessagesToBeReprocessed(context)); + + Optional msgOpt = getSingleMessageEntity(messages); + + while (msgOpt.isPresent()) { + LDNProcessor processor = null; + LDNMessageEntity msg = msgOpt.get(); + processor = ldnRouter.route(msg); + try { + boolean isServiceDisabled = !isServiceEnabled(msg); + if (processor == null || isServiceDisabled) { + log.warn("No processor found for LDN message " + msg); + Integer status = isServiceDisabled ? LDNMessageEntity.QUEUE_STATUS_UNTRUSTED + : LDNMessageEntity.QUEUE_STATUS_UNMAPPED_ACTION; + msg.setQueueStatus(status); + msg.setQueueAttempts(msg.getQueueAttempts() + 1); + update(context, msg); + } else { + msg.setQueueLastStartTime(new Date()); + msg.setQueueStatus(LDNMessageEntity.QUEUE_STATUS_PROCESSING); + msg.setQueueTimeout(DateUtils.addMinutes(new Date(), timeoutInMinutes)); + update(context, msg); + ObjectMapper mapper = new ObjectMapper(); + Notification notification = mapper.readValue(msg.getMessage(), Notification.class); + processor.process(context, notification); + msg.setQueueStatus(LDNMessageEntity.QUEUE_STATUS_PROCESSED); + count++; + } + } catch (JsonSyntaxException jse) { + log.error("Unable to read JSON notification from LdnMessage " + msg, jse); + msg.setQueueStatus(LDNMessageEntity.QUEUE_STATUS_FAILED); + } catch (Exception e) { + log.error(e); + msg.setQueueStatus(LDNMessageEntity.QUEUE_STATUS_FAILED); + } finally { + msg.setQueueAttempts(msg.getQueueAttempts() + 1); + update(context, msg); + } + + messages = findOldestMessagesToProcess(context); + messages.addAll(findMessagesToBeReprocessed(context)); + msgOpt = getSingleMessageEntity(messages); + } + return count; + } + + private boolean isServiceEnabled(LDNMessageEntity msg) { + String localInboxUrl = configurationService.getProperty("ldn.notify.inbox"); + if (msg.getTarget() == null || StringUtils.equals(msg.getTarget().getLdnUrl(), localInboxUrl)) { + return msg.getOrigin().isEnabled(); + } + return msg.getTarget().isEnabled(); + } + + @Override + public int checkQueueMessageTimeout(Context context) throws SQLException { + int count = 0; + int maxAttempts = configurationService.getIntProperty("ldn.processor.max.attempts", 5); + /* + * put failed on processing messages with timed-out timeout and + * attempts >= configured_max_attempts put queue on processing messages with + * timed-out timeout and attempts < configured_max_attempts + */ + Optional msgOpt = getSingleMessageEntity(findProcessingTimedoutMessages(context)); + + while (msgOpt.isPresent()) { + LDNMessageEntity msg = msgOpt.get(); + try { + if (msg.getQueueAttempts() >= maxAttempts) { + msg.setQueueStatus(LDNMessageEntity.QUEUE_STATUS_FAILED); + } else { + msg.setQueueStatus(LDNMessageEntity.QUEUE_STATUS_QUEUED); + } + update(context, msg); + count++; + } catch (SQLException e) { + log.error("Can't update LDN message " + msg, e); + } + msgOpt = getSingleMessageEntity(findProcessingTimedoutMessages(context)); + } + return count; + } + + public Optional getSingleMessageEntity(Collection messages) { + return messages.stream().findFirst(); + } + + @Override + public NotifyRequestStatus findRequestsByItem(Context context, Item item) throws SQLException { + NotifyRequestStatus result = new NotifyRequestStatus(); + result.setItemUuid(item.getID()); + List msgs = ldnMessageDao.findAllMessagesByItem( + context, item, "Offer"); + if (msgs != null && !msgs.isEmpty()) { + for (LDNMessageEntity msg : msgs) { + RequestStatus offer = new RequestStatus(); + NotifyServiceEntity nse = msg.getOrigin(); + if (nse == null) { + nse = msg.getTarget(); + } + offer.setServiceName(nse == null ? "Unknown Service" : nse.getName()); + offer.setServiceUrl(nse == null ? "" : nse.getUrl()); + offer.setOfferType(LDNUtils.getNotifyType(msg.getCoarNotifyType())); + List acks = ldnMessageDao.findAllRelatedMessagesByItem( + context, msg, item, "Accept", "TentativeReject", "TentativeAccept", "Announce"); + if (acks == null || acks.isEmpty()) { + offer.setStatus(NotifyRequestStatusEnum.REQUESTED); + } else if (acks.stream() + .filter(c -> (c.getActivityStreamType().equalsIgnoreCase("TentativeAccept") || + c.getActivityStreamType().equalsIgnoreCase("Accept"))) + .findAny().isPresent()) { + offer.setStatus(NotifyRequestStatusEnum.ACCEPTED); + } else if (acks.stream() + .filter(c -> c.getActivityStreamType().equalsIgnoreCase("TentativeReject")) + .findAny().isPresent()) { + offer.setStatus(NotifyRequestStatusEnum.REJECTED); + } + if (acks.stream().filter( + c -> c.getActivityStreamType().equalsIgnoreCase("Announce")) + .findAny().isEmpty()) { + result.addRequestStatus(offer); + } + } + } + return result; + } + + public void delete(Context context, LDNMessageEntity ldnMessage) throws SQLException { + ldnMessageDao.delete(context, ldnMessage); + } + + @Override + public boolean isTargetCurrent(Notification notification) { + String localInboxUrl = configurationService.getProperty("ldn.notify.inbox"); + return StringUtils.equals(notification.getTarget().getInbox(), localInboxUrl); + } + +} diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/service/impl/NotifyPatternToTriggerImpl.java b/dspace-api/src/main/java/org/dspace/app/ldn/service/impl/NotifyPatternToTriggerImpl.java new file mode 100644 index 000000000000..89ec4abe58d9 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/ldn/service/impl/NotifyPatternToTriggerImpl.java @@ -0,0 +1,62 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.ldn.service.impl; + +import java.sql.SQLException; +import java.util.List; + +import org.dspace.app.ldn.NotifyPatternToTrigger; +import org.dspace.app.ldn.dao.NotifyPatternToTriggerDao; +import org.dspace.app.ldn.service.NotifyPatternToTriggerService; +import org.dspace.content.Item; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Implementation of {@link NotifyPatternToTriggerService}. + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + */ +public class NotifyPatternToTriggerImpl implements NotifyPatternToTriggerService { + + @Autowired(required = true) + private NotifyPatternToTriggerDao notifyPatternToTriggerDao; + + @Override + public List findAll(Context context) throws SQLException { + return notifyPatternToTriggerDao.findAll(context, NotifyPatternToTrigger.class); + } + + @Override + public List findByItem(Context context, Item item) throws SQLException { + return notifyPatternToTriggerDao.findByItem(context, item); + } + + @Override + public List findByItemAndPattern(Context context, Item item, String pattern) + throws SQLException { + return notifyPatternToTriggerDao.findByItemAndPattern(context, item, pattern); + } + + @Override + public NotifyPatternToTrigger create(Context context) throws SQLException { + NotifyPatternToTrigger notifyPatternToTrigger = new NotifyPatternToTrigger(); + return notifyPatternToTriggerDao.create(context, notifyPatternToTrigger); + } + + @Override + public void update(Context context, NotifyPatternToTrigger notifyPatternToTrigger) throws SQLException { + notifyPatternToTriggerDao.save(context, notifyPatternToTrigger); + } + + @Override + public void delete(Context context, NotifyPatternToTrigger notifyPatternToTrigger) throws SQLException { + notifyPatternToTriggerDao.delete(context, notifyPatternToTrigger); + } + +} diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/service/impl/NotifyServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/ldn/service/impl/NotifyServiceImpl.java new file mode 100644 index 000000000000..87be008371aa --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/ldn/service/impl/NotifyServiceImpl.java @@ -0,0 +1,67 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.ldn.service.impl; + +import java.sql.SQLException; +import java.util.List; + +import org.dspace.app.ldn.NotifyServiceEntity; +import org.dspace.app.ldn.dao.NotifyServiceDao; +import org.dspace.app.ldn.service.NotifyService; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Implementation of {@link NotifyService}. + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + */ +public class NotifyServiceImpl implements NotifyService { + + @Autowired(required = true) + private NotifyServiceDao notifyServiceDao; + + @Override + public List findAll(Context context) throws SQLException { + return notifyServiceDao.findAll(context, NotifyServiceEntity.class); + } + + @Override + public NotifyServiceEntity find(Context context, Integer id) throws SQLException { + return notifyServiceDao.findByID(context, NotifyServiceEntity.class, id); + } + + @Override + public NotifyServiceEntity create(Context context, String name) throws SQLException { + NotifyServiceEntity notifyServiceEntity = new NotifyServiceEntity(); + notifyServiceEntity.setName(name); + return notifyServiceDao.create(context, notifyServiceEntity); + } + + @Override + public void update(Context context, NotifyServiceEntity notifyServiceEntity) throws SQLException { + notifyServiceDao.save(context, notifyServiceEntity); + } + + @Override + public void delete(Context context, NotifyServiceEntity notifyServiceEntity) throws SQLException { + notifyServiceDao.delete(context, notifyServiceEntity); + } + + @Override + public NotifyServiceEntity findByLdnUrl(Context context, String ldnUrl) throws SQLException { + return notifyServiceDao.findByLdnUrl(context, ldnUrl); + } + + @Override + public List findManualServicesByInboundPattern(Context context, String pattern) + throws SQLException { + return notifyServiceDao.findManualServicesByInboundPattern(context, pattern); + } + +} diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/service/impl/NotifyServiceInboundPatternServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/ldn/service/impl/NotifyServiceInboundPatternServiceImpl.java new file mode 100644 index 000000000000..c699d9fd0376 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/ldn/service/impl/NotifyServiceInboundPatternServiceImpl.java @@ -0,0 +1,59 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.ldn.service.impl; + +import java.sql.SQLException; +import java.util.List; + +import org.dspace.app.ldn.NotifyServiceEntity; +import org.dspace.app.ldn.NotifyServiceInboundPattern; +import org.dspace.app.ldn.dao.NotifyServiceInboundPatternDao; +import org.dspace.app.ldn.service.NotifyServiceInboundPatternService; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Implementation Service class for the {@link NotifyServiceInboundPatternService}. + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + */ +public class NotifyServiceInboundPatternServiceImpl implements NotifyServiceInboundPatternService { + + @Autowired + private NotifyServiceInboundPatternDao inboundPatternDao; + + @Override + public NotifyServiceInboundPattern findByServiceAndPattern(Context context, + NotifyServiceEntity notifyServiceEntity, + String pattern) throws SQLException { + return inboundPatternDao.findByServiceAndPattern(context, notifyServiceEntity, pattern); + } + + @Override + public List findAutomaticPatterns(Context context) throws SQLException { + return inboundPatternDao.findAutomaticPatterns(context); + } + + @Override + public NotifyServiceInboundPattern create(Context context, NotifyServiceEntity notifyServiceEntity) + throws SQLException { + NotifyServiceInboundPattern inboundPattern = new NotifyServiceInboundPattern(); + inboundPattern.setNotifyService(notifyServiceEntity); + return inboundPatternDao.create(context, inboundPattern); + } + + @Override + public void update(Context context, NotifyServiceInboundPattern inboundPattern) throws SQLException { + inboundPatternDao.save(context, inboundPattern); + } + + @Override + public void delete(Context context, NotifyServiceInboundPattern inboundPattern) throws SQLException { + inboundPatternDao.delete(context, inboundPattern); + } +} diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/utility/LDNUtils.java b/dspace-api/src/main/java/org/dspace/app/ldn/utility/LDNUtils.java new file mode 100644 index 000000000000..949da655bc70 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/ldn/utility/LDNUtils.java @@ -0,0 +1,96 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.ldn.utility; + +import static org.apache.commons.lang3.StringUtils.EMPTY; + +import java.util.UUID; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Some linked data notification utilities. + */ +public class LDNUtils { + + private final static Pattern UUID_REGEX_PATTERN = Pattern.compile( + "\\p{XDigit}{8}-\\p{XDigit}{4}-\\p{XDigit}{4}-\\p{XDigit}{4}-\\p{XDigit}{12}"); + + private final static String SIMPLE_PROTOCOL_REGEX = "^(http[s]?://www\\.|http[s]?://|www\\.)"; + + /** + * + */ + private LDNUtils() { + + } + + /** + * Whether the URL contains an UUID. Used to determine item id from item URL. + * + * @param url item URL + * @return boolean true if URL has UUID, false otherwise + */ + public static boolean hasUUIDInURL(String url) { + Matcher matcher = UUID_REGEX_PATTERN.matcher(url); + + return matcher.find(); + } + + /** + * Extract UUID from URL. + * + * @param url item URL + * @return UUID item id + */ + public static UUID getUUIDFromURL(String url) { + Matcher matcher = UUID_REGEX_PATTERN.matcher(url); + StringBuilder handle = new StringBuilder(); + if (matcher.find()) { + handle.append(matcher.group(0)); + } + return UUID.fromString(handle.toString()); + } + + /** + * Remove http or https protocol from URL. + * + * @param url URL + * @return String URL without protocol + */ + public static String removedProtocol(String url) { + return url.replaceFirst(SIMPLE_PROTOCOL_REGEX, EMPTY); + } + + /** + * Custom context resolver processing. Currently converting DOI URL to DOI id. + * + * @param value context ietf:cite-as + * @return String ietf:cite-as identifier + */ + public static String processContextResolverId(String value) { + String resolverId = value; + resolverId = resolverId.replace("https://doi.org/", "doi:"); + + return resolverId; + } + + /** + * Clear the coarNotifyType from the source code. + * + * @param coarNotifyType coar Notify Type to sanitize + * @return String just the notify type + */ + public static String getNotifyType(String coarNotifyType) { + String justNotifyType = coarNotifyType; + justNotifyType = justNotifyType.substring(justNotifyType.lastIndexOf(":") + 1); + justNotifyType = justNotifyType.replace("Action", ""); + return justNotifyType; + } + +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickThumbnailFilter.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickThumbnailFilter.java index 408982d157e5..8ed67f4df4a0 100644 --- a/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickThumbnailFilter.java +++ b/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickThumbnailFilter.java @@ -148,7 +148,7 @@ public File getImageFile(File f, boolean verbose) // the thumbnail because the CropBox is generally used to define the // area displayed when a user opens the PDF on a screen, whereas the // MediaBox is used for print. Not all PDFs set these correctly, so - // we can use ImageMagick's default behavior unless we see an explit + // we can use ImageMagick's default behavior unless we see an explicit // CropBox. Note: we don't need to do anything special to detect if // the CropBox is missing or empty because pdfbox will set it to the // same size as the MediaBox if it doesn't exist. Also note that we diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterScript.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterScript.java index 5fbbebbb28cc..7f022f38b318 100644 --- a/dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterScript.java +++ b/dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterScript.java @@ -7,6 +7,7 @@ */ package org.dspace.app.mediafilter; +import java.time.LocalDate; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -37,8 +38,9 @@ * MFM: -v verbose outputs all extracted text to STDOUT; -f force forces all * bitstreams to be processed, even if they have been before; -n noindex does not * recreate index after processing bitstreams; -i [identifier] limits processing - * scope to a community, collection or item; and -m [max] limits processing to a - * maximum number of items. + * scope to a community, collection or item; -m [max] limits processing to a + * maximum number of items; -fd [fromdate] takes only items starting from this date, + * filtering by last_modified in the item table. */ public class MediaFilterScript extends DSpaceRunnable { @@ -60,6 +62,7 @@ public class MediaFilterScript extends DSpaceRunnable> filterFormats = new HashMap<>(); + private LocalDate fromDate = null; public MediaFilterScriptConfiguration getScriptConfiguration() { return new DSpace().getServiceManager() @@ -112,6 +115,10 @@ public void setup() throws ParseException { skipIds = commandLine.getOptionValues('s'); } + if (commandLine.hasOption('d')) { + fromDate = LocalDate.parse(commandLine.getOptionValue('d')); + } + } @@ -215,6 +222,10 @@ public void internalRun() throws Exception { mediaFilterService.setSkipList(Arrays.asList(skipIds)); } + if (fromDate != null) { + mediaFilterService.setFromDate(fromDate); + } + Context c = null; try { diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterScriptConfiguration.java index 867e684db86b..c9f61292d617 100644 --- a/dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterScriptConfiguration.java +++ b/dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterScriptConfiguration.java @@ -33,7 +33,8 @@ public Options getOptions() { options.addOption("v", "verbose", false, "print all extracted text and other details to STDOUT"); options.addOption("q", "quiet", false, "do not print anything except in the event of errors."); options.addOption("f", "force", false, "force all bitstreams to be processed"); - options.addOption("i", "identifier", true, "ONLY process bitstreams belonging to identifier"); + options.addOption("i", "identifier", true, + "ONLY process bitstreams belonging to the provided handle identifier"); options.addOption("m", "maximum", true, "process no more than maximum items"); options.addOption("h", "help", false, "help"); @@ -51,6 +52,8 @@ public Options getOptions() { .build(); options.addOption(pluginOption); + options.addOption("d", "fromdate", true, "Process only item from specified last modified date"); + Option skipOption = Option.builder("s") .longOpt("skip") .hasArg() diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterServiceImpl.java index b50fb22355a3..512b8f803b9b 100644 --- a/dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterServiceImpl.java @@ -9,8 +9,11 @@ import java.io.InputStream; import java.sql.SQLException; +import java.time.LocalDate; +import java.time.ZoneId; import java.util.ArrayList; import java.util.Collections; +import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -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 void applyFiltersAllItems(Context context) throws Exception { for (Community topLevelCommunity : topLevelCommunities) { applyFiltersCommunity(context, topLevelCommunity); } + } else if (fromDate != null) { + Iterator itemIterator = + itemService.findByLastModifiedSince( + context, + Date.from(fromDate.atStartOfDay(ZoneId.systemDefault()).toInstant()) + ); + while (itemIterator.hasNext() && processed < max2Process) { + applyFiltersItem(context, itemIterator.next()); + } } else { //otherwise, just find every item and process Iterator itemIterator = itemService.findAll(context); @@ -132,12 +145,18 @@ public void applyFiltersAllItems(Context context) throws Exception { @Override public void applyFiltersCommunity(Context context, Community community) throws Exception { //only apply filters if community not in skip-list + // ensure that the community is attached to the current hibernate session + // as we are committing after each item (handles, sub-communties and + // collections are lazy attributes) + community = context.reloadEntity(community); if (!inSkipList(community.getHandle())) { List subcommunities = community.getSubcommunities(); for (Community subcommunity : subcommunities) { applyFiltersCommunity(context, subcommunity); } - + // ensure that the community is attached to the current hibernate session + // as we are committing after each item + community = context.reloadEntity(community); List collections = community.getCollections(); for (Collection collection : collections) { applyFiltersCollection(context, collection); @@ -148,6 +167,9 @@ public void applyFiltersCommunity(Context context, Community community) @Override public void applyFiltersCollection(Context context, Collection collection) throws Exception { + // ensure that the collection is attached to the current hibernate session + // as we are committing after each item (handles are lazy attributes) + collection = context.reloadEntity(collection); //only apply filters if collection not in skip-list if (!inSkipList(collection.getHandle())) { Iterator itemIterator = itemService.findAllByCollection(context, collection); @@ -171,6 +193,8 @@ public void applyFiltersItem(Context c, Item item) throws Exception { } // clear item objects from context cache and internal cache c.uncacheEntity(currentItem); + // commit after each item to release DB resources + c.commit(); currentItem = null; } } @@ -577,4 +601,9 @@ public void setFilterFormats(Map> filterFormats) { public void setLogHandler(DSpaceRunnableHandler handler) { this.handler = handler; } + + @Override + public void setFromDate(LocalDate fromDate) { + this.fromDate = fromDate; + } } diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/TikaTextExtractionFilter.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/TikaTextExtractionFilter.java index e83bf706ed02..17e7b85e9bfc 100644 --- a/dspace-api/src/main/java/org/dspace/app/mediafilter/TikaTextExtractionFilter.java +++ b/dspace-api/src/main/java/org/dspace/app/mediafilter/TikaTextExtractionFilter.java @@ -18,6 +18,7 @@ import org.apache.commons.lang.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.apache.poi.util.IOUtils; import org.apache.tika.Tika; import org.apache.tika.exception.TikaException; import org.apache.tika.metadata.Metadata; @@ -72,21 +73,23 @@ public InputStream getDestinationStream(Item currentItem, InputStream source, bo // Not using temporary file. We'll use Tika's default in-memory parsing. // Get maximum characters to extract. Default is 100,000 chars, which is also Tika's default setting. String extractedText; - int maxChars = configurationService.getIntProperty("textextractor.max-chars", 100000); + int maxChars = configurationService.getIntProperty("textextractor.max-chars", 100_000); try { // Use Tika to extract text from input. Tika will automatically detect the file type. Tika tika = new Tika(); tika.setMaxStringLength(maxChars); // Tell Tika the maximum number of characters to extract + IOUtils.setByteArrayMaxOverride( + configurationService.getIntProperty("textextractor.max-array", 100_000_000)); extractedText = tika.parseToString(source); } catch (IOException e) { System.err.format("Unable to extract text from bitstream in Item %s%n", currentItem.getID().toString()); - e.printStackTrace(); + e.printStackTrace(System.err); log.error("Unable to extract text from bitstream in Item {}", currentItem.getID().toString(), e); throw e; } catch (OutOfMemoryError oe) { System.err.format("OutOfMemoryError occurred when extracting text from bitstream in Item %s. " + "You may wish to enable 'textextractor.use-temp-file'.%n", currentItem.getID().toString()); - oe.printStackTrace(); + oe.printStackTrace(System.err); log.error("OutOfMemoryError occurred when extracting text from bitstream in Item {}. " + "You may wish to enable 'textextractor.use-temp-file'.", currentItem.getID().toString(), oe); throw oe; diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/service/MediaFilterService.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/service/MediaFilterService.java index bc92ff521098..30e6dba42f08 100644 --- a/dspace-api/src/main/java/org/dspace/app/mediafilter/service/MediaFilterService.java +++ b/dspace-api/src/main/java/org/dspace/app/mediafilter/service/MediaFilterService.java @@ -8,6 +8,7 @@ package org.dspace.app.mediafilter.service; import java.sql.SQLException; +import java.time.LocalDate; import java.util.List; import java.util.Map; @@ -149,4 +150,6 @@ public void updatePoliciesOfDerivativeBitstreams(Context context, Item item, Bit * @param handler */ public void setLogHandler(DSpaceRunnableHandler handler); + + public void setFromDate(LocalDate fromDate); } diff --git a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItem.java b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItem.java index cdefd1298c6e..bf2bfb4d60b2 100644 --- a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItem.java +++ b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItem.java @@ -8,19 +8,19 @@ package org.dspace.app.requestitem; import java.util.Date; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.JoinColumn; -import javax.persistence.ManyToOne; -import javax.persistence.SequenceGenerator; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.SequenceGenerator; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import org.dspace.content.Bitstream; import org.dspace.content.Item; import org.dspace.core.Context; diff --git a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemEmailNotifier.java b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemEmailNotifier.java index 6499c45a7830..c489fb4b3ff0 100644 --- a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemEmailNotifier.java +++ b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemEmailNotifier.java @@ -11,11 +11,11 @@ import java.io.IOException; import java.sql.SQLException; import java.util.List; -import javax.annotation.ManagedBean; -import javax.inject.Inject; -import javax.inject.Singleton; -import javax.mail.MessagingException; +import jakarta.annotation.ManagedBean; +import jakarta.inject.Inject; +import jakarta.inject.Singleton; +import jakarta.mail.MessagingException; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.app.requestitem.service.RequestItemService; diff --git a/dspace-api/src/main/java/org/dspace/app/requestitem/dao/impl/RequestItemDAOImpl.java b/dspace-api/src/main/java/org/dspace/app/requestitem/dao/impl/RequestItemDAOImpl.java index 008174ded88c..c76bd50d1910 100644 --- a/dspace-api/src/main/java/org/dspace/app/requestitem/dao/impl/RequestItemDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/app/requestitem/dao/impl/RequestItemDAOImpl.java @@ -9,11 +9,11 @@ import java.sql.SQLException; import java.util.Iterator; -import javax.persistence.Query; -import javax.persistence.criteria.CriteriaBuilder; -import javax.persistence.criteria.CriteriaQuery; -import javax.persistence.criteria.Root; +import jakarta.persistence.Query; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Root; import org.dspace.app.requestitem.RequestItem; import org.dspace.app.requestitem.RequestItem_; import org.dspace.app.requestitem.dao.RequestItemDAO; @@ -44,8 +44,12 @@ public RequestItem findByToken(Context context, String token) throws SQLExceptio } @Override public Iterator findByItem(Context context, Item item) throws SQLException { - Query query = createQuery(context, "FROM RequestItem WHERE item_id= :uuid"); - query.setParameter("uuid", item.getID()); + CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); + CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, RequestItem.class); + Root requestItemRoot = criteriaQuery.from(RequestItem.class); + criteriaQuery.select(requestItemRoot); + criteriaQuery.where(criteriaBuilder.equal(requestItemRoot.get(RequestItem_.item), item)); + Query query = createQuery(context, criteriaQuery); return iterate(query); } } diff --git a/dspace-api/src/main/java/org/dspace/app/sherpa/SHERPAService.java b/dspace-api/src/main/java/org/dspace/app/sherpa/SHERPAService.java index ead725e842c4..943df5f2a0e8 100644 --- a/dspace-api/src/main/java/org/dspace/app/sherpa/SHERPAService.java +++ b/dspace-api/src/main/java/org/dspace/app/sherpa/SHERPAService.java @@ -12,8 +12,8 @@ import java.net.URI; import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpEntity; @@ -78,7 +78,7 @@ public SHERPAService() { @SuppressWarnings("unused") @PostConstruct private void init() { - // Get endoint and API key from configuration + // Get endpoint and API key from configuration endpoint = configurationService.getProperty("sherpa.romeo.url", "https://v2.sherpa.ac.uk/cgi/retrieve"); apiKey = configurationService.getProperty("sherpa.romeo.apikey"); @@ -93,7 +93,7 @@ private void init() { * @param query ISSN string to pass in an "issn equals" API query * @return SHERPAResponse containing an error or journal policies */ - @Cacheable(key = "#query", cacheNames = "sherpa.searchByJournalISSN") + @Cacheable(key = "#query", condition = "#query != null", cacheNames = "sherpa.searchByJournalISSN") public SHERPAResponse searchByJournalISSN(String query) { return performRequest("publication", "issn", "equals", query, 0, 1); } @@ -156,7 +156,7 @@ public SHERPAPublisherResponse performPublisherRequest(String type, String field // If the response body is valid, pass to SHERPAResponse for parsing as JSON if (null != responseBody) { - log.debug("Non-null SHERPA resonse received for query of " + value); + log.debug("Non-null SHERPA response received for query of " + value); InputStream content = null; try { content = responseBody.getContent(); @@ -259,7 +259,7 @@ public SHERPAResponse performRequest(String type, String field, String predicate // If the response body is valid, pass to SHERPAResponse for parsing as JSON if (null != responseBody) { - log.debug("Non-null SHERPA resonse received for query of " + value); + log.debug("Non-null SHERPA response received for query of " + value); InputStream content = null; try { content = responseBody.getContent(); diff --git a/dspace-api/src/main/java/org/dspace/app/sherpa/cache/SherpaCacheLogger.java b/dspace-api/src/main/java/org/dspace/app/sherpa/cache/SherpaCacheLogger.java index e84fb7775ae2..5bdf1efa2632 100644 --- a/dspace-api/src/main/java/org/dspace/app/sherpa/cache/SherpaCacheLogger.java +++ b/dspace-api/src/main/java/org/dspace/app/sherpa/cache/SherpaCacheLogger.java @@ -13,7 +13,7 @@ import org.ehcache.event.CacheEventListener; /** - * This is a EHCache listner responsible for logging sherpa cache events. It is + * This is a EHCache listener responsible for logging sherpa cache events. It is * bound to the sherpa cache via the dspace/config/ehcache.xml file. We need a * dedicated Logger for each cache as the CacheEvent doesn't include details * about where the event occur diff --git a/dspace-api/src/main/java/org/dspace/app/sherpa/submit/SHERPASubmitService.java b/dspace-api/src/main/java/org/dspace/app/sherpa/submit/SHERPASubmitService.java index b795c8a2b2d2..cb913a9f261e 100644 --- a/dspace-api/src/main/java/org/dspace/app/sherpa/submit/SHERPASubmitService.java +++ b/dspace-api/src/main/java/org/dspace/app/sherpa/submit/SHERPASubmitService.java @@ -47,7 +47,7 @@ public void setConfiguration(SHERPASubmitConfigurationService configuration) { } /** - * Setter for SHERPA service, reponsible for actual HTTP API calls + * Setter for SHERPA service, responsible for actual HTTP API calls * @see "dspace-dspace-addon-sherpa-configuration-services.xml" * @param sherpaService */ diff --git a/dspace-api/src/main/java/org/dspace/app/sitemap/GenerateSitemaps.java b/dspace-api/src/main/java/org/dspace/app/sitemap/GenerateSitemaps.java index 90962d12aa75..2464221d2df2 100644 --- a/dspace-api/src/main/java/org/dspace/app/sitemap/GenerateSitemaps.java +++ b/dspace-api/src/main/java/org/dspace/app/sitemap/GenerateSitemaps.java @@ -141,7 +141,7 @@ public static void generateSitemapsScheduled() throws IOException, SQLException public static void deleteSitemaps() throws IOException { File outputDir = new File(configurationService.getProperty("sitemap.dir")); if (!outputDir.exists() && !outputDir.isDirectory()) { - log.error("Unable to delete sitemaps directory, doesn't exist or isn't a directort"); + log.error("Unable to delete sitemaps directory, doesn't exist or isn't a directory"); } else { FileUtils.deleteDirectory(outputDir); } diff --git a/dspace-api/src/main/java/org/dspace/app/solrdatabaseresync/SolrDatabaseResyncCli.java b/dspace-api/src/main/java/org/dspace/app/solrdatabaseresync/SolrDatabaseResyncCli.java index f901c9ca569e..aac42ce1acf9 100644 --- a/dspace-api/src/main/java/org/dspace/app/solrdatabaseresync/SolrDatabaseResyncCli.java +++ b/dspace-api/src/main/java/org/dspace/app/solrdatabaseresync/SolrDatabaseResyncCli.java @@ -105,35 +105,17 @@ private void performStatusUpdate(Context context) throws SearchServiceException, solrQuery.addFilterQuery(dateRangeFilter); solrQuery.addField(SearchUtils.RESOURCE_ID_FIELD); solrQuery.addField(SearchUtils.RESOURCE_UNIQUE_ID); + solrQuery.setRows(0); QueryResponse response = solrSearchCore.getSolr().query(solrQuery, solrSearchCore.REQUEST_METHOD); - - if (response != null) { - logInfoAndOut(response.getResults().size() + " items found to process"); - - for (SolrDocument doc : response.getResults()) { - String uuid = (String) doc.getFirstValue(SearchUtils.RESOURCE_ID_FIELD); - String uniqueId = (String) doc.getFirstValue(SearchUtils.RESOURCE_UNIQUE_ID); - logDebugAndOut("Processing item with UUID: " + uuid); - - Optional indexableObject = Optional.empty(); - try { - indexableObject = indexObjectServiceFactory - .getIndexableObjectFactory(uniqueId).findIndexableObject(context, uuid); - } catch (SQLException e) { - log.warn("An exception occurred when attempting to retrieve item with UUID \"" + uuid + - "\" from the database, removing related solr document", e); - } - - try { - if (indexableObject.isPresent()) { - logDebugAndOut("Item exists in DB, updating solr document"); - updateItem(context, indexableObject.get()); - } else { - logDebugAndOut("Item doesn't exist in DB, removing solr document"); - removeItem(context, uniqueId); - } - } catch (SQLException | IOException e) { - log.error(e.getMessage(), e); + if (response != null && response.getResults() != null) { + long nrOfPreDBResults = response.getResults().getNumFound(); + if (nrOfPreDBResults > 0) { + logInfoAndOut(nrOfPreDBResults + " items found to process"); + int batchSize = configurationService.getIntProperty("script.solr-database-resync.batch-size", 100); + for (int start = 0; start < nrOfPreDBResults; start += batchSize) { + solrQuery.setStart(start); + solrQuery.setRows(batchSize); + performStatusUpdateOnNextBatch(context, solrQuery); } } } @@ -141,6 +123,38 @@ private void performStatusUpdate(Context context) throws SearchServiceException, indexingService.commit(); } + private void performStatusUpdateOnNextBatch(Context context, SolrQuery solrQuery) + throws SolrServerException, IOException { + QueryResponse response = solrSearchCore.getSolr().query(solrQuery, solrSearchCore.REQUEST_METHOD); + + for (SolrDocument doc : response.getResults()) { + String uuid = (String) doc.getFirstValue(SearchUtils.RESOURCE_ID_FIELD); + String uniqueId = (String) doc.getFirstValue(SearchUtils.RESOURCE_UNIQUE_ID); + logDebugAndOut("Processing item with UUID: " + uuid); + + Optional indexableObject = Optional.empty(); + try { + indexableObject = indexObjectServiceFactory + .getIndexableObjectFactory(uniqueId).findIndexableObject(context, uuid); + } catch (SQLException e) { + log.warn("An exception occurred when attempting to retrieve item with UUID \"" + uuid + + "\" from the database, removing related solr document", e); + } + + try { + if (indexableObject.isPresent()) { + logDebugAndOut("Item exists in DB, updating solr document"); + updateItem(context, indexableObject.get()); + } else { + logDebugAndOut("Item doesn't exist in DB, removing solr document"); + removeItem(context, uniqueId); + } + } catch (SQLException | IOException e) { + log.error(e.getMessage(), e); + } + } + } + private void updateItem(Context context, IndexableObject indexableObject) throws SolrServerException, IOException { Map fieldModifier = new HashMap<>(1); fieldModifier.put("remove", STATUS_FIELD_PREDB); diff --git a/dspace-api/src/main/java/org/dspace/app/statistics/HTMLReport.java b/dspace-api/src/main/java/org/dspace/app/statistics/HTMLReport.java index 3d76ecaecfdc..9e27ffdd9334 100644 --- a/dspace-api/src/main/java/org/dspace/app/statistics/HTMLReport.java +++ b/dspace-api/src/main/java/org/dspace/app/statistics/HTMLReport.java @@ -451,7 +451,7 @@ public String footer() { } /** - * Clean Stirngs for display in HTML + * Clean Strings for display in HTML * * @param s The String to clean * @return The cleaned String diff --git a/dspace-api/src/main/java/org/dspace/app/statistics/LogAnalyser.java b/dspace-api/src/main/java/org/dspace/app/statistics/LogAnalyser.java index 2e4ed69b268e..982c339963d2 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 static String processLogs(Context context, String myLogDir, // of the log file are sequential, but can we assume the files are // provided in a data sequence? for (i = 0; i < logFiles.length; i++) { - // check to see if this file is a log file agains the global regex + // check to see if this file is a log file against the global regex Matcher matchRegex = logRegex.matcher(logFiles[i].getName()); if (matchRegex.matches()) { // if it is a log file, open it up and lets have a look at the diff --git a/dspace-api/src/main/java/org/dspace/app/statistics/ReportGenerator.java b/dspace-api/src/main/java/org/dspace/app/statistics/ReportGenerator.java index c5fe0072f514..5b526773d48a 100644 --- a/dspace-api/src/main/java/org/dspace/app/statistics/ReportGenerator.java +++ b/dspace-api/src/main/java/org/dspace/app/statistics/ReportGenerator.java @@ -352,7 +352,7 @@ public static void processReport(Context context, Report report, report.setEndDate(endDate); report.setMainTitle(name, serverName); - // define our standard variables for re-use + // define our standard variables for reuse // FIXME: we probably don't need these once we've finished re-factoring Iterator keys = null; int i = 0; @@ -518,7 +518,7 @@ public static void processReport(Context context, Report report, /** * a standard stats block preparation method for use when an aggregator - * has to be put out in its entirity. This method will not be able to + * has to be put out in its entirety. This method will not be able to * deal with complex cases, although it will perform sorting by value and * translations as per the map file if requested * @@ -783,7 +783,7 @@ public static String getItemInfo(Context context, String handle) return null; } - // build the referece + // build the reference // FIXME: here we have blurred the line between content and presentation // and it should probably be un-blurred List title = itemService.getMetadata(item, MetadataSchemaEnum.DC.getName(), diff --git a/dspace-api/src/main/java/org/dspace/app/statistics/StatisticsLoader.java b/dspace-api/src/main/java/org/dspace/app/statistics/StatisticsLoader.java index cc8a7024f1b2..23dbe19b61a6 100644 --- a/dspace-api/src/main/java/org/dspace/app/statistics/StatisticsLoader.java +++ b/dspace-api/src/main/java/org/dspace/app/statistics/StatisticsLoader.java @@ -291,7 +291,7 @@ private static synchronized void loadFileList(File[] fileList) { * by the formatter provided, then we return null. * * @param thisFile file - * @param thisPattern patter + * @param thisPattern pattern * @param sdf date format * @return StatsFile */ diff --git a/dspace-api/src/main/java/org/dspace/app/statistics/package.html b/dspace-api/src/main/java/org/dspace/app/statistics/package.html index a6d8d8699cf7..931a7039080d 100644 --- a/dspace-api/src/main/java/org/dspace/app/statistics/package.html +++ b/dspace-api/src/main/java/org/dspace/app/statistics/package.html @@ -46,8 +46,6 @@
writes event records to the Java logger.
{@link org.dspace.statistics.SolrLoggerUsageEventListener SolrLoggerUsageEventListener}
writes event records to Solr.
-
{@link org.dspace.google.GoogleRecorderEventListener GoogleRecorderEventListener}<.dt> -
writes event records to Google Analytics.
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 index 6281b6910701..3c2ad71846a6 100644 --- a/dspace-api/src/main/java/org/dspace/app/suggestion/SolrSuggestionStorageServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/app/suggestion/SolrSuggestionStorageServiceImpl.java @@ -56,6 +56,7 @@ * */ public class SolrSuggestionStorageServiceImpl implements SolrSuggestionStorageService { + private static final Logger log = LogManager.getLogger(SolrSuggestionStorageServiceImpl.class); protected SolrClient solrSuggestionClient; 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 index 41d33026ed0f..e8a883026174 100644 --- a/dspace-api/src/main/java/org/dspace/app/suggestion/SuggestionService.java +++ b/dspace-api/src/main/java/org/dspace/app/suggestion/SuggestionService.java @@ -22,7 +22,7 @@ public interface SuggestionService { /** find a {@link SuggestionTarget } by source name and suggestion id */ public SuggestionTarget find(Context context, String source, UUID id); - /** count all suggetion targets by suggestion source */ + /** count all suggestion targets by suggestion source */ public long countAll(Context context, String source); /** find all suggestion targets by source (paged) */ 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 index 66773fbc128d..57fe42806b12 100644 --- a/dspace-api/src/main/java/org/dspace/app/suggestion/SuggestionServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/app/suggestion/SuggestionServiceImpl.java @@ -13,8 +13,8 @@ import java.util.Map; import java.util.UUID; import java.util.stream.Collectors; -import javax.annotation.Resource; +import jakarta.annotation.Resource; import org.apache.logging.log4j.Logger; import org.dspace.core.Context; import org.springframework.stereotype.Service; 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 index a408c448e9f5..76e8213cd7d2 100644 --- 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 @@ -12,6 +12,8 @@ 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; @@ -23,8 +25,6 @@ import org.dspace.scripts.DSpaceRunnable; import org.dspace.sort.SortOption; import org.dspace.utils.DSpace; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * Runner responsible to import metadata about authors from OpenAIRE to Solr. @@ -33,13 +33,13 @@ * 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 = LoggerFactory.getLogger(PublicationLoaderRunnable.class); + private static final Logger LOGGER = LogManager.getLogger(); private PublicationLoader oairePublicationLoader = null; @@ -63,9 +63,9 @@ public void setup() throws ParseException { profile = commandLine.getOptionValue("s"); if (profile == null) { - LOGGER.info("No argument for -s, process all profile"); + LOGGER.info("No argument for -s, process all profiles"); } else { - LOGGER.info("Process eperson item with UUID " + profile); + LOGGER.info("Process eperson item with UUID {}", profile); } } @@ -87,7 +87,7 @@ public void internalRun() throws Exception { * 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 @@ -96,10 +96,10 @@ public void internalRun() throws Exception { private Iterator getResearchers(String profileUUID) { SearchService searchService = new DSpace().getSingletonService(SearchService.class); DiscoverQueryBuilder queryBuilder = SearchUtils.getQueryBuilder(); - List filters = new ArrayList(); + List filters = new ArrayList<>(); String query = "*:*"; if (profileUUID != null) { - query = "search.resourceid:" + profileUUID.toString(); + query = "search.resourceid:" + profileUUID; } try { DiscoverQuery discoverQuery = queryBuilder.buildQuery(context, null, 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 c0cb5d226c1f..48df3dbc12d5 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.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 @@ */ 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 b498b693956a..ed59f4b24f05 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 @@ 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,7 +624,7 @@ public static boolean authorizeNewAccountRegistration(Context context, HttpServl 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 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 11f9aadd869b..dd88390cb856 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.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 @@ */ 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 DCInput(Map fieldMap, Map> listMap) } //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 DCInput(Map fieldMap, Map> listMap) || "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 boolean isClosedVocabulary() { * @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 b0289ec4a4e1..51d78ccaba5c 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.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 c2817169b21e..32c4ff9c1c71 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 7bdaa95b5c02..86349547959e 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 static void main(String[] argv) // 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 0a072a9819eb..e8c5d93181e9 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 static void main(String[] argv) throws SQLException, AuthorizeException, 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/OpenSearchServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/util/OpenSearchServiceImpl.java index 514143c93ea0..2800ae6d10a2 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/OpenSearchServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/app/util/OpenSearchServiceImpl.java @@ -14,7 +14,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.Map; import com.rometools.modules.opensearch.OpenSearchModule; import com.rometools.modules.opensearch.entity.OSQuery; @@ -58,12 +57,12 @@ public class OpenSearchServiceImpl implements OpenSearchService { private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(OpenSearchServiceImpl.class); // Namespaces used - protected final String osNs = "http://a9.com/-/spec/opensearch/1.1/"; + protected final static String osNs = "http://a9.com/-/spec/opensearch/1.1/"; - @Autowired(required = true) + @Autowired protected ConfigurationService configurationService; - @Autowired(required = true) + @Autowired protected HandleService handleService; protected OpenSearchServiceImpl() { @@ -119,11 +118,10 @@ public String getDescription(String scope) { @Override public String getResultsString(Context context, String format, String query, int totalResults, int start, - int pageSize, - IndexableObject scope, List results, - Map labels) throws IOException { + int pageSize, IndexableObject scope, List results) + throws IOException { try { - return getResults(context, format, query, totalResults, start, pageSize, scope, results, labels) + return getResults(context, format, query, totalResults, start, pageSize, scope, results) .outputString(); } catch (FeedException e) { log.error(e.toString(), e); @@ -133,11 +131,10 @@ public String getResultsString(Context context, String format, String query, int @Override public Document getResultsDoc(Context context, String format, String query, int totalResults, int start, - int pageSize, - IndexableObject scope, List results, Map labels) + int pageSize, IndexableObject scope, List results) throws IOException { try { - return getResults(context, format, query, totalResults, start, pageSize, scope, results, labels) + return getResults(context, format, query, totalResults, start, pageSize, scope, results) .outputW3CDom(); } catch (FeedException e) { log.error(e.toString(), e); @@ -146,8 +143,7 @@ public Document getResultsDoc(Context context, String format, String query, int } protected SyndicationFeed getResults(Context context, String format, String query, int totalResults, int start, - int pageSize, IndexableObject scope, - List results, Map labels) { + int pageSize, IndexableObject scope, List results) { // Encode results in requested format if ("rss".equals(format)) { format = "rss_2.0"; @@ -155,8 +151,8 @@ protected SyndicationFeed getResults(Context context, String format, String quer format = "atom_1.0"; } - SyndicationFeed feed = new SyndicationFeed(labels.get(SyndicationFeed.MSG_UITYPE)); - feed.populate(null, context, scope, results, labels); + SyndicationFeed feed = new SyndicationFeed(); + feed.populate(null, context, scope, results); feed.setType(format); feed.addModule(openSearchMarkup(query, totalResults, start, pageSize)); return feed; 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 578e57fb0909..17d94027af08 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 1c91b40b9735..0f8f7456801f 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 @@ -24,10 +24,10 @@ 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.CollectionService; import org.dspace.core.Context; -import org.dspace.discovery.SearchServiceException; import org.dspace.handle.factory.HandleServiceFactory; import org.dspace.services.factory.DSpaceServicesFactory; import org.w3c.dom.Document; @@ -99,6 +99,13 @@ public class SubmissionConfigReader { */ private Map communityToSubmissionConfig = null; + /** + * Hashmap which stores which submission process configuration is used by + * which entityType, computed from the item submission config file + * (specifically, the 'submission-map' tag) + */ + private Map entityTypeToSubmissionConfig = null; + /** * Reference to the global submission step definitions defined in the * "step-definitions" section @@ -137,6 +144,7 @@ public SubmissionConfigReader() throws SubmissionConfigReaderException { public void reload() throws SubmissionConfigReaderException { collectionToSubmissionConfig = null; communityToSubmissionConfig = null; + entityTypeToSubmissionConfig = null; stepDefns = null; submitDefns = null; buildInputs(configDir + SUBMIT_DEF_FILE_PREFIX + SUBMIT_DEF_FILE_SUFFIX); @@ -150,12 +158,13 @@ public void reload() throws SubmissionConfigReaderException { *
  • 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>>(); String uri = "file:" + new File(fileName).getAbsolutePath(); @@ -173,9 +182,6 @@ private void buildInputs(String fileName) throws SubmissionConfigReaderException } catch (FactoryConfigurationError fe) { throw new SubmissionConfigReaderException( "Cannot create Item Submission Configuration parser", fe); - } catch (SearchServiceException se) { - throw new SubmissionConfigReaderException( - "Cannot perform a discovery search for Item Submission Configuration", se); } catch (Exception e) { throw new SubmissionConfigReaderException( "Error creating Item Submission Configuration: " + e); @@ -238,6 +244,16 @@ public SubmissionConfig getSubmissionConfigByCollection(Collection col) { 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(); @@ -342,7 +358,7 @@ public SubmissionStepConfig getStepConfig(String stepID) 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) { @@ -358,7 +374,7 @@ public SubmissionStepConfig getStepConfig(String stepID) * should correspond to the collection-form maps, the form definitions, and * the display/storage word pairs. */ - private void doNodes(Node n) throws SAXException, SearchServiceException, SubmissionConfigReaderException { + private void doNodes(Node n) throws SAXException, SubmissionConfigReaderException { if (n == null) { return; } @@ -405,9 +421,7 @@ private void doNodes(Node n) throws SAXException, SearchServiceException, Submis * the collection handle and item submission name, put name in hashmap keyed * by the collection handle. */ - private void processMap(Node e) throws SAXException, SearchServiceException { - // create a context - Context context = new Context(); + private void processMap(Node e) throws SAXException { NodeList nl = e.getChildNodes(); int len = nl.getLength(); @@ -428,7 +442,7 @@ private void processMap(Node e) throws SAXException, SearchServiceException { throw new SAXException( "name-map element is missing submission-name attribute in 'item-submission.xml'"); } - if (content != null && content.length() > 0) { + if (content != null && !content.isEmpty()) { throw new SAXException( "name-map element has content in 'item-submission.xml', it should be empty."); } @@ -437,12 +451,7 @@ private void processMap(Node e) throws SAXException, SearchServiceException { } else if (communityId != null) { communityToSubmissionConfig.put(communityId, value); } else { - // get all collections for this entity-type - List collections = collectionService.findAllCollectionsByEntityType( context, - entityType); - for (Collection collection : collections) { - collectionToSubmissionConfig.putIfAbsent(collection.getHandle(), value); - } + entityTypeToSubmissionConfig.put(entityType, value); } } // ignore any child node that isn't a "name-map" } 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 28d39d911b95..db45d42e495e 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 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 c1402499c444..5d64009727c6 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 @@ -11,9 +11,9 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.Date; +import java.util.HashMap; 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 +35,7 @@ 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; @@ -84,11 +85,6 @@ public class SyndicationFeed { public static final String MSG_FEED_TITLE = "feed.title"; public static final String MSG_FEED_DESCRIPTION = "general-feed.description"; public static final String MSG_METADATA = "metadata."; - public static final String MSG_UITYPE = "ui.type"; - - // UI keywords - public static final String UITYPE_XMLUI = "xmlui"; - public static final String UITYPE_JSPUI = "jspui"; // default DC fields for entry protected String defaultTitleField = "dc.title"; @@ -140,45 +136,23 @@ public class SyndicationFeed { protected String[] podcastableMIMETypes = configurationService.getArrayProperty("webui.feed.podcast.mimetypes", new String[] {"audio/x-mpeg"}); - // -------- Instance variables: - // the feed object we are building protected SyndFeed feed = null; - // memory of UI that called us, "xmlui" or "jspui" - // affects Bitstream retrieval URL and I18N keys - protected String uiType = null; - protected HttpServletRequest request = null; protected CollectionService collectionService; protected CommunityService communityService; protected ItemService itemService; - /** - * Constructor. - * - * @param ui either "xmlui" or "jspui" - */ - public SyndicationFeed(String ui) { + public SyndicationFeed() { feed = new SyndFeedImpl(); - uiType = ui; ContentServiceFactory contentServiceFactory = ContentServiceFactory.getInstance(); itemService = contentServiceFactory.getItemService(); collectionService = contentServiceFactory.getCollectionService(); communityService = contentServiceFactory.getCommunityService(); } - /** - * Returns list of metadata selectors used to compose the description element - * - * @return selector list - format 'schema.element[.qualifier]' - */ - public static String[] getDescriptionSelectors() { - return (String[]) ArrayUtils.clone(descriptionFields); - } - - /** * Fills in the feed and entry-level metadata from DSpace objects. * @@ -186,15 +160,17 @@ public static String[] getDescriptionSelectors() { * @param context context * @param dso the scope * @param items array of objects - * @param labels label map */ public void populate(HttpServletRequest request, Context context, IndexableObject dso, - List items, Map labels) { + List items) { String logoURL = null; String objectURL = null; String defaultTitle = null; boolean podcastFeed = false; this.request = request; + + Map labels = getLabels(); + // dso is null for the whole site, or a search without scope if (dso == null) { defaultTitle = configurationService.getProperty("dspace.name"); @@ -518,8 +494,7 @@ protected static String getDefaultedConfiguration(String key, String dfl) { protected String urlOfBitstream(HttpServletRequest request, Bitstream logo) { String name = logo.getName(); return resolveURL(request, null) + - (uiType.equalsIgnoreCase(UITYPE_XMLUI) ? "/bitstream/id/" : "/retrieve/") + - logo.getID() + "/" + (name == null ? "" : name); + "/bitstreams/" + logo.getID() + "/download"; } protected String baseURL = null; // cache the result for null @@ -536,7 +511,7 @@ protected String urlOfBitstream(HttpServletRequest request, Bitstream logo) { */ 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) { @@ -566,5 +541,19 @@ protected String getOneDC(Item item, String field) { List dcv = itemService.getMetadataByMetadataString(item, field); return (dcv.size() > 0) ? dcv.get(0).getValue() : null; } -} + /** + * Internal method to get labels for the returned document + */ + private Map getLabels() { + // TODO: get strings from translation file or configuration + Map labelMap = new HashMap<>(); + labelMap.put(SyndicationFeed.MSG_UNTITLED, "notitle"); + labelMap.put(SyndicationFeed.MSG_LOGO_TITLE, "logo.title"); + labelMap.put(SyndicationFeed.MSG_FEED_DESCRIPTION, "general-feed.description"); + for (String selector : descriptionFields) { + labelMap.put("metadata." + selector, selector); + } + return labelMap; + } +} 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 3bc828d6c496..3a0c368880e4 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.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 static String encodeBitstreamName(String stringIn) throws java.io.Unsuppo * @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 static UUID getUUIDParameter(HttpServletRequest request, String param) { } 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 2f42c1459f63..c2fc133f41c6 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 03f41e535c53..78b208faa2bc 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 @@ -10,7 +10,6 @@ import java.io.IOException; import java.sql.SQLException; import java.util.List; -import java.util.Map; import org.dspace.content.DSpaceObject; import org.dspace.core.Context; @@ -85,15 +84,13 @@ 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 labels labels to apply - format specific + * @param results the retrieved DSpace objects satisfying search * @return formatted search results * @throws IOException if IO error */ public String getResultsString(Context context, String format, String query, int totalResults, int start, - int pageSize, - IndexableObject scope, List results, - Map labels) throws IOException; + int pageSize, IndexableObject scope, List results) + throws IOException; /** * Returns a formatted set of search results as a document @@ -105,14 +102,12 @@ public String getResultsString(Context context, String format, String query, int * @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 labels labels to apply - format specific + * @param results the retrieved DSpace objects satisfying search * @return formatted search results * @throws IOException if IO error */ public Document getResultsDoc(Context context, String format, String query, int totalResults, int start, - int pageSize, - IndexableObject scope, List results, Map labels) + int pageSize, IndexableObject scope, List results) throws IOException; public DSpaceObject resolveScope(Context context, String scope) throws SQLException; 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 500ee04a979b..d316cb636f87 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 @@ 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 1d67da37ecb3..2b07f73c489c 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.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.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 @@ * 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 @@ protected int authenticateInternal(Context context, 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 0c2be211a532..db71ec1c2ff1 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.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 585eaf9cd8b1..40b8f48078c9 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.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.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 @@ * @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 boolean allowSetPassword(Context context, return false; } - /* + /** * This is an explicit method. */ @Override @@ -138,7 +135,7 @@ public boolean isImplicit() { return false; } - /* + /** * Add authenticated users to the group defined in dspace.cfg by * the login.specialgroup key. */ @@ -177,7 +174,7 @@ public List getSpecialGroups(Context context, HttpServletRequest request) 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 List getSpecialGroups(Context context, HttpServletRequest request) * @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 int authenticate(Context context, } // 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 int authenticate(Context context, 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 int authenticate(Context context, 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 int authenticate(Context context, 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 int authenticate(Context context, 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 @@ protected String getDNOfUser(String adminUser, String adminPassword, Context con } 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 @@ protected String getDNOfUser(String adminUser, String adminPassword, Context con 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 @@ protected boolean ldapAuthenticate(String netid, String password, 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 @@ protected boolean ldapAuthenticate(String netid, String password, } } - /* + /** * 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 String getName() { 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 5d4635d48ef5..c7ac1ff55748 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 @@ 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 8a4ac190c816..44c9fb7dc872 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.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.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 String loginPageURL(Context context, HttpServletRequest request, HttpServ 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 String loginPageURL(Context context, HttpServletRequest request, HttpServ 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 @@ private OidcTokenResponseDTO getOidcAccessToken(String code) { 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 @@ private Map getOidcUserInfo(String accessToken) { 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 3e9ff6638a61..932d963307f6 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 @@ 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 a11bbfc867b4..ee30338a8f2e 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.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.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 @@ private Person getPersonFromOrcid(OrcidTokenResponseDTO token) { 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 @@ private OrcidTokenResponseDTO getOrcidAccessToken(String code) { 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 6d1ca862d307..f66d0730e918 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.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 boolean canSelfRegister(Context context, // 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 791634a7dc25..2f117e6944a0 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.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 int authenticate(Context context, String username, String password, @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 List getSpecialGroups(Context context, HttpServletRequest request) 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 List getSpecialGroups(Context context, HttpServletRequest request) 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 static boolean isEnabled() { /** * 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 @@ protected EPerson findEPerson(Context context, HttpServletRequest request) throw 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 @@ protected int swordCompatibility(Context context, String username, String passwo "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 @@ protected int swordCompatibility(Context context, String username, String passwo /** * 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 @@ protected synchronized void initialize(Context context) throws SQLException { 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 12dc5feda583..55843c710760 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.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 int authenticate(Context context, String username, String password, 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 ddab01e8cb5d..68fffd3fb264 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.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 e955302ec3d7..45ad8932daec 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 @@ 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 ca5b4a11b543..2ab2bbf90266 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.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/indexer/DSpaceAuthorityIndexer.java b/dspace-api/src/main/java/org/dspace/authority/indexer/DSpaceAuthorityIndexer.java index 20baef8f9c01..b42bd130b7bd 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 List getAuthorityValues(Context context, Item item, Map 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 beff6fdc48e8..932cd71744d4 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java @@ -550,13 +550,11 @@ public void addPolicies(Context c, List policies, DSpaceObject d List newPolicies = new ArrayList<>(policies.size()); for (ResourcePolicy srp : policies) { - ResourcePolicy rp = resourcePolicyService.create(c); + ResourcePolicy rp = resourcePolicyService.create(c, srp.getEPerson(), srp.getGroup()); // copy over values rp.setdSpaceObject(dest); rp.setAction(srp.getAction()); - rp.setEPerson(srp.getEPerson()); - rp.setGroup(srp.getGroup()); rp.setStartDate(srp.getStartDate()); rp.setEndDate(srp.getEndDate()); rp.setRpName(srp.getRpName()); @@ -670,11 +668,9 @@ public ResourcePolicy createResourcePolicy(Context context, DSpaceObject dso, Gr "We need at least an eperson or a group in order to create a resource policy."); } - ResourcePolicy myPolicy = resourcePolicyService.create(context); + ResourcePolicy myPolicy = resourcePolicyService.create(context, eperson, group); myPolicy.setdSpaceObject(dso); myPolicy.setAction(type); - myPolicy.setGroup(group); - myPolicy.setEPerson(eperson); myPolicy.setRpType(rpType); myPolicy.setRpName(rpName); myPolicy.setRpDescription(rpDescription); @@ -697,7 +693,7 @@ public ResourcePolicy createOrModifyPolicy(ResourcePolicy policy, Context contex 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/FixDefaultPolicies.java b/dspace-api/src/main/java/org/dspace/authorize/FixDefaultPolicies.java index 61e783920ae5..b430a2635fd9 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/FixDefaultPolicies.java +++ b/dspace-api/src/main/java/org/dspace/authorize/FixDefaultPolicies.java @@ -126,10 +126,9 @@ private static void addAnonymousPolicy(Context c, DSpaceObject t, // now create the default policies for submitted items ResourcePolicyService resourcePolicyService = AuthorizeServiceFactory.getInstance().getResourcePolicyService(); - ResourcePolicy myPolicy = resourcePolicyService.create(c); + ResourcePolicy myPolicy = resourcePolicyService.create(c, null, anonymousGroup); myPolicy.setdSpaceObject(t); myPolicy.setAction(myaction); - myPolicy.setGroup(anonymousGroup); resourcePolicyService.update(c, myPolicy); } } 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 72998b9fd18a..23806b35dd7c 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 static void setPolicies(Context c, int containerType, * 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 @@ -229,11 +229,10 @@ public static void setPoliciesFilter(Context c, int containerType, // before create a new policy check if an identical policy is already in place if (!authorizeService.isAnIdenticalPolicyAlreadyInPlace(c, myitem, group, actionID, -1)) { // now add the policy - ResourcePolicy rp = resourcePolicyService.create(c); + ResourcePolicy rp = resourcePolicyService.create(c, null, group); rp.setdSpaceObject(myitem); rp.setAction(actionID); - rp.setGroup(group); rp.setRpName(name); rp.setRpDescription(description); @@ -262,11 +261,10 @@ public static void setPoliciesFilter(Context c, int containerType, // before create a new policy check if an identical policy is already in place if (!authorizeService.isAnIdenticalPolicyAlreadyInPlace(c, bundle, group, actionID, -1)) { // now add the policy - ResourcePolicy rp = resourcePolicyService.create(c); + ResourcePolicy rp = resourcePolicyService.create(c, null, group); rp.setdSpaceObject(bundle); rp.setAction(actionID); - rp.setGroup(group); rp.setRpName(name); rp.setRpDescription(description); @@ -305,11 +303,10 @@ public static void setPoliciesFilter(Context c, int containerType, if (!authorizeService .isAnIdenticalPolicyAlreadyInPlace(c, bitstream, group, actionID, -1)) { // now add the policy - ResourcePolicy rp = resourcePolicyService.create(c); + ResourcePolicy rp = resourcePolicyService.create(c, null, group); rp.setdSpaceObject(bitstream); rp.setAction(actionID); - rp.setGroup(group); rp.setRpName(name); rp.setRpDescription(description); 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 d12c3ba91929..2cefec35b348 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 @@ /** * 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 c781400bae45..68a59d46ae2f 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 @@ 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 b762107a84c5..86998a2196e7 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/ResourcePolicyServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/authorize/ResourcePolicyServiceImpl.java @@ -71,14 +71,22 @@ public ResourcePolicy find(Context context, int id) throws SQLException { * Create a new ResourcePolicy * * @param context DSpace context object + * @param ePerson + * @param group * @return ResourcePolicy * @throws SQLException if database error */ @Override - public ResourcePolicy create(Context context) throws SQLException { + public ResourcePolicy create(Context context, EPerson ePerson, Group group) throws SQLException { // FIXME: Check authorisation // Create a table row - ResourcePolicy resourcePolicy = resourcePolicyDAO.create(context, new ResourcePolicy()); + ResourcePolicy policyToBeCreated = new ResourcePolicy(); + if (ePerson == null && group == null) { + throw new IllegalArgumentException("A resource policy must contain a valid eperson or group"); + } + policyToBeCreated.setEPerson(ePerson); + policyToBeCreated.setGroup(group); + ResourcePolicy resourcePolicy = resourcePolicyDAO.create(context, policyToBeCreated); return resourcePolicy; } @@ -205,9 +213,7 @@ public boolean isDateValid(ResourcePolicy resourcePolicy) { @Override public ResourcePolicy clone(Context context, ResourcePolicy resourcePolicy) throws SQLException, AuthorizeException { - ResourcePolicy clone = create(context); - clone.setGroup(resourcePolicy.getGroup()); - clone.setEPerson(resourcePolicy.getEPerson()); + ResourcePolicy clone = create(context, resourcePolicy.getEPerson(), resourcePolicy.getGroup()); clone.setStartDate((Date) ObjectUtils.clone(resourcePolicy.getStartDate())); clone.setEndDate((Date) ObjectUtils.clone(resourcePolicy.getEndDate())); clone.setRpType((String) ObjectUtils.clone(resourcePolicy.getRpType())); @@ -411,7 +417,7 @@ public boolean isMyResourcePolicy(Context context, EPerson eperson, Integer id) 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 26b6bb1d7345..3b09f9cf300b 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 List findByTypeGroupActionExceptId(Context context, DSpac 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 List findByEPerson(Context context, EPerson ePerson, int @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 List findByEPersonAndResourceUuid(Context context, EPerso @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 List findByResouceUuidAndActionId(Context context, UUID r @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 List findByResouceUuid(Context context, UUID resourceUuid @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 List findByGroup(Context context, Group group, int offset @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 List findByGroupAndResourceUuid(Context context, Group gr @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 e0a94833d76c..9fb2e6d4a17a 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 void authorizeAction(Context c, EPerson e, DSpaceObject o, int action, bo 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 boolean authorizeActionBoolean(Context c, EPerson e, DSpaceObject o, int * @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 void addPolicy(Context c, DSpaceObject o, int actionID, EPerson e, String * @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/authorize/service/ResourcePolicyService.java b/dspace-api/src/main/java/org/dspace/authorize/service/ResourcePolicyService.java index 43735fcd6089..523bd64006d0 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/service/ResourcePolicyService.java +++ b/dspace-api/src/main/java/org/dspace/authorize/service/ResourcePolicyService.java @@ -17,7 +17,6 @@ import org.dspace.core.Context; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; -import org.dspace.service.DSpaceCRUDService; /** * Service interface class for the ResourcePolicy object. @@ -26,7 +25,34 @@ * * @author kevinvandevelde at atmire.com */ -public interface ResourcePolicyService extends DSpaceCRUDService { +public interface ResourcePolicyService { + + public ResourcePolicy create(Context context, EPerson eperson, Group group) throws SQLException, AuthorizeException; + + public ResourcePolicy find(Context context, int id) throws SQLException; + + /** + * Persist a model object. + * + * @param context + * @param resourcePolicy object to be persisted. + * @throws SQLException passed through. + * @throws AuthorizeException passed through. + */ + public void update(Context context, ResourcePolicy resourcePolicy) throws SQLException, AuthorizeException; + + + /** + * Persist a collection of model objects. + * + * @param context + * @param resourcePolicies object to be persisted. + * @throws SQLException passed through. + * @throws AuthorizeException passed through. + */ + public void update(Context context, List resourcePolicies) throws SQLException, AuthorizeException; + + public void delete(Context context, ResourcePolicy resourcePolicy) throws SQLException, AuthorizeException; public List find(Context c, DSpaceObject o) throws SQLException; 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 6c38c8dd664b..ff6de08583be 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 @@ private BrowseIndex(String definition, int number) } } - // 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") @@ -543,19 +543,6 @@ public String getDistinctTableName() { return getTableName(false, false, true, false); } - /** - * Get the name of the column that is used to store the default value column - * - * @return the name of the value column - */ - public String getValueColumn() { - if (!isDate()) { - return "sort_text_value"; - } else { - return "text_value"; - } - } - /** * Get the name of the primary key index column * @@ -565,39 +552,10 @@ public String getIndexColumn() { return "id"; } - /** - * Is this browse index type for a title? - * - * @return true if title type, false if not - */ -// public boolean isTitle() -// { -// return "title".equals(getDataType()); -// } - - /** - * Is the browse index type for a date? - * - * @return true if date type, false if not - */ - public boolean isDate() { - return "date".equals(getDataType()); - } - - /** - * Is the browse index type for a plain text type? - * - * @return true if plain text type, false if not - */ -// public boolean isText() -// { -// return "text".equals(getDataType()); -// } - /** * 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 ec4cb199ea1d..f78070d4f625 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 String findLinkType(String metadata) { // 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/browse/ItemCountDAO.java b/dspace-api/src/main/java/org/dspace/browse/ItemCountDAO.java index ab16f68d3569..6438b5745bdb 100644 --- a/dspace-api/src/main/java/org/dspace/browse/ItemCountDAO.java +++ b/dspace-api/src/main/java/org/dspace/browse/ItemCountDAO.java @@ -13,26 +13,17 @@ /** * Interface for data access of cached community and collection item count * information - * - * @author Richard Jones */ public interface ItemCountDAO { - /** - * Set the DSpace Context to use during data access - * - * @param context DSpace Context - * @throws ItemCountException if count error - */ - public void setContext(Context context) throws ItemCountException; /** * Get the number of items in the given DSpaceObject container. This method will * only succeed if the DSpaceObject is an instance of either a Community or a * Collection. Otherwise it will throw an exception. * + * @param context DSpace context * @param dso Dspace Object * @return count - * @throws ItemCountException if count error */ - public int getCount(DSpaceObject dso) throws ItemCountException; + int getCount(Context context, DSpaceObject dso); } diff --git a/dspace-api/src/main/java/org/dspace/browse/ItemCountDAOFactory.java b/dspace-api/src/main/java/org/dspace/browse/ItemCountDAOFactory.java deleted file mode 100644 index 722be645fdaa..000000000000 --- a/dspace-api/src/main/java/org/dspace/browse/ItemCountDAOFactory.java +++ /dev/null @@ -1,67 +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.browse; - -import java.lang.reflect.InvocationTargetException; - -import org.dspace.core.Context; -import org.dspace.services.ConfigurationService; -import org.dspace.services.factory.DSpaceServicesFactory; - -/** - * Factory class to allow us to load the correct DAO for registering - * item count information - * - * @author Richard Jones - * @author Ivan Masár - */ -public class ItemCountDAOFactory { - - /** - * Default constructor - */ - private ItemCountDAOFactory() { } - - /** - * Get an instance of ItemCountDAO which supports the correct storage backend - * for the specific DSpace instance. - * - * @param context DSpace Context - * @return DAO - * @throws ItemCountException if count error - */ - public static ItemCountDAO getInstance(Context context) - throws ItemCountException { - - /** Log4j logger */ - ItemCountDAO dao = null; - - ConfigurationService configurationService - = DSpaceServicesFactory.getInstance().getConfigurationService(); - String className = configurationService.getProperty("ItemCountDAO.class"); - - // SOLR implementation is the default since DSpace 4.0 - if (className == null) { - dao = new ItemCountDAOSolr(); - } else { - try { - dao = (ItemCountDAO) Class.forName(className.trim()) - .getDeclaredConstructor() - .newInstance(); - } catch (ClassNotFoundException | IllegalAccessException - | InstantiationException | NoSuchMethodException - | SecurityException | IllegalArgumentException - | InvocationTargetException e) { - throw new ItemCountException("The configuration for ItemCountDAO is invalid: " + className, e); - } - } - - dao.setContext(context); - return dao; - } -} diff --git a/dspace-api/src/main/java/org/dspace/browse/ItemCountDAOSolr.java b/dspace-api/src/main/java/org/dspace/browse/ItemCountDAOSolr.java index d233270d813c..e4d0079fe20b 100644 --- a/dspace-api/src/main/java/org/dspace/browse/ItemCountDAOSolr.java +++ b/dspace-api/src/main/java/org/dspace/browse/ItemCountDAOSolr.java @@ -24,14 +24,12 @@ import org.dspace.discovery.SearchServiceException; import org.dspace.discovery.configuration.DiscoveryConfigurationParameters; import org.dspace.discovery.indexobject.IndexableItem; -import org.dspace.services.factory.DSpaceServicesFactory; +import org.springframework.beans.factory.annotation.Autowired; /** * Discovery (Solr) driver implementing ItemCountDAO interface to look up item * count information in communities and collections. Caching operations are * intentionally not implemented because Solr already is our cache. - * - * @author Ivan Masár, Andrea Bollini */ public class ItemCountDAOSolr implements ItemCountDAO { /** @@ -39,11 +37,6 @@ public class ItemCountDAOSolr implements ItemCountDAO { */ private static Logger log = org.apache.logging.log4j.LogManager.getLogger(ItemCountDAOSolr.class); - /** - * DSpace context - */ - private Context context; - /** * Hold the communities item count obtained from SOLR after the first query. This only works * well if the ItemCountDAO lifecycle is bound to the request lifecycle as @@ -61,41 +54,28 @@ public class ItemCountDAOSolr implements ItemCountDAO { /** * Solr search service */ - SearchService searcher = DSpaceServicesFactory.getInstance().getServiceManager() - .getServiceByName(SearchService.class.getName(), SearchService.class); - - /** - * Set the dspace context to use - * - * @param context DSpace Context - * @throws ItemCountException if count error - */ - @Override - public void setContext(Context context) throws ItemCountException { - this.context = context; - } + @Autowired + protected SearchService searchService; /** * Get the count of the items in the given container. * - * @param dso Dspace Context + * @param context DSpace context + * @param dso DspaceObject * @return count - * @throws ItemCountException if count error */ @Override - public int getCount(DSpaceObject dso) throws ItemCountException { - loadCount(); - Integer val; + public int getCount(Context context, DSpaceObject dso) { + loadCount(context); + Integer val = null; if (dso instanceof Collection) { - val = collectionsCount.get(String.valueOf(((Collection) dso).getID())); + val = collectionsCount.get(dso.getID().toString()); } else if (dso instanceof Community) { - val = communitiesCount.get(String.valueOf(((Community) dso).getID())); - } else { - throw new ItemCountException("We can only count items in Communities or Collections"); + val = communitiesCount.get(dso.getID().toString()); } if (val != null) { - return val.intValue(); + return val; } else { return 0; } @@ -105,15 +85,15 @@ public int getCount(DSpaceObject dso) throws ItemCountException { * make sure that the counts are actually fetched from Solr (if haven't been * cached in a Map yet) * - * @throws ItemCountException if count error + * @param context DSpace Context */ - private void loadCount() throws ItemCountException { + private void loadCount(Context context) { if (communitiesCount != null || collectionsCount != null) { return; } - communitiesCount = new HashMap(); - collectionsCount = new HashMap(); + communitiesCount = new HashMap<>(); + collectionsCount = new HashMap<>(); DiscoverQuery query = new DiscoverQuery(); query.setFacetMinCount(1); @@ -125,11 +105,13 @@ private void loadCount() throws ItemCountException { DiscoveryConfigurationParameters.SORT.COUNT)); query.addFilterQueries("search.resourcetype:" + IndexableItem.TYPE); // count only items query.addFilterQueries("NOT(discoverable:false)"); // only discoverable + query.addFilterQueries("withdrawn:false"); // only not withdrawn + query.addFilterQueries("archived:true"); // only archived query.setMaxResults(0); - DiscoverResult sResponse = null; + DiscoverResult sResponse; try { - sResponse = searcher.search(context, query); + sResponse = searchService.search(context, query); List commCount = sResponse.getFacetResult("location.comm"); List collCount = sResponse.getFacetResult("location.coll"); for (FacetResult c : commCount) { @@ -139,8 +121,7 @@ private void loadCount() throws ItemCountException { collectionsCount.put(c.getAsFilterQuery(), (int) c.getCount()); } } catch (SearchServiceException e) { - log.error("caught exception: ", e); - throw new ItemCountException(e); + log.error("Could not initialize Community/Collection Item Counts from Solr: ", e); } } } diff --git a/dspace-api/src/main/java/org/dspace/browse/ItemCountException.java b/dspace-api/src/main/java/org/dspace/browse/ItemCountException.java deleted file mode 100644 index 533e6ecceb97..000000000000 --- a/dspace-api/src/main/java/org/dspace/browse/ItemCountException.java +++ /dev/null @@ -1,32 +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.browse; - -/** - * Exception type to handle item count specific problems - * - * @author Richard Jones - */ -public class ItemCountException extends Exception { - - public ItemCountException() { - } - - public ItemCountException(String message) { - super(message); - } - - public ItemCountException(Throwable cause) { - super(cause); - } - - public ItemCountException(String message, Throwable cause) { - super(message, cause); - } - -} diff --git a/dspace-api/src/main/java/org/dspace/browse/ItemCounter.java b/dspace-api/src/main/java/org/dspace/browse/ItemCounter.java index 20c43fc37298..b5a695566976 100644 --- a/dspace-api/src/main/java/org/dspace/browse/ItemCounter.java +++ b/dspace-api/src/main/java/org/dspace/browse/ItemCounter.java @@ -13,26 +13,18 @@ import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.DSpaceObject; -import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.ItemService; import org.dspace.core.Context; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; -import org.dspace.web.ContextUtil; +import org.springframework.beans.factory.annotation.Autowired; /** * This class provides a standard interface to all item counting - * operations for communities and collections. It can be run from the - * command line to prepare the cached data if desired, simply by - * running: + * operations for communities and collections. * - * java org.dspace.browse.ItemCounter - * - * It can also be invoked via its standard API. In the event that - * the data cache is not being used, this class will return direct + * In the event that the data cache is not being used, this class will return direct * real time counts of content. - * - * @author Richard Jones */ public class ItemCounter { /** @@ -40,57 +32,15 @@ public class ItemCounter { */ private static Logger log = org.apache.logging.log4j.LogManager.getLogger(ItemCounter.class); - /** - * DAO to use to store and retrieve data - */ - private ItemCountDAO dao; - - /** - * DSpace Context - */ - private Context context; - - /** - * This field is used to hold singular instance of a class. - * Singleton pattern is used but this class should be - * refactored to modern DSpace approach (injectible service). - */ - - private static ItemCounter instance; - + @Autowired protected ItemService itemService; + @Autowired protected ConfigurationService configurationService; - private boolean showStrengths; - private boolean useCache; - - /** - * Construct a new item counter which will use the given DSpace Context - * - * @param context current context - * @throws ItemCountException if count error - */ - public ItemCounter(Context context) throws ItemCountException { - this.context = context; - this.dao = ItemCountDAOFactory.getInstance(this.context); - this.itemService = ContentServiceFactory.getInstance().getItemService(); - this.configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); - this.showStrengths = configurationService.getBooleanProperty("webui.strengths.show", false); - this.useCache = configurationService.getBooleanProperty("webui.strengths.cache", true); - } - /** - * Get the singular instance of a class. - * It creates a new instance at the first usage of this method. - * - * @return instance af a class - * @throws ItemCountException when error occurs + * Construct a new item counter */ - public static ItemCounter getInstance() throws ItemCountException { - if (instance == null) { - instance = new ItemCounter(ContextUtil.obtainCurrentRequestContext()); - } - return instance; + protected ItemCounter() { } /** @@ -103,17 +53,24 @@ public static ItemCounter getInstance() throws ItemCountException { * If it is equal to 'false' it will count the number of items * in the container in real time. * + * @param context DSpace Context * @param dso DSpaceObject - * @return count - * @throws ItemCountException when error occurs + * @return count (-1 is returned if count could not be determined or is disabled) */ - public int getCount(DSpaceObject dso) throws ItemCountException { + public int getCount(Context context, DSpaceObject dso) { + boolean showStrengths = configurationService.getBooleanProperty("webui.strengths.show", false); + boolean useCache = configurationService.getBooleanProperty("webui.strengths.cache", true); if (!showStrengths) { return -1; } if (useCache) { - return dao.getCount(dso); + // NOTE: This bean is NOT Autowired above because it's a "prototype" bean which we want to reload + // occasionally. Each time the bean reloads it will update the cached item counts. + ItemCountDAO dao = + DSpaceServicesFactory.getInstance().getServiceManager().getServiceByName("itemCountDAO", + ItemCountDAO.class); + return dao.getCount(context, dso); } // if we make it this far, we need to manually count @@ -121,8 +78,8 @@ public int getCount(DSpaceObject dso) throws ItemCountException { try { return itemService.countItems(context, (Collection) dso); } catch (SQLException e) { - log.error("caught exception: ", e); - throw new ItemCountException(e); + log.error("Error counting number of Items in Collection {} :", dso.getID(), e); + return -1; } } @@ -130,8 +87,8 @@ public int getCount(DSpaceObject dso) throws ItemCountException { try { return itemService.countItems(context, ((Community) dso)); } catch (SQLException e) { - log.error("caught exception: ", e); - throw new ItemCountException(e); + log.error("Error counting number of Items in Community {} :", dso.getID(), e); + return -1; } } 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 63779cda02fb..521bf546a4f2 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 57fcdb8e365e..a63488077703 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 b291232e8b55..50ef4baa98e3 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.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 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 93ce634a0541..36fcefd47b3e 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 LimitedCountDispatcher(BitstreamDispatcher del) { } /** - * 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 eff8a8be1cde..5cb1851e7c4f 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 @@ 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 328d4a717eb1..44c594d0eb32 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 @@ 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 7552c6d5bb8f..ac882e971dfe 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 a31e02cbab4a..669621aeeb58 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.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 000000000000..c9d65186b631 --- /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 000000000000..d678aa052303 --- /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 000000000000..ee23ee346521 --- /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 000000000000..afb771529f4a --- /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 000000000000..43f3ea17330b --- /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 451a3b75784d..3fdb6316b210 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.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. @@ -307,10 +307,18 @@ public Collection getCollection() { return collection; } + public void setCollection(Collection collection) { + this.collection = collection; + } + public Community getCommunity() { return community; } + public void setCommunity(Community community) { + this.community = community; + } + /** * Get the asset store number where this bitstream is stored * 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 6d64ee3073e9..4dacea0952a5 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.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 e23e5ce2c825..bd56ad465163 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.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 e5cbdb6ff244..b619d5cd069e 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 @@ 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 void unsetPrimaryBitstreamID() { /** * 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 000000000000..fa6ec7676a32 --- /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 53b63dbef1fa..8c27c5a4ae1c 100644 --- a/dspace-api/src/main/java/org/dspace/content/Collection.java +++ b/dspace-api/src/main/java/org/dspace/content/Collection.java @@ -15,29 +15,26 @@ 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.browse.ItemCountException; 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. @@ -54,9 +51,7 @@ */ @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; @@ -135,6 +130,9 @@ public Bitstream getLogo() { protected void setLogo(Bitstream logo) { this.logo = logo; + if (logo != null) { + logo.setCollection(this); + } setModified(); } @@ -157,7 +155,7 @@ public Group getSubmitters() { /** * 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 */ @@ -231,7 +229,7 @@ public String getLicenseCollection() { * @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); } /** @@ -336,18 +334,4 @@ private CollectionService getCollectionService() { } return collectionService; } - - /** - * return count of the collection items - * - * @return int - */ - public int countArchivedItems() { - try { - return collectionService.countArchivedItems(this); - } catch (ItemCountException e) { - throw new RuntimeException(e); - } - } - } 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 652d2a5f38a0..b800ce21a962 100644 --- a/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java @@ -31,7 +31,6 @@ import org.dspace.authorize.ResourcePolicy; import org.dspace.authorize.service.AuthorizeService; import org.dspace.authorize.service.ResourcePolicyService; -import org.dspace.browse.ItemCountException; import org.dspace.browse.ItemCounter; import org.dspace.content.dao.CollectionDAO; import org.dspace.content.service.BitstreamService; @@ -122,6 +121,9 @@ public class CollectionServiceImpl extends DSpaceObjectServiceImpl i @Autowired(required = true) protected ConfigurationService configurationService; + @Autowired + protected ItemCounter itemCounter; + protected CollectionServiceImpl() { super(); } @@ -1109,35 +1111,15 @@ public int countCollectionsWithSubmit(String q, Context context, Community commu return (int) resp.getTotalSearchResults(); } - @Override - @SuppressWarnings("rawtypes") - public List findAllCollectionsByEntityType(Context context, String entityType) - throws SearchServiceException { - List collectionList = new ArrayList<>(); - - DiscoverQuery discoverQuery = new DiscoverQuery(); - discoverQuery.setDSpaceObjectFilter(IndexableCollection.TYPE); - discoverQuery.addFilterQueries("dspace.entity.type:" + entityType); - - DiscoverResult discoverResult = searchService.search(context, discoverQuery); - List solrIndexableObjects = discoverResult.getIndexableObjects(); - - for (IndexableObject solrCollection : solrIndexableObjects) { - Collection c = ((IndexableCollection) solrCollection).getIndexedObject(); - collectionList.add(c); - } - return collectionList; - } - /** * Returns total collection archived items * + * @param context DSpace Context * @param collection Collection * @return total collection archived items - * @throws ItemCountException */ @Override - public int countArchivedItems(Collection collection) throws ItemCountException { - return ItemCounter.getInstance().getCount(collection); + public int countArchivedItems(Context context, Collection collection) { + return itemCounter.getCount(context, collection); } } 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 dd6d978936df..7f362d2f1610 100644 --- a/dspace-api/src/main/java/org/dspace/content/Community.java +++ b/dspace-api/src/main/java/org/dspace/content/Community.java @@ -12,28 +12,26 @@ 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.browse.ItemCountException; 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 @@ -46,9 +44,7 @@ */ @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; @@ -123,6 +119,9 @@ public Bitstream getLogo() { void setLogo(Bitstream logo) { this.logo = logo; + if (logo != null) { + logo.setCommunity(this); + } setModified(); } @@ -264,17 +263,4 @@ private CommunityService getCommunityService() { } return communityService; } - - /** - * return count of the community items - * - * @return int - */ - public int countArchivedItems() { - try { - return communityService.countArchivedItems(this); - } catch (ItemCountException e) { - throw new RuntimeException(e); - } - } } diff --git a/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java index 045adc229e79..ff815396c469 100644 --- a/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java @@ -24,7 +24,6 @@ import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.ResourcePolicy; import org.dspace.authorize.service.AuthorizeService; -import org.dspace.browse.ItemCountException; import org.dspace.browse.ItemCounter; import org.dspace.content.dao.CommunityDAO; import org.dspace.content.service.BitstreamService; @@ -78,6 +77,8 @@ public class CommunityServiceImpl extends DSpaceObjectServiceImpl imp protected IdentifierService identifierService; @Autowired(required = true) protected SubscribeService subscribeService; + @Autowired + protected ItemCounter itemCounter; protected CommunityServiceImpl() { super(); @@ -719,12 +720,12 @@ public int countTotal(Context context) throws SQLException { /** * Returns total community archived items * + * @param context DSpace context * @param community Community * @return total community archived items - * @throws ItemCountException */ @Override - public int countArchivedItems(Community community) throws ItemCountException { - return ItemCounter.getInstance().getCount(community); + public int countArchivedItems(Context context, Community community) { + return itemCounter.getCount(context, community); } } 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 d58aff7b1e22..163e21cdc2c5 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 @@ private enum DateGran { YEAR, MONTH, DAY, TIME } // 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 DCDate(String fromDC) { 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 @@ private void setUTCForFormatting() { 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 cb9b5346ff69..e51882f82ede 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 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 59217a109f66..b659035dc650 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.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 2119959073f0..eee858fbc3e2 100644 --- a/dspace-api/src/main/java/org/dspace/content/DSpaceObjectServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/DSpaceObjectServiceImpl.java @@ -242,10 +242,31 @@ public List addMetadata(Context context, T dso, MetadataField met } + /** + * Add metadata value(s) to a MetadataField of a DSpace Object + * @param context current DSpace context + * @param dso DSpaceObject to modify + * @param metadataField MetadataField to add values to + * @param lang Language code to add + * @param values One or more metadata values to add + * @param authorities One or more authorities to add + * @param confidences One or more confidences to add (for authorities) + * @param placeSupplier Supplier of "place" for new metadata values + * @return List of newly added metadata values + * @throws SQLException if database error occurs + * @throws IllegalArgumentException for an empty list of values + */ public List addMetadata(Context context, T dso, MetadataField metadataField, String lang, List values, List authorities, List confidences, Supplier placeSupplier) throws SQLException { + // Throw an error if we are attempting to add empty values + if (values == null || values.isEmpty()) { + throw new IllegalArgumentException("Cannot add empty values to a new metadata field " + + metadataField.toString() + " on object with uuid = " + + dso.getID().toString() + " and type = " + getTypeText(dso)); + } + boolean authorityControlled = metadataAuthorityService.isAuthorityControlled(metadataField); boolean authorityRequired = metadataAuthorityService.isAuthorityRequired(metadataField); List newMetadata = new ArrayList<>(); @@ -302,7 +323,7 @@ public List addMetadata(Context context, T dso, MetadataField met } } 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()); } @@ -314,20 +335,26 @@ public List addMetadata(Context context, T dso, MetadataField met @Override public MetadataValue addMetadata(Context context, T dso, MetadataField metadataField, String language, String value, String authority, int confidence) throws SQLException { - return addMetadata(context, dso, metadataField, language, Arrays.asList(value), Arrays.asList(authority), - Arrays.asList(confidence)).get(0); + List metadataValues = + addMetadata(context, dso, metadataField, language, Arrays.asList(value), Arrays.asList(authority), + Arrays.asList(confidence)); + return CollectionUtils.isNotEmpty(metadataValues) ? metadataValues.get(0) : null; } @Override public MetadataValue addMetadata(Context context, T dso, String schema, String element, String qualifier, String lang, String value) throws SQLException { - return addMetadata(context, dso, schema, element, qualifier, lang, Arrays.asList(value)).get(0); + List metadataValues = + addMetadata(context, dso, schema, element, qualifier, lang, Arrays.asList(value)); + return CollectionUtils.isNotEmpty(metadataValues) ? metadataValues.get(0) : null; } @Override public MetadataValue addMetadata(Context context, T dso, MetadataField metadataField, String language, String value) throws SQLException { - return addMetadata(context, dso, metadataField, language, Arrays.asList(value)).get(0); + List metadataValues = + addMetadata(context, dso, metadataField, language, Arrays.asList(value)); + return CollectionUtils.isNotEmpty(metadataValues) ? metadataValues.get(0) : null; } @Override @@ -629,6 +656,7 @@ public void update(Context context, T dso) throws SQLException, AuthorizeExcepti // 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 000000000000..9f52b7b63ac3 --- /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 2f34129f2e69..e83178667840 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 Entity findByItemId(Context context, UUID itemId, Integer limit, Integer @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 List getLeftRelations(Context context, Entity entity) { 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 List getRightRelations(Context context, Entity entity) { 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 List getAllRelationshipTypes(Context context, Entity en @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 List getLeftRelationshipTypes(Context context, Entity e @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 List getRightRelationshipTypes(Context context, Entity 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 20ab758a0b76..720e0c492ca7 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/EntityTypeServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/EntityTypeServiceImpl.java index 0e0c6d51e501..b5b066d9c36f 100644 --- a/dspace-api/src/main/java/org/dspace/content/EntityTypeServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/EntityTypeServiceImpl.java @@ -148,7 +148,7 @@ public List getSubmitAuthorizedTypes(Context context) sQuery.setFacetMinCount(1); sQuery.setFacetLimit(Integer.MAX_VALUE); sQuery.setFacetSort(FacetParams.FACET_SORT_INDEX); - QueryResponse qResp = solrSearchCore.getSolr().query(sQuery); + QueryResponse qResp = solrSearchCore.getSolr().query(sQuery, solrSearchCore.REQUEST_METHOD); FacetField facetField = qResp.getFacetField("search.entitytype"); if (Objects.nonNull(facetField)) { for (Count c : facetField.getValues()) { 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 7e34af132b0a..13f149f69f64 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 @@ 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 89e42c182bf3..23f30615d061 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.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() { } @@ -270,14 +274,20 @@ public String getSubmittedByProvenanceMessage(Context context, Item item) throws // 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 547ff490b84b..5422528f84a3 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.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 000000000000..8b664a972605 --- /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 000000000000..bdb23d65666c --- /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 9791f69abbc5..9a97851937e5 100644 --- a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java @@ -18,6 +18,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.UUID; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -49,6 +50,7 @@ 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; @@ -65,6 +67,7 @@ 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; @@ -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; @@ -175,7 +178,6 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl implements It private QAEventsDAO qaEventsDao; protected ItemServiceImpl() { - super(); } @Override @@ -275,9 +277,57 @@ public Item createTemplateItem(Context context, Collection collection) throws SQ + template.getID())); return template; - } else { - return collection.getTemplateItem(); } + return collection.getTemplateItem(); + } + + @Override + public void populateWithTemplateItemMetadata(Context context, Collection collection, boolean template, Item item) + throws SQLException { + + Item templateItem = collection.getTemplateItem(); + + Optional colEntityType = getDSpaceEntityType(collection); + Optional templateItemEntityType = getDSpaceEntityType(templateItem); + + if (template && colEntityType.isPresent() && templateItemEntityType.isPresent() && + !StringUtils.equals(colEntityType.get().getValue(), templateItemEntityType.get().getValue())) { + throw new IllegalStateException("The template item has entity type : (" + + templateItemEntityType.get().getValue() + ") different than collection entity type : " + + colEntityType.get().getValue()); + } + + if (template && colEntityType.isPresent() && templateItemEntityType.isEmpty()) { + MetadataValue original = colEntityType.get(); + MetadataField metadataField = original.getMetadataField(); + MetadataSchema metadataSchema = metadataField.getMetadataSchema(); + // NOTE: dspace.entity.type = does not make sense + // the collection entity type is by default blank when a collection is first created + if (StringUtils.isNotBlank(original.getValue())) { + addMetadata(context, item, metadataSchema.getName(), metadataField.getElement(), + metadataField.getQualifier(), original.getLanguage(), original.getValue()); + } + } + + if (template && (templateItem != null)) { + List md = getMetadata(templateItem, Item.ANY, Item.ANY, Item.ANY, Item.ANY); + + for (MetadataValue aMd : md) { + MetadataField metadataField = aMd.getMetadataField(); + MetadataSchema metadataSchema = metadataField.getMetadataSchema(); + addMetadata(context, item, metadataSchema.getName(), metadataField.getElement(), + metadataField.getQualifier(), aMd.getLanguage(), aMd.getValue()); + } + } + } + + private Optional getDSpaceEntityType(DSpaceObject dSpaceObject) { + return Objects.nonNull(dSpaceObject) ? dSpaceObject.getMetadata() + .stream() + .filter(x -> x.getMetadataField().toString('.') + .equalsIgnoreCase("dspace.entity.type")) + .findFirst() + : Optional.empty(); } @Override @@ -802,6 +852,7 @@ protected void rawDelete(Context context, Item item) throws AuthorizeException, DOI doi = doiService.findDOIByDSpaceObject(context, item); if (doi != null) { doi.setDSpaceObject(null); + doi.setStatus(DOIIdentifierProvider.TO_BE_DELETED); } // remove version attached to the item @@ -956,7 +1007,7 @@ public void adjustBundleBitstreamPolicies(Context context, Item item, Collection 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 @@ -1190,9 +1241,8 @@ public boolean canEdit(Context context, Item item) throws SQLException { if (item.getOwningCollection() == null) { if (!isInProgressSubmission(context, item)) { return true; - } else { - return false; } + return false; } return collectionService.canEditBoolean(context, item.getOwningCollection(), false); @@ -1284,8 +1334,8 @@ protected void addDefaultPoliciesNotInPlace(Context context, DSpaceObject dso, 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); @@ -1384,9 +1434,8 @@ public Iterator findArchivedByMetadataField(Context context, 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 @@ -1430,9 +1479,24 @@ public Iterator findByMetadataField(Context context, 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 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 @@ -1498,20 +1562,19 @@ public DSpaceObject getParentObject(Context context, Item item) throws SQLExcept 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); - } + } + 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(); + if (inprogress != null) { + return inprogress.getCollection(); } + // is a template item? + return item.getTemplateItemOf(); } @Override @@ -1570,13 +1633,13 @@ public boolean isItemListedForUser(Context context, Item item) { @Override public int countItems(Context context, Collection collection) throws SQLException { - return itemDAO.countItems(context, collection, true, false); + return itemDAO.countItems(context, collection, true, false, true); } @Override public int countAllItems(Context context, Collection collection) throws SQLException { - return itemDAO.countItems(context, collection, true, false) + itemDAO.countItems(context, collection, - false, true); + return itemDAO.countItems(context, collection, true, false, true) + itemDAO.countItems(context, collection, + false, true, true); } @Override @@ -1585,7 +1648,7 @@ public int countItems(Context context, Community community) throws SQLException List collections = communityService.getAllCollections(context, community); // Now, lets count unique items across that list of collections - return itemDAO.countItems(context, collections, true, false); + return itemDAO.countItems(context, collections, true, false, true); } @Override @@ -1594,8 +1657,8 @@ public int countAllItems(Context context, Community community) throws SQLExcepti List collections = communityService.getAllCollections(context, community); // Now, lets count unique items across that list of collections - return itemDAO.countItems(context, collections, true, false) + itemDAO.countItems(context, collections, - false, true); + return itemDAO.countItems(context, collections, true, false, true) + itemDAO.countItems(context, collections, + false, false, true); } @Override @@ -1611,9 +1674,8 @@ public Item findByIdOrLegacyId(Context context, String id) throws SQLException { 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; @@ -1639,19 +1701,19 @@ public int countTotal(Context context) throws SQLException { @Override public int countNotArchivedItems(Context context) throws SQLException { // return count of items not in archive and also not withdrawn - return itemDAO.countItems(context, false, false); + return itemDAO.countItems(context, false, false, true); } @Override public int countArchivedItems(Context context) throws SQLException { // return count of items in archive and also not withdrawn - return itemDAO.countItems(context, true, false); + return itemDAO.countItems(context, true, false, true); } @Override public int countWithdrawnItems(Context context) throws SQLException { // return count of items that are not in archive and withdrawn - return itemDAO.countItems(context, false, true); + return itemDAO.countItems(context, false, true, true ); } @Override @@ -1732,12 +1794,14 @@ public List getMetadata(Item item, String schema, String element, */ @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); @@ -1747,10 +1811,10 @@ protected void moveSingleMetadataValue(Context context, Item dso, int place, Met //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 8b767011999e..cbe90a374432 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 8d7f4b027733..32c08dab2b56 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 f60e5e1604cf..b9a4665e6672 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 31479e620618..dc45579f4ef9 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 String getLanguage() { * @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 0c34c04f3051..97f0c2ccf4ca 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 MetadataValue create(Context context, DSpaceObject dso, MetadataField met 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 aa4a8ea5429c..15d302ec6152 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 @@ 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 index 9e90f81be32c..0f2608ef705c 100644 --- a/dspace-api/src/main/java/org/dspace/content/QAEvent.java +++ b/dspace-api/src/main/java/org/dspace/content/QAEvent.java @@ -13,6 +13,8 @@ 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; @@ -21,6 +23,7 @@ * 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', @@ -30,6 +33,8 @@ public class QAEvent { 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; @@ -62,8 +67,7 @@ public class QAEvent { private String status = "PENDING"; - public QAEvent() { - } + public QAEvent() {} public QAEvent(String source, String originalId, String target, String title, String topic, double trust, String message, Date lastUpdate) { @@ -81,7 +85,6 @@ public QAEvent(String source, String originalId, String target, String title, } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) { throw new IllegalStateException(e); } - } public String getOriginalId() { @@ -205,6 +208,10 @@ 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 index 3631a2ff68c6..fcca5b45703a 100644 --- a/dspace-api/src/main/java/org/dspace/content/QAEventProcessed.java +++ b/dspace-api/src/main/java/org/dspace/content/QAEventProcessed.java @@ -9,15 +9,15 @@ 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.ManyToOne; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +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; /** 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 77c418a23dea..05e4b0071861 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 5e6941052b83..bcabc98cff34 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 0bdab6ffe564..904c1d3e6b86 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 a4c880173bf7..355e18269494 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/WorkspaceItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/WorkspaceItemServiceImpl.java index d43ad01f1a8c..543f5a55efe2 100644 --- a/dspace-api/src/main/java/org/dspace/content/WorkspaceItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/WorkspaceItemServiceImpl.java @@ -12,11 +12,8 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.Objects; -import java.util.Optional; import java.util.UUID; -import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.dspace.app.util.DCInputsReaderException; import org.dspace.app.util.Util; @@ -96,11 +93,18 @@ public WorkspaceItem find(Context context, int id) throws SQLException { @Override public WorkspaceItem create(Context context, Collection collection, boolean template) throws AuthorizeException, SQLException { - return create(context, collection, null, template); + return create(context, collection, null, template, false); } @Override - public WorkspaceItem create(Context context, Collection collection, UUID uuid, boolean template) + public WorkspaceItem create(Context context, Collection collection, boolean template, boolean isNewVersion) + throws AuthorizeException, SQLException { + return create(context, collection, null, template, isNewVersion); + } + + @Override + public WorkspaceItem create(Context context, Collection collection, UUID uuid, boolean template, + boolean isNewVersion) throws AuthorizeException, SQLException { // Check the user has permission to ADD to the collection authorizeService.authorizeAction(context, collection, Constants.ADD); @@ -134,48 +138,16 @@ public WorkspaceItem create(Context context, Collection collection, UUID uuid, b .addPolicy(context, item, Constants.DELETE, item.getSubmitter(), ResourcePolicy.TYPE_SUBMISSION); // Copy template if appropriate - Item templateItem = collection.getTemplateItem(); - - Optional colEntityType = getDSpaceEntityType(collection); - Optional templateItemEntityType = getDSpaceEntityType(templateItem); - - if (template && colEntityType.isPresent() && templateItemEntityType.isPresent() && - !StringUtils.equals(colEntityType.get().getValue(), templateItemEntityType.get().getValue())) { - throw new IllegalStateException("The template item has entity type : (" + - templateItemEntityType.get().getValue() + ") different than collection entity type : " + - colEntityType.get().getValue()); - } - - if (template && colEntityType.isPresent() && templateItemEntityType.isEmpty()) { - MetadataValue original = colEntityType.get(); - MetadataField metadataField = original.getMetadataField(); - MetadataSchema metadataSchema = metadataField.getMetadataSchema(); - // NOTE: dspace.entity.type = does not make sense - // the collection entity type is by default blank when a collection is first created - if (StringUtils.isNotBlank(original.getValue())) { - itemService.addMetadata(context, item, metadataSchema.getName(), metadataField.getElement(), - metadataField.getQualifier(), original.getLanguage(), original.getValue()); - } - } - - if (template && (templateItem != null)) { - List md = itemService.getMetadata(templateItem, Item.ANY, Item.ANY, Item.ANY, Item.ANY); - - for (MetadataValue aMd : md) { - MetadataField metadataField = aMd.getMetadataField(); - MetadataSchema metadataSchema = metadataField.getMetadataSchema(); - itemService.addMetadata(context, item, metadataSchema.getName(), metadataField.getElement(), - metadataField.getQualifier(), aMd.getLanguage(), - aMd.getValue()); - } - } + itemService.populateWithTemplateItemMetadata(context, collection, template, item); itemService.update(context, item); // If configured, register identifiers (eg handle, DOI) now. This is typically used with the Show Identifiers // submission step which previews minted handles and DOIs during the submission process. Default: false + // Additional check needed: if we are creating a new version of an existing item we skip the identifier + // generation here, as this will be performed when the new version is created in VersioningServiceImpl if (DSpaceServicesFactory.getInstance().getConfigurationService() - .getBooleanProperty("identifiers.submission.register", false)) { + .getBooleanProperty("identifiers.submission.register", false) && !isNewVersion) { try { // Get map of filters to use for identifier types, while the item is in progress Map, Filter> filters = FilterUtils.getIdentifierFilters(true); @@ -204,15 +176,6 @@ public WorkspaceItem create(Context context, Collection collection, UUID uuid, b return workspaceItem; } - private Optional getDSpaceEntityType(DSpaceObject dSpaceObject) { - return Objects.nonNull(dSpaceObject) ? dSpaceObject.getMetadata() - .stream() - .filter(x -> x.getMetadataField().toString('.') - .equalsIgnoreCase("dspace.entity.type")) - .findFirst() - : Optional.empty(); - } - @Override public WorkspaceItem create(Context c, WorkflowItem workflowItem) throws SQLException, AuthorizeException { WorkspaceItem potentialDuplicate = findByItem(c, workflowItem.getItem()); 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 6d73bdb5eadb..9f6bff03bcbe 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 Choice(String authority, String label, String value, Map /** * 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 750e761f3d39..fddd85cea399 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 @@ default Integer getPreloadLevel() { * 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 f4d1f02710e1..f4201c1e28aa 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 @@ * 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 902bded33ef7..17548d808bad 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 @@ * 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/DSpaceControlledVocabulary.java b/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java index 16632ee5466b..444332df97d2 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java @@ -102,8 +102,9 @@ public boolean accept(File dir, String name) { } } String vocabulariesPath = DSpaceServicesFactory.getInstance().getConfigurationService() - .getProperty( - "dspace.dir") + "/config/controlled-vocabularies/"; + .getProperty("dspace.dir") + + File.separator + "config" + + File.separator + "controlled-vocabularies"; String[] xmlFiles = (new File(vocabulariesPath)).list(new xmlFilter()); List names = new ArrayList(); for (String filename : xmlFiles) { @@ -120,7 +121,8 @@ protected void init() { log.info("Initializing " + this.getClass().getName()); vocabularyName = this.getPluginInstanceName(); - String vocabulariesPath = config.getProperty("dspace.dir") + "/config/controlled-vocabularies/"; + String vocabulariesPath = config.getProperty("dspace.dir") + File.separator + "config" + + File.separator + "controlled-vocabularies" + File.separator; String configurationPrefix = "vocabulary.plugin." + vocabularyName; storeHierarchy = config.getBooleanProperty(configurationPrefix + ".hierarchy.store", storeHierarchy); suggestHierarchy = config.getBooleanProperty(configurationPrefix + ".hierarchy.suggest", suggestHierarchy); @@ -224,6 +226,7 @@ public Choice getChoice(String authKey, String locale) { @Override public boolean isHierarchical() { + init(); return true; } @@ -256,6 +259,7 @@ public Choice getParentChoice(String authorityName, String childId, String local @Override public Integer getPreloadLevel() { + init(); return preloadLevel; } 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 da5c743c5b07..418000e51e2a 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 Choices getMatches(String text, int start, int limit, String locale) { @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 0f93dff49879..d1001ce5dba8 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 Choices getMatches(String text, int start, int limit, String locale) { @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 123626cd0965..a8631ba1f067 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 Choices getMatches(String text, int start, int limit, String locale, 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 94e5ca57a028..f7325744c46a 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 @@ * 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 2ba6791de59c..e40bb978d6fe 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 @@ /** * 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/CreativeCommonsRDFStreamDisseminationCrosswalk.java b/dspace-api/src/main/java/org/dspace/content/crosswalk/CreativeCommonsRDFStreamDisseminationCrosswalk.java index 9042a3a7f523..0528c0c20570 100644 --- a/dspace-api/src/main/java/org/dspace/content/crosswalk/CreativeCommonsRDFStreamDisseminationCrosswalk.java +++ b/dspace-api/src/main/java/org/dspace/content/crosswalk/CreativeCommonsRDFStreamDisseminationCrosswalk.java @@ -59,7 +59,6 @@ public void disseminate(Context context, DSpaceObject dso, OutputStream out) Bitstream cc = creativeCommonsService.getLicenseRdfBitstream((Item) dso); if (cc != null) { Utils.copy(bitstreamService.retrieve(context, cc), out); - out.close(); } } } diff --git a/dspace-api/src/main/java/org/dspace/content/crosswalk/CreativeCommonsTextStreamDisseminationCrosswalk.java b/dspace-api/src/main/java/org/dspace/content/crosswalk/CreativeCommonsTextStreamDisseminationCrosswalk.java index edb9d60a623e..cd26b4d19cee 100644 --- a/dspace-api/src/main/java/org/dspace/content/crosswalk/CreativeCommonsTextStreamDisseminationCrosswalk.java +++ b/dspace-api/src/main/java/org/dspace/content/crosswalk/CreativeCommonsTextStreamDisseminationCrosswalk.java @@ -65,7 +65,6 @@ public void disseminate(Context context, DSpaceObject dso, OutputStream out) Bitstream cc = creativeCommonsService.getLicenseTextBitstream((Item) dso); if (cc != null) { Utils.copy(bitstreamService.retrieve(context, cc), out); - out.close(); } } } 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 4365d9a48533..76738118f6be 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 @@ */ 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 bb73c83c459e..a8d983697271 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/LicenseStreamDisseminationCrosswalk.java b/dspace-api/src/main/java/org/dspace/content/crosswalk/LicenseStreamDisseminationCrosswalk.java index 75b884613db9..46858747870d 100644 --- a/dspace-api/src/main/java/org/dspace/content/crosswalk/LicenseStreamDisseminationCrosswalk.java +++ b/dspace-api/src/main/java/org/dspace/content/crosswalk/LicenseStreamDisseminationCrosswalk.java @@ -57,7 +57,6 @@ public void disseminate(Context context, DSpaceObject dso, OutputStream out) if (licenseBs != null) { Utils.copy(bitstreamService.retrieve(context, licenseBs), out); - out.close(); } } } diff --git a/dspace-api/src/main/java/org/dspace/content/crosswalk/METSRightsCrosswalk.java b/dspace-api/src/main/java/org/dspace/content/crosswalk/METSRightsCrosswalk.java index 7f6622841ba7..bd424598771c 100644 --- a/dspace-api/src/main/java/org/dspace/content/crosswalk/METSRightsCrosswalk.java +++ b/dspace-api/src/main/java/org/dspace/content/crosswalk/METSRightsCrosswalk.java @@ -432,31 +432,7 @@ public void ingest(Context context, DSpaceObject dso, List ml, boolean //get what class of context this is String contextClass = element.getAttributeValue("CONTEXTCLASS"); - ResourcePolicy rp = resourcePolicyService.create(context); - SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); - - // get reference to the element - // Note: we are assuming here that there will only ever be ONE - // element. Currently there are no known use cases for multiple. - Element permsElement = element.getChild("Permissions", METSRights_NS); - if (permsElement == null) { - log.error("No element was found. Skipping this element."); - continue; - } - - if (element.getAttributeValue("rpName") != null) { - rp.setRpName(element.getAttributeValue("rpName")); - } - try { - if (element.getAttributeValue("start-date") != null) { - rp.setStartDate(sdf.parse(element.getAttributeValue("start-date"))); - } - if (element.getAttributeValue("end-date") != null) { - rp.setEndDate(sdf.parse(element.getAttributeValue("end-date"))); - } - } catch (ParseException ex) { - log.error("Failed to parse embargo date. The date needs to be in the format 'yyyy-MM-dd'.", ex); - } + ResourcePolicy rp = null; //Check if this permission pertains to Anonymous users if (ANONYMOUS_CONTEXTCLASS.equals(contextClass)) { @@ -464,22 +440,23 @@ public void ingest(Context context, DSpaceObject dso, List ml, boolean Group anonGroup = groupService.findByName(context, Group.ANONYMOUS); if (anonGroup == null) { throw new CrosswalkInternalException( - "The DSpace database has not been properly initialized. The Anonymous Group is " + - "missing from the database."); + "The DSpace database has not been properly initialized. The Anonymous Group is " + + "missing from the database."); } - rp.setGroup(anonGroup); + rp = resourcePolicyService.create(context, null, anonGroup); } else if (ADMIN_CONTEXTCLASS.equals(contextClass)) { // else if this permission declaration pertains to Administrators // get DSpace Administrator group Group adminGroup = groupService.findByName(context, Group.ADMIN); if (adminGroup == null) { throw new CrosswalkInternalException( - "The DSpace database has not been properly initialized. The Administrator Group is " + - "missing from the database."); + "The DSpace database has not been properly initialized. " + + "The Administrator Group is " + + "missing from the database."); } - rp.setGroup(adminGroup); + rp = resourcePolicyService.create(context, null, adminGroup); } else if (GROUP_CONTEXTCLASS.equals(contextClass)) { // else if this permission pertains to another DSpace group try { @@ -498,18 +475,17 @@ public void ingest(Context context, DSpaceObject dso, List ml, boolean //if not found, throw an error -- user should restore group from the SITE AIP if (group == null) { throw new CrosswalkInternalException("Cannot restore Group permissions on object (" - + "type=" + Constants.typeText[dso - .getType()] + ", " - + "handle=" + dso.getHandle() + ", " - + "ID=" + dso.getID() - + "). The Group named '" + groupName + "' is" + - " missing from DSpace. " - + "Please restore this group using the SITE " + - "AIP, or recreate it."); + + "type=" + Constants.typeText[dso.getType()] + ", " + + "handle=" + dso.getHandle() + ", " + + "ID=" + dso.getID() + + "). The Group named '" + groupName + "' is" + + " missing from DSpace. " + + "Please restore this group using the SITE " + + "AIP, or recreate it."); } //assign group to policy - rp.setGroup(group); + rp = resourcePolicyService.create(context, null, group); } catch (PackageException pe) { //A PackageException will only be thrown if translateDefaultGroupName() fails //We'll just wrap it as a CrosswalkException and throw it upwards @@ -535,25 +511,52 @@ public void ingest(Context context, DSpaceObject dso, List ml, boolean //if not found, throw an error -- user should restore person from the SITE AIP if (person == null) { throw new CrosswalkInternalException("Cannot restore Person permissions on object (" - + "type=" + Constants.typeText[dso - .getType()] + ", " - + "handle=" + dso.getHandle() + ", " - + "ID=" + dso.getID() - + "). The Person with email/netid '" + - personEmail + "' is missing from DSpace. " - + "Please restore this Person object using the " + - "SITE AIP, or recreate it."); + + "type=" + Constants.typeText[dso.getType()] + ", " + + "handle=" + dso.getHandle() + ", " + + "ID=" + dso.getID() + + "). The Person with email/netid '" + + personEmail + "' is missing from DSpace. " + + "Please restore this Person object using the " + + "SITE AIP, or recreate it."); } - //assign person to the policy - rp.setEPerson(person); + //create rp with the person + rp = resourcePolicyService.create(context, person, null); } else { log.error("Unrecognized CONTEXTCLASS: " + contextClass); } + if (rp != null) { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); + + // get reference to the element + // Note: we are assuming here that there will only ever be ONE + // element. Currently there are no known use cases for multiple. + Element permsElement = element.getChild("Permissions", METSRights_NS); + if (permsElement == null) { + log.error("No element was found. Skipping this element."); + continue; + } + + if (element.getAttributeValue("rpName") != null) { + rp.setRpName(element.getAttributeValue("rpName")); + } + try { + if (element.getAttributeValue("start-date") != null) { + rp.setStartDate(sdf.parse(element.getAttributeValue("start-date"))); + } + if (element.getAttributeValue("end-date") != null) { + rp.setEndDate(sdf.parse(element.getAttributeValue("end-date"))); + } + } catch (ParseException ex) { + log.error("Failed to parse embargo date. The date needs to be in the format 'yyyy-MM-dd'.", + ex); + } - //set permissions on policy add to list of policies - rp.setAction(parsePermissions(permsElement)); - policies.add(rp); + //set permissions and type on policy and add to list of policies + rp.setAction(parsePermissions(permsElement)); + rp.setRpType(ResourcePolicy.TYPE_CUSTOM); + policies.add(rp); + } } //end if "Context" element } //end for loop 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 1e63be5ba1b9..6315f73a7c4a 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 static modsTriple create(String qdc, String xml, String xpath) { * 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 f756aae22577..aa9096517b30 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 void ingest(Context context, DSpaceObject dso, Element root, boolean crea 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 2fdbaaad003e..fa582fd62f50 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 05fda2b97475..29baa980fc8b 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 void disseminate(Context context, DSpaceObject dso, OutputStream out) thr 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 == 1) { - 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 d4ccebf82e2c..1d07da0e51c7 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.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 @@ protected Transformer getTransformer(String direction) { 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 26371b46aab0..70bb3f1316e0 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.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.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 @@ private void init() // 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 Namespace[] getNamespaces() { try { init(); } catch (CrosswalkInternalException e) { - LOG.error(e.toString()); + LOG.error(e::toString); } return (Namespace[]) ArrayUtils.clone(namespaces); } @@ -187,7 +188,7 @@ public String getSchemaLocation() { try { init(); } catch (CrosswalkInternalException e) { - LOG.error(e.toString()); + LOG.error(e::toString); } return schemaLocation; } @@ -220,7 +221,7 @@ public Element disseminateElement(Context context, DSpaceObject dso, } 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 Element disseminateElement(Context context, DSpaceObject dso, 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 List disseminateList(Context context, DSpaceObject dso) .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 boolean preferList() { try { init(); } catch (CrosswalkInternalException e) { - LOG.error(e.toString()); + LOG.error(e::toString); } return preferList; } @@ -312,7 +313,7 @@ public boolean preferList() { /** * 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 @@ private static String checkedString(String value) { 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 static void main(String[] argv) throws Exception { 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 static void main(String[] argv) throws Exception { 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 49d3527a358a..7cba88c0baf3 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 @@ -11,11 +11,13 @@ import java.util.Date; import java.util.Iterator; import java.util.List; +import java.util.UUID; 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; @@ -27,12 +29,11 @@ * @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: @@ -45,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. @@ -55,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 @@ -70,19 +71,40 @@ public Iterator findByLastModifiedSince(Context context, Date since) * @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 findByAuthorityValue(Context context, MetadataField metadataField, String authority, + /** + * 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; + + 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; /** @@ -95,7 +117,7 @@ public Iterator findArchivedByCollection(Context context, Collection colle * @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; /** @@ -106,11 +128,11 @@ public Iterator findArchivedByCollectionExcludingOwning(Context context, C * @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; /** @@ -123,7 +145,8 @@ public Iterator findAllByCollection(Context context, Collection collection * @return item count * @throws SQLException if database error */ - public int countItems(Context context, Collection collection, boolean includeArchived, boolean includeWithdrawn) + int countItems(Context context, Collection collection, boolean includeArchived, boolean includeWithdrawn, + boolean discoverable) throws SQLException; /** @@ -139,8 +162,8 @@ public int countItems(Context context, Collection collection, boolean includeArc * @return item count * @throws SQLException if database error */ - public int countItems(Context context, List collections, boolean includeArchived, - boolean includeWithdrawn) throws SQLException; + int countItems(Context context, List collections, boolean includeArchived, + boolean includeWithdrawn, boolean discoverable) throws SQLException; /** * Get all Items installed or withdrawn, discoverable, and modified since a Date. @@ -153,7 +176,7 @@ public int countItems(Context context, List collections, boolean inc * @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; @@ -175,7 +198,8 @@ public Iterator findAll(Context context, boolean archived, * @return count of items * @throws SQLException if database error */ - int countItems(Context context, boolean includeArchived, boolean includeWithdrawn) throws SQLException; + int countItems(Context context, boolean includeArchived, boolean includeWithdrawn, + boolean discoverable) throws SQLException; /** * Count number of items from the specified submitter based on specific status flags @@ -187,7 +211,8 @@ public Iterator findAll(Context context, boolean archived, * @return count of items * @throws SQLException if database error */ - public int countItems(Context context, EPerson submitter, boolean includeArchived, boolean includeWithdrawn) + int countItems(Context context, EPerson submitter, boolean includeArchived, boolean includeWithdrawn, + boolean discoverable) 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 0e051625aaee..25f102f6def4 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 @@ -12,20 +12,27 @@ import java.util.Iterator; 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 java.util.UUID; +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; import org.dspace.core.Context; +import org.dspace.core.UUIDIterator; /** * Hibernate implementation of the Database Access Object interface class for the Bitstream object. @@ -76,48 +83,74 @@ public List findBitstreamsWithNoRecentChecksum(Context context) throw @Override public Iterator findByCommunity(Context context, Community community) throws SQLException { - Query query = createQuery(context, "select b from Bitstream b " + - "join b.bundles bitBundles " + - "join bitBundles.items item " + - "join item.collections itemColl " + - "join itemColl.communities community " + - "WHERE :community IN community"); - - query.setParameter("community", community); - - return iterate(query); + // 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))); + + // 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); } @Override public Iterator findByCollection(Context context, Collection collection) throws SQLException { - Query query = createQuery(context, "select b from Bitstream b " + - "join b.bundles bitBundles " + - "join bitBundles.items item " + - "join item.collections c " + - "WHERE :collection IN c"); - - query.setParameter("collection", collection); - - return iterate(query); + // 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))); + + // 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); } @Override public Iterator findByItem(Context context, Item item) throws SQLException { - Query query = createQuery(context, "select b from Bitstream b " + - "join b.bundles bitBundles " + - "join bitBundles.items item " + - "WHERE :item IN item"); - - query.setParameter("item", item); - - return iterate(query); + // 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))); + + // 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); } @Override public Iterator findByStoreNumber(Context context, Integer storeNumber) throws SQLException { - Query query = createQuery(context, "select b from Bitstream b where b.storeNumber = :storeNumber"); + Query query = createQuery(context, "select b.id from Bitstream b where b.storeNumber = :storeNumber"); query.setParameter("storeNumber", storeNumber); - return iterate(query); + @SuppressWarnings("unchecked") + List uuids = query.getResultList(); + return new UUIDIterator(context, uuids, Bitstream.class, this); } @Override 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 4d9283bbec4d..eaf58fbb887a 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 @@ 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 List findNonInternal(Context context) throws SQLExceptio ); - List 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 List findByFileExtension(Context context, String extensi public List 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 c0ef6ea42fce..841da319f0b2 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.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 List findAll(Context context, MetadataField order, Integer li 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); @@ -159,7 +159,8 @@ public List findAuthorizedByGroup(Context context, EPerson ePerson, @Override public List findCollectionsWithSubscribers(Context context) throws SQLException { - return list(createQuery(context, "SELECT DISTINCT col FROM Subscription s join s.collection col")); + return list(createQuery(context, "SELECT DISTINCT c FROM Collection c JOIN Subscription s ON c.id = " + + "s.dSpaceObject")); } @Override 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 7a3750485151..5712b898598e 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 @@ 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 List findAll(Context context, MetadataField sortField, Integer 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 List findAllNoParent(Context context, MetadataField sortField) 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 489f4cd0667d..32af7ed35c31 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 @@ 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 List getEntityTypesByNames(Context context, List name @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 5c840f68e998..bd042648384b 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,25 +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 javax.persistence.Query; -import javax.persistence.TemporalType; -import javax.persistence.criteria.CriteriaBuilder; -import javax.persistence.criteria.CriteriaQuery; -import javax.persistence.criteria.Root; - +import java.util.UUID; + +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.dspace.util.JpaCriteriaBuilderKit; /** * Hibernate implementation of the Database Access Object interface class for the Item object. @@ -39,33 +51,38 @@ public class ItemDAOImpl extends AbstractHibernateDSODAO implements ItemDA private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(ItemDAOImpl.class); protected ItemDAOImpl() { - super(); } @Override public Iterator findAll(Context context, boolean archived) throws SQLException { - Query query = createQuery(context, "FROM Item WHERE inArchive=:in_archive ORDER BY id"); + Query query = createQuery(context, "SELECT i.id FROM Item i WHERE inArchive=:in_archive ORDER BY id"); query.setParameter("in_archive", archived); - return iterate(query); + @SuppressWarnings("unchecked") + List uuids = query.getResultList(); + return new UUIDIterator(context, uuids, Item.class, this); } @Override public Iterator findAll(Context context, boolean archived, int limit, int offset) throws SQLException { - Query query = createQuery(context, "FROM Item WHERE inArchive=:in_archive ORDER BY id"); + Query query = createQuery(context, "SELECT i.id FROM Item i WHERE inArchive=:in_archive ORDER BY id"); query.setParameter("in_archive", archived); query.setFirstResult(offset); query.setMaxResults(limit); - return iterate(query); + @SuppressWarnings("unchecked") + List uuids = query.getResultList(); + return new UUIDIterator(context, uuids, Item.class, this); } @Override public Iterator findAll(Context context, boolean archived, boolean withdrawn) throws SQLException { Query query = createQuery(context, - "FROM Item WHERE inArchive=:in_archive or withdrawn=:withdrawn ORDER BY id"); + "SELECT i.id FROM Item i WHERE inArchive=:in_archive or withdrawn=:withdrawn ORDER BY id"); query.setParameter("in_archive", archived); query.setParameter("withdrawn", withdrawn); - return iterate(query); + @SuppressWarnings("unchecked") + List uuids = query.getResultList(); + return new UUIDIterator(context, uuids, Item.class, this); } @Override @@ -74,12 +91,14 @@ public Iterator findAllRegularItems(Context context) throws SQLException { // It does not include workspace, workflow or template items. Query query = createQuery( context, - "SELECT i FROM Item as i " + + "SELECT i.id FROM Item as i " + "LEFT JOIN Version as v ON i = v.item " + "WHERE i.inArchive=true or i.withdrawn=true or (i.inArchive=false and v.id IS NOT NULL) " + "ORDER BY i.id" ); - return iterate(query); + @SuppressWarnings("unchecked") + List uuids = query.getResultList(); + return new UUIDIterator(context, uuids, Item.class, this); } @Override @@ -87,12 +106,12 @@ public Iterator findAll(Context context, boolean archived, boolean withdrawn, boolean discoverable, Date lastModified) throws SQLException { StringBuilder queryStr = new StringBuilder(); - queryStr.append("SELECT i FROM Item i"); + queryStr.append("SELECT i.id FROM Item i"); queryStr.append(" WHERE (inArchive = :in_archive OR withdrawn = :withdrawn)"); 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"); @@ -103,16 +122,20 @@ public Iterator findAll(Context context, boolean archived, if (lastModified != null) { query.setParameter("last_modified", lastModified, TemporalType.TIMESTAMP); } - return iterate(query); + @SuppressWarnings("unchecked") + List uuids = query.getResultList(); + return new UUIDIterator(context, uuids, Item.class, this); } @Override public Iterator findBySubmitter(Context context, EPerson eperson) throws SQLException { Query query = createQuery(context, - "FROM Item WHERE inArchive=:in_archive and submitter=:submitter ORDER BY id"); + "SELECT i.id FROM Item i WHERE inArchive=:in_archive and submitter=:submitter ORDER BY id"); query.setParameter("in_archive", true); query.setParameter("submitter", eperson); - return iterate(query); + @SuppressWarnings("unchecked") + List uuids = query.getResultList(); + return new UUIDIterator(context, uuids, Item.class, this); } @Override @@ -121,16 +144,18 @@ public Iterator findBySubmitter(Context context, EPerson eperson, boolean if (!retrieveAllItems) { return findBySubmitter(context, eperson); } - Query query = createQuery(context, "FROM Item WHERE submitter=:submitter ORDER BY id"); + Query query = createQuery(context, "SELECT i.id FROM Item i WHERE submitter=:submitter ORDER BY id"); query.setParameter("submitter", eperson); - return iterate(query); + @SuppressWarnings("unchecked") + List uuids = query.getResultList(); + return new UUIDIterator(context, uuids, Item.class, this); } @Override public Iterator findBySubmitter(Context context, EPerson eperson, MetadataField metadataField, int limit) throws SQLException { StringBuilder query = new StringBuilder(); - query.append("SELECT item FROM Item as item "); + query.append("SELECT item.id FROM Item as item "); addMetadataLeftJoin(query, Item.class.getSimpleName().toLowerCase(), Collections.singletonList(metadataField)); query.append(" WHERE item.inArchive = :in_archive"); query.append(" AND item.submitter =:submitter"); @@ -142,13 +167,15 @@ public Iterator findBySubmitter(Context context, EPerson eperson, Metadata hibernateQuery.setParameter("in_archive", true); hibernateQuery.setParameter("submitter", eperson); hibernateQuery.setMaxResults(limit); - return iterate(hibernateQuery); + @SuppressWarnings("unchecked") + List uuids = hibernateQuery.getResultList(); + return new UUIDIterator(context, uuids, Item.class, this); } @Override public Iterator findByMetadataField(Context context, MetadataField metadataField, String value, boolean inArchive) throws SQLException { - String hqlQueryString = "SELECT item FROM Item as item join item.metadata metadatavalue " + + String hqlQueryString = "SELECT item.id FROM Item as item join item.metadata metadatavalue " + "WHERE item.inArchive=:in_archive AND metadatavalue.metadataField = :metadata_field"; if (value != null) { hqlQueryString += " AND STR(metadatavalue.value) = :text_value"; @@ -160,61 +187,171 @@ public Iterator findByMetadataField(Context context, MetadataField metadat if (value != null) { query.setParameter("text_value", value); } - return iterate(query); + @SuppressWarnings("unchecked") + List uuids = query.getResultList(); + return new UUIDIterator(context, uuids, Item.class, this); + } + + @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 + 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); + } + + private List toPredicates(CriteriaBuilder criteriaBuilder, CriteriaQuery query, + Root root, List queryPredicates, + List collectionUuids, String regexClause) { + List predicates = new ArrayList<>(); + + if (!collectionUuids.isEmpty()) { + 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)); + } + + 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: null"); + continue; + } + + if (op.getUsesRegex()) { + if (regexClause.isEmpty()) { + log.warn("Skipping Unsupported Regex Operator: " + op); + continue; + } + } + + 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 (!predicate.getFields().isEmpty()) { + In inFields = criteriaBuilder.in(mvRoot.get(MetadataValue_.METADATA_FIELD)); + predicate.getFields().forEach(inFields::value); + mvPredicates.add(inFields); + } + + JpaCriteriaBuilderKit jpaKit = new JpaCriteriaBuilderKit<>(criteriaBuilder, mvQuery, mvRoot); + mvPredicates.add(op.buildJpaPredicate(predicate.getValue(), regexClause, jpaKit)); + + 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 { + predicates.add(criteriaBuilder.exists(mvQuery)); + } + } + + log.debug(String.format("Running custom query with %d filters", queryPredicates.size())); + return predicates; } @Override public Iterator findByAuthorityValue(Context context, MetadataField metadataField, String authority, boolean inArchive) throws SQLException { Query query = createQuery(context, - "SELECT item FROM Item as item join item.metadata metadatavalue " + + "SELECT item.id FROM Item as item join item.metadata metadatavalue " + "WHERE item.inArchive=:in_archive AND metadatavalue.metadataField = :metadata_field AND " + "metadatavalue.authority = :authority ORDER BY item.id"); query.setParameter("in_archive", inArchive); query.setParameter("metadata_field", metadataField); query.setParameter("authority", authority); - return iterate(query); + @SuppressWarnings("unchecked") + List uuids = query.getResultList(); + return new UUIDIterator(context, uuids, Item.class, this); } @Override public Iterator findArchivedByCollection(Context context, Collection collection, Integer limit, Integer offset) throws SQLException { - Query query = createQuery(context, - "select i 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); } if (limit != null) { query.setMaxResults(limit); } - return iterate(query); + @SuppressWarnings("unchecked") + List uuids = query.getResultList(); + return new UUIDIterator(context, uuids, Item.class, this); } @Override 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)), @@ -224,55 +361,78 @@ public int countArchivedByCollectionExcludingOwning(Context context, Collection @Override public Iterator findAllByCollection(Context context, Collection collection) throws SQLException { - Query query = createQuery(context, - "select i from Item i join i.collections c WHERE :collection IN c ORDER BY i.id"); - query.setParameter("collection", collection); - - return iterate(query); + // 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); } @Override public Iterator findAllByCollection(Context context, Collection collection, Integer limit, Integer offset) throws SQLException { - Query query = createQuery(context, - "select i 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); } if (limit != null) { query.setMaxResults(limit); } - - return iterate(query); + @SuppressWarnings("unchecked") + List uuids = query.getResultList(); + return new UUIDIterator(context, uuids, Item.class, this); } @Override - public int countItems(Context context, Collection collection, boolean includeArchived, boolean includeWithdrawn) + 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"); - query.setParameter("collection", collection); - query.setParameter("in_archive", includeArchived); - query.setParameter("withdrawn", includeWithdrawn); - - 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 public int countItems(Context context, List collections, boolean includeArchived, - boolean includeWithdrawn) throws SQLException { + boolean includeWithdrawn, boolean discoverable) throws SQLException { if (collections.size() == 0) { return 0; } Query query = createQuery(context, "select count(distinct i) from Item i " + "join i.collections collection " + - "WHERE collection IN (:collections) AND i.inArchive=:in_archive AND i.withdrawn=:withdrawn"); + "WHERE collection IN (:collections) AND i.inArchive=:in_archive AND i.withdrawn=:withdrawn AND " + + "discoverable=:discoverable"); query.setParameter("collections", collections); query.setParameter("in_archive", includeArchived); query.setParameter("withdrawn", includeWithdrawn); + query.setParameter("discoverable", discoverable); return count(query); } @@ -281,9 +441,11 @@ public int countItems(Context context, List collections, boolean inc public Iterator findByLastModifiedSince(Context context, Date since) throws SQLException { Query query = createQuery(context, - "SELECT i 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); - return iterate(query); + @SuppressWarnings("unchecked") + List uuids = query.getResultList(); + return new UUIDIterator(context, uuids, Item.class, this); } @Override @@ -292,24 +454,29 @@ public int countRows(Context context) throws SQLException { } @Override - public int countItems(Context context, boolean includeArchived, boolean includeWithdrawn) throws SQLException { + public int countItems(Context context, boolean includeArchived, boolean includeWithdrawn, + boolean discoverable) throws SQLException { Query query = createQuery(context, "SELECT count(*) FROM Item i " + - "WHERE i.inArchive=:in_archive AND i.withdrawn=:withdrawn"); + "WHERE i.inArchive=:in_archive AND i.withdrawn=:withdrawn AND discoverable=:discoverable"); query.setParameter("in_archive", includeArchived); query.setParameter("withdrawn", includeWithdrawn); + query.setParameter("discoverable", discoverable); return count(query); } @Override - public int countItems(Context context, EPerson submitter, boolean includeArchived, boolean includeWithdrawn) + public int countItems(Context context, EPerson submitter, boolean includeArchived, boolean includeWithdrawn, + boolean discoverable) throws SQLException { Query query = createQuery(context, "SELECT count(*) FROM Item i join i.submitter submitter " + - "WHERE i.inArchive=:in_archive AND i.withdrawn=:withdrawn AND submitter = :submitter"); + "WHERE i.inArchive=:in_archive AND i.withdrawn=:withdrawn AND submitter = :submitter AND " + + "discoverable=:discoverable"); query.setParameter("submitter", submitter); query.setParameter("in_archive", includeArchived); query.setParameter("withdrawn", includeWithdrawn); + query.setParameter("discoverable", discoverable); return count(query); } 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 e64aaa4dd29f..ddbde361d5ac 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.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 List findAll(Context context, Class clazz) 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 71eb487b8395..4630bed90b16 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 @@ 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 List findAll(Context context, Class clazz) throws SQLExce 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 f37ced9ab7d4..dc624c98c6aa 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 @@ 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 Iterator findItemValuesByFieldAndValue(Context context, 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 void deleteByMetadataField(Context context, MetadataField metadataField) 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 d719b5006c14..829dd3280078 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.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 List findAll(Context context, int limit, int offset) throws SQLE 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 int countTotalWithParameters(Context context, ProcessQueryParameterContai 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 List findByUser(Context context, EPerson user, int limit, int of 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 List findByUser(Context context, EPerson user, int limit, int of @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 e2f84bc1cb64..43bbc15c31b8 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.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 int countByItem( 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 List findByTypeName(Context context, String typeName, Integ 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 int countByRelationshipType(Context context, RelationshipType relationshi @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 int countByItemAndRelationshipType( 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 int countByTypeName(Context context, String typeName) 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 int countByTypeName(Context context, String typeName) 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 List findByItemAndRelationshipTypeAndList(Context context, @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 7fff2a1f57da..7b0e33fd41d9 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 @@ 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 List findByEntityType(Context context, EntityType entit .equal(relationshipTypeRoot.get(RelationshipType_.rightType), entityType) ) ); - List 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 List findByEntityType(Context context, EntityType entit @Override public int countByEntityType(Context context, EntityType entityType) throws SQLException { CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); - CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, RelationshipType.class); + CriteriaQuery 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 8889909b1a49..ebaa78ae3788 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 138451365522..0862a81e8678 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.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 List findAll(Context context) throws SQLException { 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 List findAll(Context context, Integer limit, Integer offse 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 2ef0ab0b2306..952aa799d2d1 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 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 MetadataValueDTO(String schema, String element, String qualifier, String * @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 int getConfidence() { 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 0b06b34038e1..3a897081f07c 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.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 InProgressSubmissionService getInProgressSubmissionService(InProgressSubm } } + /** + * 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 e970f0bdab12..3c3c2bf162bb 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.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 EntityService getEntityService() { 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 a878d69e6ed8..96ec82726775 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 static Map, Filter> getIdentifierFilters(bool 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/AbstractMETSDisseminator.java b/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSDisseminator.java index 685fd9000da8..fd50ec8023e2 100644 --- a/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSDisseminator.java +++ b/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSDisseminator.java @@ -628,6 +628,7 @@ protected MdSec makeMdSec(Context context, DSpaceObject dso, Class mdSecClass, // Disseminate crosswalk output to an outputstream ByteArrayOutputStream disseminateOutput = new ByteArrayOutputStream(); sxwalk.disseminate(context, dso, disseminateOutput); + disseminateOutput.close(); // Convert output to an inputstream, so we can write to manifest or Zip file ByteArrayInputStream crosswalkedStream = new ByteArrayInputStream( disseminateOutput.toByteArray()); diff --git a/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSIngester.java b/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSIngester.java index 98277c4f9c06..0ed0abe21825 100644 --- a/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSIngester.java +++ b/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSIngester.java @@ -636,6 +636,9 @@ protected DSpaceObject replaceObject(Context context, DSpaceObject dso, owningCollection = inProgressSubmission.getCollection(); } + itemService.populateWithTemplateItemMetadata(context, owningCollection, params.useCollectionTemplate(), + item); + addLicense(context, item, license, owningCollection , params); 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 e7be7ab51190..e231c30809b0 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 void crosswalkObjectDmd(Context context, DSpaceObject dso, } } - // 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 6c7baad45497..c9105f8afe14 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 @@ private void crosswalkPDF(Context context, Item item, InputStream metadata) * 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 c5ebffc9f887..6c82c639a02f 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 @@ * 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 9b1d9f4f4980..c28bd98e1844 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 @@ * 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 b472a52c3bad..f2b95118675b 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 @@ 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/PackageUtils.java b/dspace-api/src/main/java/org/dspace/content/packager/PackageUtils.java index 9e7d870076aa..9a8ae4606487 100644 --- a/dspace-api/src/main/java/org/dspace/content/packager/PackageUtils.java +++ b/dspace-api/src/main/java/org/dspace/content/packager/PackageUtils.java @@ -503,7 +503,7 @@ public static DSpaceObject createDSpaceObject(Context context, DSpaceObject pare wsi = workspaceItemService.create(context, (Collection)parent, params.useCollectionTemplate()); } else { wsi = workspaceItemService.create(context, (Collection)parent, - uuid, params.useCollectionTemplate()); + uuid, params.useCollectionTemplate(), false); } // Please note that we are returning an Item which is *NOT* yet in the Archive, diff --git a/dspace-api/src/main/java/org/dspace/content/packager/RoleDisseminator.java b/dspace-api/src/main/java/org/dspace/content/packager/RoleDisseminator.java index f627779af8dc..15e9f1b14494 100644 --- a/dspace-api/src/main/java/org/dspace/content/packager/RoleDisseminator.java +++ b/dspace-api/src/main/java/org/dspace/content/packager/RoleDisseminator.java @@ -431,7 +431,7 @@ protected void writeEPerson(EPerson eperson, XMLStreamWriter writer, if (emitPassword) { PasswordHash password = ePersonService.getPasswordHash(eperson); - if (null != password) { + if (null != password && password.getHashString() != null) { writer.writeStartElement(PASSWORD_HASH); String algorithm = password.getAlgorithm(); 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 2ce3f50a3cbc..ca27abe20614 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.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.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 @@ * @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 @@ void ingestDocument(Context context, DSpaceObject parent, // 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 @@ void ingestDocument(Context context, DSpaceObject parent, // 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 8effabf28435..c22428f11a96 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.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 0a56105ead40..3a865d9d63fd 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 @@ -15,7 +15,6 @@ import java.util.UUID; import org.dspace.authorize.AuthorizeException; -import org.dspace.browse.ItemCountException; import org.dspace.content.Bitstream; import org.dspace.content.Collection; import org.dspace.content.Community; @@ -484,27 +483,12 @@ public int countCollectionsWithSubmit(String q, Context context, Community commu public int countCollectionsWithSubmit(String q, Context context, Community community, String entityType) throws SQLException, SearchServiceException; - /** - * Returns a list of all collections for a specific entity type. - * NOTE: for better performance, this method retrieves its results from an index (cache) - * and does not query the database directly. - * This means that results may be stale or outdated until - * https://github.com/DSpace/DSpace/issues/2853 is resolved." - * - * @param context DSpace Context - * @param entityType limit the returned collection to those related to given entity type - * @return list of collections found - * @throws SearchServiceException if search error - */ - public List findAllCollectionsByEntityType(Context context, String entityType) - throws SearchServiceException; - /** * Returns total collection archived items * + * @param context DSpace context * @param collection Collection * @return total collection archived items - * @throws ItemCountException */ - int countArchivedItems(Collection collection) throws ItemCountException; + int countArchivedItems(Context context, Collection collection); } 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 c089bcec8df1..5734d5a9d9ab 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 @@ -14,7 +14,6 @@ import java.util.UUID; import org.dspace.authorize.AuthorizeException; -import org.dspace.browse.ItemCountException; import org.dspace.content.Bitstream; import org.dspace.content.Collection; import org.dspace.content.Community; @@ -182,7 +181,7 @@ public Bitstream setLogo(Context context, Community community, InputStream is) t /** - * Add an exisiting collection to the community + * Add an existing collection to the community * * @param context context * @param community community @@ -297,9 +296,9 @@ public void removeSubcommunity(Context context, Community parentCommunity, Commu /** * Returns total community archived items * + * @param context DSpace context * @param community Community * @return total community archived items - * @throws ItemCountException */ - int countArchivedItems(Community community) throws ItemCountException; + int countArchivedItems(Context context, 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 000000000000..1f0d3495b1d6 --- /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 d21afd678000..68bb0b15585a 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 @@ 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 43a804cde2eb..47d2d5bdaa88 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 @@ -26,6 +26,7 @@ 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; @@ -41,7 +42,7 @@ 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 @@ -53,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 @@ -66,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, @@ -80,7 +81,19 @@ 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. + * + * @param context DSpace context object + * @param collection Collection (parent) + * @param template if true, the item inherits all collection's template item metadata + * @param item item to populate with template item specified metadata + * @throws SQLException if database error + */ + public void populateWithTemplateItemMetadata (Context context, Collection collection, boolean template, Item item) + throws SQLException; /** * Get all the items in the archive. Only items with the "in archive" flag @@ -90,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 @@ -102,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 @@ -112,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: @@ -126,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 @@ -137,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; /** @@ -152,7 +164,7 @@ public Iterator findBySubmitter(Context context, EPerson eperson) * @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; /** @@ -164,7 +176,7 @@ public Iterator findBySubmitter(Context context, EPerson eperson, boolean * @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; /** @@ -175,7 +187,7 @@ public Iterator findBySubmitterDateSorted(Context context, EPerson eperson * @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. @@ -187,7 +199,7 @@ public Iterator findBySubmitterDateSorted(Context context, EPerson eperson * @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; /** @@ -200,7 +212,7 @@ public Iterator findByCollection(Context context, Collection collection, I * @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; /** @@ -211,7 +223,7 @@ public Iterator findByCollectionMapping(Context context, Collection collec * @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. @@ -223,7 +235,7 @@ public Iterator findByCollectionMapping(Context context, Collection collec * @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; /** @@ -234,7 +246,7 @@ public Iterator findAllByCollection(Context context, Collection collection * @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; /** @@ -244,7 +256,7 @@ public Iterator findInArchiveOrWithdrawnDiscoverableModifiedSince(Context * @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; /** @@ -255,7 +267,7 @@ public Iterator findInArchiveOrWithdrawnNonDiscoverableModifiedSince(Conte * @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. @@ -265,7 +277,7 @@ public Iterator findInArchiveOrWithdrawnNonDiscoverableModifiedSince(Conte * @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 @@ -277,7 +289,7 @@ public Iterator findInArchiveOrWithdrawnNonDiscoverableModifiedSince(Conte * @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; /** @@ -288,7 +300,7 @@ public Iterator findInArchiveOrWithdrawnNonDiscoverableModifiedSince(Conte * @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. @@ -299,7 +311,7 @@ public Iterator findInArchiveOrWithdrawnNonDiscoverableModifiedSince(Conte * @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 @@ -312,7 +324,7 @@ public Iterator findInArchiveOrWithdrawnNonDiscoverableModifiedSince(Conte * @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; /** @@ -325,7 +337,7 @@ public void removeBundle(Context context, Item item, Bundle bundle) throws SQLEx * @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 @@ -340,7 +352,7 @@ public void removeBundle(Context context, Item item, Bundle bundle) throws SQLEx * @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; /** @@ -354,7 +366,7 @@ public Bitstream createSingleBitstream(Context context, InputStream is, Item ite * @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; /** @@ -367,7 +379,7 @@ public Bitstream createSingleBitstream(Context context, InputStream is, Item ite * @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 @@ -382,7 +394,7 @@ public Bitstream createSingleBitstream(Context context, InputStream is, Item ite * @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; /** @@ -394,7 +406,7 @@ public void removeDSpaceLicense(Context context, Item item) throws SQLException, * @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 @@ -405,7 +417,7 @@ public void removeDSpaceLicense(Context context, Item item) throws SQLException, * @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; /** @@ -416,7 +428,7 @@ public void removeDSpaceLicense(Context context, Item item) throws SQLException, * @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 @@ -425,7 +437,7 @@ public void removeDSpaceLicense(Context context, Item item) throws SQLException, * @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 @@ -439,7 +451,7 @@ public void removeDSpaceLicense(Context context, Item item) throws SQLException, * @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; @@ -455,7 +467,7 @@ public void replaceAllItemPolicies(Context context, Item item, List newpolicies) + void replaceAllBitstreamPolicies(Context context, Item item, List newpolicies) throws SQLException, AuthorizeException; @@ -469,7 +481,7 @@ public void replaceAllBitstreamPolicies(Context context, Item item, 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 @@ -686,7 +698,7 @@ public void move(Context context, Item item, Collection from, Collection to, boo * @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 @@ -697,7 +709,7 @@ public void move(Context context, Item item, Collection from, Collection to, boo * @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 @@ -712,7 +724,7 @@ public void move(Context context, Item item, Collection from, Collection to, boo * @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; @@ -727,7 +739,7 @@ public Iterator findArchivedByMetadataField(Context context, String schema * @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; /** @@ -744,10 +756,42 @@ public Iterator findArchivedByMetadataField(Context context, String metada * @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; + /** + * 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 * in the indicated metadata field. @@ -762,12 +806,12 @@ public Iterator findByMetadataField(Context context, * @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; /** @@ -779,7 +823,7 @@ public Iterator findByMetadataFieldAuthority(Context context, String mdStr * @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 @@ -789,7 +833,7 @@ public Iterator findByMetadataFieldAuthority(Context context, String mdStr * @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 @@ -799,7 +843,7 @@ public Iterator findByMetadataFieldAuthority(Context context, String mdStr * @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. @@ -809,7 +853,7 @@ public Iterator findByMetadataFieldAuthority(Context context, String mdStr * @return iterator over items * @throws SQLException if database error */ - public Iterator findByLastModifiedSince(Context context, Date last) + Iterator findByLastModifiedSince(Context context, Date last) throws SQLException; /** @@ -820,7 +864,7 @@ public Iterator findByLastModifiedSince(Context context, Date last) * @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 @@ -830,7 +874,7 @@ public Iterator findByLastModifiedSince(Context context, Date last) * @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 @@ -877,7 +921,7 @@ public Iterator findByLastModifiedSince(Context context, Date last) * @throws SQLException * @throws SearchServiceException */ - public List findItemsWithEdit(Context context, int offset, int limit) + List findItemsWithEdit(Context context, int offset, int limit) throws SQLException, SearchServiceException; /** @@ -887,7 +931,7 @@ public List findItemsWithEdit(Context context, int offset, int limit) * @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 @@ -947,7 +991,7 @@ public List findItemsWithEdit(Context context, int offset, int limit) * 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); /** @@ -955,7 +999,7 @@ public List getMetadata(Item item, String schema, String element, * @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. @@ -963,6 +1007,6 @@ public List getMetadata(Item item, String schema, String element, * @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 719f966e4622..f32e965c6273 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 List findByLatestItemAndRelationshipType( 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/service/WorkspaceItemService.java b/dspace-api/src/main/java/org/dspace/content/service/WorkspaceItemService.java index c8df68e43498..8559bcc61402 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/WorkspaceItemService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/WorkspaceItemService.java @@ -56,6 +56,23 @@ public interface WorkspaceItemService extends InProgressSubmissionServicetrue, the workspace item starts as a copy + * of the collection's template item + * @param isNewVersion whether we are creating a new workspace item version of an existing item + * @return the newly created workspace item + * @throws SQLException if database error + * @throws AuthorizeException if authorization error + */ + public WorkspaceItem create(Context context, Collection collection, boolean template, boolean isNewVersion) + throws AuthorizeException, SQLException; + /** * Create a new workspace item, with a new ID. An Item is also created. The * submitter is the current user in the context. @@ -65,11 +82,13 @@ public WorkspaceItem create(Context context, Collection collection, boolean temp * @param uuid the preferred uuid of the new item (used if restoring an item and retaining old uuid) * @param template if true, the workspace item starts as a copy * of the collection's template item + * @param isNewVersion whether we are creating a new workspace item version of an existing item * @return the newly created workspace item * @throws SQLException if database error * @throws AuthorizeException if authorization error */ - public WorkspaceItem create(Context context, Collection collection, UUID uuid, boolean template) + public WorkspaceItem create(Context context, Collection collection, UUID uuid, boolean template, + boolean isNewVersion) throws AuthorizeException, SQLException; public WorkspaceItem create(Context c, WorkflowItem wfi) throws SQLException, AuthorizeException; 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 b788cbf9fc17..0bc7e3cd2a39 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 void setFields(List fields) { } /** - * 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 000000000000..6c193bb28506 --- /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 000000000000..e22ac0e96fdc --- /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 000000000000..e5fa588140f3 --- /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 000000000000..8823ff31413f --- /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 000000000000..21a39778babf --- /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 000000000000..058d1546d160 --- /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 000000000000..71c4c74a3ecd --- /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 000000000000..3dc9faed1cfc --- /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 000000000000..20c714fcf3b1 --- /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 000000000000..7cd8606f8348 --- /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 000000000000..91c9b78255ad --- /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 000000000000..16c01b8b4808 --- /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 32ad747d765e..b4347f0969d7 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.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 T findByID(Context context, Class clazz, int id) throws SQLException { 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 Iterator iterate(Query query) { 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 int count(Context context, CriteriaQuery criteriaQuery, CriteriaBuilder c /** * 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 List findByX(Context context, Class clazz, Map equals, 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 e9c6b95b7f05..d72a4d619003 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 @@ 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 f730ef6545f1..98bb1b7731a0 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 02a3fee09f8a..877b7a00554f 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 Set getSpecialGroupUuids() { 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 f6df740a53ef..bb434c07cb96 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.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; @@ -395,7 +395,7 @@ public void send() throws MessagingException, IOException { for (String headerName : templateHeaders) { String headerValue = (String) vctx.get(headerName); if ("subject".equalsIgnoreCase(headerName)) { - if (null != headerValue) { + if ((subject == null || subject.isEmpty()) && null != headerValue) { subject = headerValue; } } else if ("charset".equalsIgnoreCase(headerName)) { 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 a04a0ccbdcc8..9835e18ad3cf 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 b371af80eede..a867849077a3 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.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 000000000000..f9bdd1c01424 --- /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 0fc48b908b82..8a1a26d9c75f 100644 --- a/dspace-api/src/main/java/org/dspace/core/I18nUtil.java +++ b/dspace-api/src/main/java/org/dspace/core/I18nUtil.java @@ -96,14 +96,14 @@ private static Locale makeLocale(String localeSpec) { */ public static Locale getEPersonLocale(EPerson ep) { if (ep == null) { - log.error("No EPerson specified, returning default locale"); + log.info("No EPerson specified, returning default locale"); return I18nUtil.getDefaultLocale(); } String lang = ep.getLanguage(); if (StringUtils.isBlank(lang)) { - log.error("No language specified for EPerson " + ep.getID()); + log.info("No language specified for EPerson " + ep.getID() + ", returning default locale"); return I18nUtil.getDefaultLocale(); } @@ -257,7 +257,7 @@ public static String getMessage(String key, Locale locale) { String message = messages.getString(key.trim()); return message; } catch (MissingResourceException e) { - log.error("'" + key + "' translation undefined in locale '" + log.warn("'" + key + "' translation undefined in locale '" + locale.toString() + "'"); return key; } @@ -377,6 +377,22 @@ public static String getEmailFilename(Locale locale, String name) { 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 000000000000..8ae5cddf5b4a --- /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 d895f9a76481..766e72e94125 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.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 @@ * @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 void writeLicenseFile(String licenseFile, 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 @@ protected void init() { 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 00cc0f27664b..5c3f345dfb89 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 static String getHeader(Context context, String action, 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 d2bdf787f818..59b4c43fa866 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.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 @@ * @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 String readNewsFile(String newsFile) { 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 String writeNewsFile(String newsFile, String news) { 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/UUIDIterator.java b/dspace-api/src/main/java/org/dspace/core/UUIDIterator.java new file mode 100644 index 000000000000..28c8d7354169 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/core/UUIDIterator.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.core; + +import java.sql.SQLException; +import java.util.Iterator; +import java.util.List; +import java.util.UUID; + +import com.google.common.collect.AbstractIterator; +import org.dspace.content.DSpaceObject; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Iterator implementation which allows to iterate over items and commit while + * iterating. Using an iterator over previous retrieved UUIDs the iterator doesn't + * get invalidated after a commit that would instead close the database ResultSet + * + * @author Andrea Bollini (andrea.bollini at 4science.com) + * @param class type + */ +public class UUIDIterator extends AbstractIterator { + private Class clazz; + + private Iterator iterator; + + @Autowired + private AbstractHibernateDSODAO dao; + + private Context ctx; + + public UUIDIterator(Context ctx, List uuids, Class clazz, AbstractHibernateDSODAO dao) + throws SQLException { + this.ctx = ctx; + this.clazz = clazz; + this.dao = dao; + this.iterator = uuids.iterator(); + } + + @Override + protected T computeNext() { + try { + if (iterator.hasNext()) { + T item = dao.findByID(ctx, clazz, iterator.next()); + if (item != null) { + return item; + } else { + return computeNext(); + } + } else { + return endOfData(); + } + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + +} 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 ea9ed57eca04..b5423decf2f0 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 000000000000..bc5abaef4efb --- /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 000000000000..847236d11071 --- /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 000000000000..e3d074b1d337 --- /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 000000000000..e76e1f7ec146 --- /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 000000000000..e64120c46a50 --- /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 fbc6eebdb5b8..9bd08bffdbd2 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 @@ -19,6 +19,8 @@ import org.dspace.content.MetadataValue; import org.dspace.curate.AbstractCurationTask; import org.dspace.curate.Curator; +import org.dspace.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; /** * A basic link checker that is designed to be extended. By default this link checker @@ -42,6 +44,9 @@ public class BasicLinkChecker extends AbstractCurationTask { // The log4j logger for this class private static Logger log = org.apache.logging.log4j.LogManager.getLogger(BasicLinkChecker.class); + protected static final ConfigurationService configurationService + = DSpaceServicesFactory.getInstance().getConfigurationService(); + /** * Perform the link checking. @@ -110,7 +115,8 @@ protected List getURLs(Item item) { */ protected boolean checkURL(String url, StringBuilder results) { // Link check the URL - int httpStatus = getResponseStatus(url); + int redirects = 0; + int httpStatus = getResponseStatus(url, redirects); if ((httpStatus >= 200) && (httpStatus < 300)) { results.append(" - " + url + " = " + httpStatus + " - OK\n"); @@ -128,14 +134,24 @@ protected boolean checkURL(String url, StringBuilder results) { * @param url The url to open * @return The HTTP response code (e.g. 200 / 301 / 404 / 500) */ - protected int getResponseStatus(String url) { + protected int getResponseStatus(String url, int redirects) { try { URL theURL = new URL(url); HttpURLConnection connection = (HttpURLConnection) theURL.openConnection(); - int code = connection.getResponseCode(); - connection.disconnect(); + connection.setInstanceFollowRedirects(true); + int statusCode = connection.getResponseCode(); + int maxRedirect = configurationService.getIntProperty("curate.checklinks.max-redirect", 0); + if ((statusCode == HttpURLConnection.HTTP_MOVED_TEMP || statusCode == HttpURLConnection.HTTP_MOVED_PERM || + statusCode == HttpURLConnection.HTTP_SEE_OTHER)) { + connection.disconnect(); + String newUrl = connection.getHeaderField("Location"); + if (newUrl != null && (maxRedirect >= redirects || maxRedirect == -1)) { + redirects++; + return getResponseStatus(newUrl, redirects); + } - return code; + } + return statusCode; } catch (IOException ioe) { // Must be a bad URL @@ -145,7 +161,7 @@ protected int getResponseStatus(String url) { } /** - * 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 fa630029b890..2455ca09530f 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 @@ protected void performItem(Item item) throws SQLException { // 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 @@ protected void performItem(Item item) throws SQLException { // 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 @@ protected void performItem(Item item) throws SQLException { 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 d1fa70565dfd..47fa6ee6452d 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.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.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 int perform(DSpaceObject dso) throws IOException { 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 @@ protected void openSession() throws IOException { 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 @@ protected String getItemHandle(Item item) { 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 21ffdd026055..7b47a9fd90dc 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 @@ 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 @@ */ 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 279204cf575b..14bd42049479 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 @@ 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 @@ */ 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 4d70286e79e0..ece1b7738af3 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 @@ private long runQueue(TaskQueue queue, Curator curator) throws SQLException, Aut * 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 @@ private void initGeneralLineOptionsAndCheckIfValid() { // 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 bd3ee3cffb9c..bdf444357668 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 @@ 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 @@ */ 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 Appendable append(CharSequence cs) 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 Appendable append(char c) 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 7f63cb76dd90..8d8ab491d2de 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 @@ * 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 00e91ee1fb40..ec32ff92f9a2 100644 --- a/dspace-api/src/main/java/org/dspace/curate/XmlWorkflowCuratorServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/curate/XmlWorkflowCuratorServiceImpl.java @@ -140,13 +140,14 @@ public boolean curate(Curator curator, Context c, XmlWorkflowItem wfi) 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"; @@ -183,14 +184,14 @@ public boolean curate(Curator curator, Context c, XmlWorkflowItem wfi) } } 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 492642f60c57..1168bbd283d2 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 21468def6866..58abf18698a1 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.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 b70e9162f7a1..3479c25bf367 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/IndexClient.java +++ b/dspace-api/src/main/java/org/dspace/discovery/IndexClient.java @@ -27,6 +27,7 @@ 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; @@ -109,7 +110,7 @@ public void internalRun() throws Exception { .getHandleService().resolveToObject(context, param); if (dso != null) { final IndexFactory indexableObjectService = IndexObjectFactoryFactory.getInstance(). - getIndexFactoryByType(String.valueOf(dso.getType())); + getIndexFactoryByType(Constants.typeText[dso.getType()]); indexableObject = indexableObjectService.findIndexableObject(context, dso.getID().toString()); } } diff --git a/dspace-api/src/main/java/org/dspace/discovery/IndexClientOptions.java b/dspace-api/src/main/java/org/dspace/discovery/IndexClientOptions.java index 0de5b22d0655..b3f72cb772dc 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/IndexClientOptions.java +++ b/dspace-api/src/main/java/org/dspace/discovery/IndexClientOptions.java @@ -94,7 +94,7 @@ protected static Options constructOptions() { 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 80602ac80459..611200e62a5d 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 void consume(Context ctx, Event event) throws Exception { 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 void consume(Context ctx, Event event) throws Exception { 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 2ef5affa47b7..6a80d5282001 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 @@ void reIndexContent(Context context, IndexableObject dso) /** * 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 60bf52836bef..e94b1f2fb71b 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 static DiscoveryConfiguration getDiscoveryConfiguration() { /** * 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 f31feab6123a..6304f39a8ca9 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 cd3797e3e34e..775908df4d58 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.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 List search(Context context, String query, String orderf } 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 String toSortFieldIndex(String metadataField, String type) { * 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, @@ -1411,8 +1411,6 @@ protected String transformFacetField(DiscoverFacetField facetFieldConfig, String } else { return field + "_acid"; } - } else if (facetFieldConfig.getType().equals(DiscoveryConfigurationParameters.TYPE_STANDARD)) { - return field; } else { return 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 000000000000..001d1c51a4dd --- /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 746a0cb83214..dda041c7b850 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 void additionalIndex(Context context, IndexableObject indexableObject, So // 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 void additionalIndex(Context context, IndexableObject indexableObject, So } } - // 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 ac64573c5d05..8767ea83da06 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 String getType() { * 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 cd1a4eecb8d4..066e3c73e9ee 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 @@ 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 000000000000..86d600b6c97e --- /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 000000000000..7752ae58627f --- /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 3d4eab125f92..4fb95962b2e6 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 @@ 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 @@ protected AccountServiceImpl() { * @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 void sendRegistrationInfo(Context context, String email) * @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 0ab66aea5c2e..b213675b163e 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.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 da83a1cafd37..f79efebf5ffc 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.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 @@ * @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 String getLanguage() { /** * 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 feefe65717df..8970bb2849d9 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 @@ 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 b9ac740685bd..40e0859be17d 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/EPersonServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/EPersonServiceImpl.java @@ -343,11 +343,11 @@ public void delete(Context context, EPerson ePerson) throws SQLException, Author 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); @@ -451,7 +451,7 @@ public void delete(Context context, EPerson ePerson, boolean cascade) 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))); } 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 67655e0e0aaf..24b44b8149a4 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 @@ 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 @@ * @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 09bdf34d4cad..a1c12371f5ff 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 730053e42ce2..3fb20e2f1e6f 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 void addMember(Context context, Group group, EPerson e) { 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 void removeMember(Context context, Group group, EPerson ePerson) throws S 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 17340c5a58a9..ba34df4dae66 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 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 @@ * @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 PasswordHash(String password) { 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 boolean matches(String secret) { 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 @@ private synchronized byte[] generateSalt() { 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 f4f41bdff2eb..0d0c5e7db851 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 5db63740f477..0f473a5a750d 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 @@ 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 7526535d7fcd..8d372966b10a 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 87d6c5869b09..7d8e0720c45f 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.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 83fb48aaf03d..1cd359188ca3 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 @@ 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 6aea9ecd8d67..abd9fc830fa4 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.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 4a15dcc86796..63e87400ce36 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 6c36211f310c..d3d4748728ce 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.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 @@ protected SubscriptionDAOImpl() { 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 List findByEPersonAndDso(Context context, EPerson eperson, 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 List findByEPersonAndDso(Context context, EPerson eperson, 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 void deleteByDSOAndEPerson(Context context, DSpaceObject dSpaceObject, EP 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 List findAllOrderedByDSO(Context context, Integer limit, In 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 List findAllSubscriptionsBySubscriptionTypeAndFrequency(Con 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 c8ecb0cc67d4..637b81c41da2 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 @@ 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 2afec161a672..b3583155f354 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.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 af8b2d45713f..1f90381d00aa 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 1afc0a22f5f0..575c42250b52 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 Dispatcher getDispatcher(String name) { 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 @@ protected class DispatcherPoolFactory implements KeyedPooledObjectFactory 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 5c032c3f74f7..d8b26e891beb 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 index c96fad1de01c..27688df6c758 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.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; @@ -278,7 +277,7 @@ public Response search(String path, int page, int size) { 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/model/ExternalDataObject.java b/dspace-api/src/main/java/org/dspace/external/model/ExternalDataObject.java index eac9921df6cc..66b0b18fbc59 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 @@ 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 String getValue() { 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/OrcidPublicationDataProvider.java b/dspace-api/src/main/java/org/dspace/external/provider/impl/OrcidPublicationDataProvider.java index 4fdf15a8a3ad..05dabe0a5bdc 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.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.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 @@ */ 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 @@ private ExternalDataObject convertToExternalDataObject(String orcid, Work work) 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 @@ private boolean doesNotContains(ExternalDataObject externalDataObject, Metadatum 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 9e61b9ac2ac0..11d8f692af19 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 @@ /** * 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 756b8654f285..578db6c56749 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 @@ 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 53423395e3fa..4c75a778d1fe 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/google/GoogleAccount.java b/dspace-api/src/main/java/org/dspace/google/GoogleAccount.java deleted file mode 100644 index a24c02a2e1c3..000000000000 --- a/dspace-api/src/main/java/org/dspace/google/GoogleAccount.java +++ /dev/null @@ -1,144 +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.google; - -import java.io.File; -import java.util.HashSet; -import java.util.Set; - -import com.google.api.client.auth.oauth2.Credential; -import com.google.api.client.googleapis.auth.oauth2.GoogleCredential; -import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport; -import com.google.api.client.http.HttpTransport; -import com.google.api.client.json.JsonFactory; -import com.google.api.client.json.jackson2.JacksonFactory; -import com.google.api.services.analytics.Analytics; -import com.google.api.services.analytics.AnalyticsScopes; -import org.apache.logging.log4j.Logger; -import org.dspace.services.factory.DSpaceServicesFactory; - -/** - * User: Robin Taylor - * Date: 11/07/2014 - * Time: 13:23 - */ - -public class GoogleAccount { - - // Read from config - private String applicationName; - private String tableId; - private String emailAddress; - private String certificateLocation; - - // Created from factories - private JsonFactory jsonFactory; - private HttpTransport httpTransport; - - // The Google stuff - private Credential credential; - private Analytics client; - - private volatile static GoogleAccount uniqueInstance; - - private static Logger log = org.apache.logging.log4j.LogManager.getLogger(GoogleAccount.class); - - - private GoogleAccount() { - applicationName = DSpaceServicesFactory.getInstance().getConfigurationService() - .getProperty("google-analytics.application.name"); - tableId = DSpaceServicesFactory.getInstance().getConfigurationService() - .getProperty("google-analytics.table.id"); - emailAddress = DSpaceServicesFactory.getInstance().getConfigurationService() - .getProperty("google-analytics.account.email"); - certificateLocation = DSpaceServicesFactory.getInstance().getConfigurationService() - .getProperty("google-analytics.certificate.location"); - - jsonFactory = JacksonFactory.getDefaultInstance(); - - try { - httpTransport = GoogleNetHttpTransport.newTrustedTransport(); - credential = authorize(); - } catch (Exception e) { - throw new RuntimeException("Error initialising Google Analytics client", e); - } - - // Create an Analytics instance - client = new Analytics.Builder(httpTransport, jsonFactory, credential).setApplicationName(applicationName) - .build(); - - log.info("Google Analytics client successfully initialised"); - } - - public static GoogleAccount getInstance() { - if (uniqueInstance == null) { - synchronized (GoogleAccount.class) { - if (uniqueInstance == null) { - uniqueInstance = new GoogleAccount(); - } - } - } - - return uniqueInstance; - } - - private Credential authorize() throws Exception { - Set scopes = new HashSet(); - scopes.add(AnalyticsScopes.ANALYTICS); - scopes.add(AnalyticsScopes.ANALYTICS_EDIT); - scopes.add(AnalyticsScopes.ANALYTICS_MANAGE_USERS); - scopes.add(AnalyticsScopes.ANALYTICS_PROVISION); - scopes.add(AnalyticsScopes.ANALYTICS_READONLY); - - credential = new GoogleCredential.Builder() - .setTransport(httpTransport) - .setJsonFactory(jsonFactory) - .setServiceAccountId(emailAddress) - .setServiceAccountScopes(scopes) - .setServiceAccountPrivateKeyFromP12File(new File(certificateLocation)) - .build(); - - return credential; - } - - - public String getApplicationName() { - return applicationName; - } - - public String getTableId() { - return tableId; - } - - public String getEmailAddress() { - return emailAddress; - } - - public String getCertificateLocation() { - return certificateLocation; - } - - public JsonFactory getJsonFactory() { - return jsonFactory; - } - - public HttpTransport getHttpTransport() { - return httpTransport; - } - - public Credential getCredential() { - return credential; - } - - public Analytics getClient() { - return client; - } - -} - 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 c1c59acf4a63..f5b27fcbd496 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.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 @@ /** * 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 @@ private GoogleAnalyticsEvent createGoogleAnalyticsEvent(UsageEvent usageEvent) { /** * 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/GoogleQueryManager.java b/dspace-api/src/main/java/org/dspace/google/GoogleQueryManager.java deleted file mode 100644 index 2719aef04da4..000000000000 --- a/dspace-api/src/main/java/org/dspace/google/GoogleQueryManager.java +++ /dev/null @@ -1,49 +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.google; - -import java.io.IOException; - -import com.google.api.services.analytics.model.GaData; - - -/** - * User: Robin Taylor - * Date: 20/08/2014 - * Time: 09:26 - */ -public class GoogleQueryManager { - - public GaData getPageViews(String startDate, String endDate, String handle) throws IOException { - return GoogleAccount.getInstance().getClient().data().ga().get( - GoogleAccount.getInstance().getTableId(), - startDate, - endDate, - "ga:pageviews") // Metrics. - .setDimensions("ga:year,ga:month") - .setSort("-ga:year,-ga:month") - .setFilters("ga:pagePath=~/handle/" + handle + "$") - .execute(); - } - - public GaData getBitstreamDownloads(String startDate, String endDate, String handle) throws IOException { - return GoogleAccount.getInstance().getClient().data().ga().get( - GoogleAccount.getInstance().getTableId(), - startDate, - endDate, - "ga:totalEvents") // Metrics. - .setDimensions("ga:year,ga:month") - .setSort("-ga:year,-ga:month") - .setFilters( - "ga:eventCategory==bitstream;ga:eventAction==download;ga:pagePath=~" + handle + "/") - .execute(); - } - -} - diff --git a/dspace-api/src/main/java/org/dspace/google/GoogleRecorderEventListener.java b/dspace-api/src/main/java/org/dspace/google/GoogleRecorderEventListener.java deleted file mode 100644 index 4159661b1ced..000000000000 --- a/dspace-api/src/main/java/org/dspace/google/GoogleRecorderEventListener.java +++ /dev/null @@ -1,201 +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.google; - -import java.io.IOException; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; -import javax.servlet.http.HttpServletRequest; - -import org.apache.commons.lang3.StringUtils; -import org.apache.http.NameValuePair; -import org.apache.http.client.entity.UrlEncodedFormEntity; -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.impl.client.HttpClients; -import org.apache.http.message.BasicNameValuePair; -import org.apache.logging.log4j.Logger; -import org.dspace.content.factory.ContentServiceFactory; -import org.dspace.core.Constants; -import org.dspace.service.ClientInfoService; -import org.dspace.services.ConfigurationService; -import org.dspace.services.model.Event; -import org.dspace.usage.AbstractUsageEventListener; -import org.dspace.usage.UsageEvent; -import org.springframework.beans.factory.annotation.Autowired; - - -/** - * User: Robin Taylor - * Date: 14/08/2014 - * Time: 10:05 - * - * Notify Google Analytics of... well anything we want really. - * @deprecated Use org.dspace.google.GoogleAsyncEventListener instead - */ -@Deprecated -public class GoogleRecorderEventListener extends AbstractUsageEventListener { - - private String analyticsKey; - private CloseableHttpClient httpclient; - private String GoogleURL = "https://www.google-analytics.com/collect"; - private static Logger log = org.apache.logging.log4j.LogManager.getLogger(GoogleRecorderEventListener.class); - - protected ContentServiceFactory contentServiceFactory; - protected ConfigurationService configurationService; - protected ClientInfoService clientInfoService; - - public GoogleRecorderEventListener() { - // httpclient is threadsafe so we only need one. - httpclient = HttpClients.createDefault(); - } - - @Autowired - public void setContentServiceFactory(ContentServiceFactory contentServiceFactory) { - this.contentServiceFactory = contentServiceFactory; - } - - @Autowired - public void setConfigurationService(ConfigurationService configurationService) { - this.configurationService = configurationService; - } - - @Autowired - public void setClientInfoService(ClientInfoService clientInfoService) { - this.clientInfoService = clientInfoService; - } - - @Override - public void receiveEvent(Event event) { - if ((event instanceof UsageEvent)) { - log.debug("Usage event received " + event.getName()); - - // This is a wee bit messy but these keys should be combined in future. - analyticsKey = configurationService.getProperty("google.analytics.key"); - - if (StringUtils.isNotBlank(analyticsKey)) { - try { - UsageEvent ue = (UsageEvent) event; - - if (ue.getAction() == UsageEvent.Action.VIEW) { - if (ue.getObject().getType() == Constants.BITSTREAM) { - logEvent(ue, "bitstream", "download"); - - // Note: I've left this commented out code here to show how we could record page views - // as events, - // but since they are already taken care of by the Google Analytics Javascript there is - // not much point. - - //} else if (ue.getObject().getType() == Constants.ITEM) { - // logEvent(ue, "item", "view"); - //} else if (ue.getObject().getType() == Constants.COLLECTION) { - // logEvent(ue, "collection", "view"); - //} else if (ue.getObject().getType() == Constants.COMMUNITY) { - // logEvent(ue, "community", "view"); - } - } - } catch (Exception e) { - log.error(e.getMessage()); - } - } - } - } - - private void logEvent(UsageEvent ue, String category, String action) throws IOException, SQLException { - HttpPost httpPost = new HttpPost(GoogleURL); - - List nvps = new ArrayList(); - nvps.add(new BasicNameValuePair("v", "1")); - nvps.add(new BasicNameValuePair("tid", analyticsKey)); - - // Client Id, should uniquely identify the user or device. If we have a session id for the user - // then lets use it, else generate a UUID. - if (ue.getRequest().getSession(false) != null) { - nvps.add(new BasicNameValuePair("cid", ue.getRequest().getSession().getId())); - } else { - nvps.add(new BasicNameValuePair("cid", UUID.randomUUID().toString())); - } - - nvps.add(new BasicNameValuePair("t", "event")); - nvps.add(new BasicNameValuePair("uip", getIPAddress(ue.getRequest()))); - nvps.add(new BasicNameValuePair("ua", ue.getRequest().getHeader("USER-AGENT"))); - nvps.add(new BasicNameValuePair("dr", ue.getRequest().getHeader("referer"))); - nvps.add(new BasicNameValuePair("dp", ue.getRequest().getRequestURI())); - nvps.add(new BasicNameValuePair("dt", getObjectName(ue))); - nvps.add(new BasicNameValuePair("ec", category)); - nvps.add(new BasicNameValuePair("ea", action)); - - if (ue.getObject().getType() == Constants.BITSTREAM) { - // Bitstream downloads may occasionally be for collection or community images, so we need to label them - // with the parent object type. - nvps.add(new BasicNameValuePair("el", getParentType(ue))); - } - - httpPost.setEntity(new UrlEncodedFormEntity(nvps)); - - try (CloseableHttpResponse response2 = httpclient.execute(httpPost)) { - // I can't find a list of what are acceptable responses, so I log the response but take no action. - log.debug("Google Analytics response is " + response2.getStatusLine()); - } - - log.debug("Posted to Google Analytics - " + ue.getRequest().getRequestURI()); - } - - private String getParentType(UsageEvent ue) { - try { - int parentType = contentServiceFactory.getDSpaceObjectService(ue.getObject()) - .getParentObject(ue.getContext(), ue.getObject()).getType(); - if (parentType == Constants.ITEM) { - return "item"; - } else if (parentType == Constants.COLLECTION) { - return "collection"; - } else if (parentType == Constants.COMMUNITY) { - return "community"; - } - } catch (SQLException e) { - // This shouldn't merit interrupting the user's transaction so log the error and continue. - log.error( - "Error in Google Analytics recording - can't determine ParentObjectType for bitstream " + ue.getObject() - .getID()); - e.printStackTrace(); - } - - return null; - } - - private String getObjectName(UsageEvent ue) { - try { - if (ue.getObject().getType() == Constants.BITSTREAM) { - // For a bitstream download we really want to know the title of the owning item rather than the - // bitstream name. - return contentServiceFactory.getDSpaceObjectService(ue.getObject()) - .getParentObject(ue.getContext(), ue.getObject()).getName(); - } else { - return ue.getObject().getName(); - } - } catch (SQLException e) { - // This shouldn't merit interrupting the user's transaction so log the error and continue. - log.error( - "Error in Google Analytics recording - can't determine ParentObjectName for bitstream " + ue.getObject() - .getID()); - e.printStackTrace(); - } - - return null; - - } - - private String getIPAddress(HttpServletRequest request) { - return clientInfoService.getClientIp(request); - } - -} 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 b5ee1806cd56..915bd25b065f 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.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 @@ */ 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 c35511353a3a..29182ad56c89 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 aa730fe2b115..5f511875d174 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 void modifyHandleDSpaceObject(Context context, String handle, DSpaceObjec 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 71bb798ae387..495f1f05a446 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.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 List findByPrefix(Context context, String prefix) throws SQLExcep @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 Long execute(Connection connection) throws SQLException { // 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 fe50bba813d6..1973b92522e5 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 HdlResolverDTO(final String requestURL, final String resolverSubPath) { } /** - * 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 52498558d49a..5947953ef50d 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 @@ private void runHarvest() { } 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 9fe2bed67d32..dc65ce254222 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 343347136bc3..943543f405de 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/OAIHarvester.java b/dspace-api/src/main/java/org/dspace/harvest/OAIHarvester.java index 5aeb40bdd912..1b8d0002ce2c 100644 --- a/dspace-api/src/main/java/org/dspace/harvest/OAIHarvester.java +++ b/dspace-api/src/main/java/org/dspace/harvest/OAIHarvester.java @@ -16,6 +16,7 @@ import java.sql.SQLException; import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Arrays; import java.util.Calendar; import java.util.Collections; import java.util.Date; @@ -683,15 +684,11 @@ protected void processRecord(Element record, String OREPrefix, final long curren * @return null or the handle to be used. */ protected String extractHandle(Item item) { - String[] acceptedHandleServers = configurationService.getArrayProperty("oai.harvester.acceptedHandleServer"); - if (acceptedHandleServers == null) { - acceptedHandleServers = new String[] {"hdl.handle.net"}; - } + String[] acceptedHandleServers = configurationService + .getArrayProperty("oai.harvester.acceptedHandleServer", new String[] {"hdl.handle.net"}); - String[] rejectedHandlePrefixes = configurationService.getArrayProperty("oai.harvester.rejectedHandlePrefix"); - if (rejectedHandlePrefixes == null) { - rejectedHandlePrefixes = new String[] {"123456789"}; - } + String[] rejectedHandlePrefixes = configurationService + .getArrayProperty("oai.harvester.rejectedHandlePrefix", new String[] {"123456789"}); List values = itemService.getMetadata(item, "dc", "identifier", Item.ANY, Item.ANY); @@ -706,12 +703,9 @@ protected String extractHandle(Item item) { for (String server : acceptedHandleServers) { if (urlPieces[2].equals(server)) { - for (String prefix : rejectedHandlePrefixes) { - if (!urlPieces[3].equals(prefix)) { - return urlPieces[3] + "/" + urlPieces[4]; - } + if (Arrays.stream(rejectedHandlePrefixes).noneMatch(prefix -> prefix.equals(urlPieces[3]))) { + return urlPieces[3] + "/" + urlPieces[4]; } - } } } 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 95a0bdf2163e..ab942a74ef07 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.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 HarvestedCollection findByStatusAndMinimalTypeOrderByLastHarvestedDesc(Co Root 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 HarvestedCollection findByStatusAndMinimalTypeOrderByLastHarvestedAsc(Con Root 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 HarvestedCollection findByCollection(Context context, Collection collecti ) ); - List orderList = new LinkedList<>(); + List orderList = new LinkedList<>(); orderList.add(criteriaBuilder.asc(harvestedCollectionRoot.get(HarvestedCollection_.lastHarvested))); criteriaQuery.orderBy(orderList); @@ -150,11 +150,10 @@ public HarvestedCollection findByCollection(Context context, Collection collecti @Override public int count(Context context) throws SQLException { - CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); CriteriaQuery 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 9e9838be6c1c..bb7ff0ee7cc4 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 ebb2ffd688c0..d083a45b13d1 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.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 e99472e45c78..2e699e990fc1 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 b70eda960d35..29c6a5bb8f6a 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.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.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 @@ * @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 void register(Context context, DSpaceObject dso, String identifier, Filte 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 void registerOnline(Context context, DSpaceObject dso, String identifier, /** * 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 void updateMetadata(Context context, DSpaceObject dso, String identifier) 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 void updateMetadata(Context context, DSpaceObject dso, String identifier) 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 void updateMetadata(Context context, DSpaceObject dso, String identifier) /** * 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 String mint(Context context, DSpaceObject dso, Filter filter) throws Iden 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 String mint(Context context, DSpaceObject dso, Filter filter) throws Iden } 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 void delete(Context context, DSpaceObject dso) 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 void delete(Context context, DSpaceObject dso) 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 void delete(Context context, DSpaceObject dso, String identifier) 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 void deleteOnline(Context context, String identifier) throws DOIIdentifie 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 DSpaceObject getObjectByDOI(Context context, String identifier) } 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 String getDOIByObject(Context context, DSpaceObject dso) throws SQLExcept } 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 void checkMintable(Context context, Filter filter, DSpaceObject dso) 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 99643db33fa0..fbbb1062ca70 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 DOI create(Context context) throws SQLException { 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 ae2cd248d417..0160c8adca80 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.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.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 @@ 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 String getXMLString(Context context, DSpaceObject dso) { 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 String getXMLString(Context context, DSpaceObject dso) { 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 78ddeb8f909b..ba1688f63580 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.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.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 @@ */ 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 void register(Context context, DSpaceObject object, String identifier) { 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 void register(Context context, DSpaceObject object, String identifier) { } } else { log.error("Identifier '{}' not registered -- EZID returned: {}", - identifier, response.getEZIDStatusValue()); + () -> identifier, response::getEZIDStatusValue); } } @@ -218,7 +219,8 @@ public void reserve(Context context, DSpaceObject dso, String identifier) 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 void reserve(Context context, DSpaceObject dso, String identifier) } } else { log.error("Identifier '{}' not registered -- EZID returned: {}", - identifier, response.getEZIDStatusValue()); + () -> identifier, response::getEZIDStatusValue); } } @@ -247,7 +249,7 @@ public String mint(Context context, DSpaceObject dso) 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 String mint(Context context, DSpaceObject dso) 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 String mint(Context context, DSpaceObject dso) 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 DSpaceObject resolve(Context context, String identifier, 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 void delete(Context context, DSpaceObject dso) 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 void delete(Context context, DSpaceObject dso) 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 void delete(Context context, DSpaceObject dso, String identifier) 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 void delete(Context context, DSpaceObject dso, String identifier) 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 @@ Map crosswalkMetadata(Context context, DSpaceObject dso) { 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 82358362da85..54ab9f90a633 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.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 @@ * @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 e5a90907c7b6..b970c9f06c48 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 String mint(Context context, DSpaceObject dso, Filter filter) 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 @@ void removePreviousVersionDOIsOutOfObject(Context c, Item item, String oldDoi) 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 4f9efd220695..4c8c785cbd4e 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.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 @@ * @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 void register(Context context, DSpaceObject dso, String identifier) 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 void register(Context context, DSpaceObject dso, String identifier) 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 void register(Context context, DSpaceObject dso, String identifier) } 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 9993f78b4dd5..67278b4db814 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.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 String register(Context context, DSpaceObject dso) { 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 String register(Context context, DSpaceObject dso) { 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 String register(Context context, DSpaceObject dso) { 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 String register(Context context, DSpaceObject dso) { 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 String register(Context context, DSpaceObject dso) { // 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 String register(Context context, DSpaceObject dso) { // 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 @@ protected void restoreItAsVersion(Context context, DSpaceObject dso, String iden 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 void delete(Context context, DSpaceObject dso) throws IdentifierException .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 @@ protected String makeIdentifierBasedOnHistory(Context context, DSpaceObject dso, 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 784fec1d8894..b045061a5fb9 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 @@ 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 61f738d7cba2..5fc3384b7de0 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 static String codeToString(int code) { 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 088e2b1cbc87..b03af68b42ab 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.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 DOI resolveToDOI(String identifier) 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 57136d6143bb..23af904f2c71 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.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.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.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 @@ 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 void reserveDOI(Context context, DSpaceObject dso, String doi) .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 void reserveDOI(Context context, DSpaceObject dso, String doi) 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 void reserveDOI(Context context, DSpaceObject dso, String doi) } 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 void reserveDOI(Context context, DSpaceObject dso, String doi) } 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 void reserveDOI(Context context, DSpaceObject dso, String doi) // 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 void registerDOI(Context context, DSpaceObject dso, String doi) 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 void registerDOI(Context context, DSpaceObject dso, String doi) // 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 void registerDOI(Context context, DSpaceObject dso, String doi) // 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 void registerDOI(Context context, DSpaceObject dso, String doi) } // 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 @@ protected DataCiteResponse sendDOIPostRequest(String doi, String url) 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 @@ protected DataCiteResponse sendMetadataPostRequest(String doi, String metadata) 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 @@ protected DataCiteResponse sendHttpRequest(HttpUriRequest req, String doi) // 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 @@ protected DataCiteResponse sendHttpRequest(HttpUriRequest req, String doi) 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 @@ protected DataCiteResponse sendHttpRequest(HttpUriRequest req, String doi) 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 525ad46b2554..bf46c3bf59da 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.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 @@ * @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 EZIDResponse lookup(String name) // 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 EZIDResponse create(String name, Map metadata) // 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 EZIDResponse mint(Map metadata) // 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 EZIDResponse modify(String name, Map metadata) // 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 EZIDResponse delete(String name) // 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 9c5ad904846c..4ac975b2db2e 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.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 EZIDResponse(HttpResponse response) 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 String getEZIDStatusValue() { * @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 5bd68a90615f..e815441b2acf 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 000000000000..a4033ee0c2d8 --- /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 e7d2d3398b6f..0f05e9bb4cc0 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 8fbe4ef2cf57..4c72c46732b7 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.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 272b14901514..48e7df89b387 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 96689e62ba75..a1df4a7f40c1 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.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 0014088c8650..7dcc8d64c784 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.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 f266ff3d8512..aad756fbb06e 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 587ad5b25838..66572f9a3d16 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.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/CrossRefAbstractProcessor.java b/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefAbstractProcessor.java new file mode 100644 index 000000000000..1b6da9d37b16 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefAbstractProcessor.java @@ -0,0 +1,123 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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 java.io.IOException; +import java.io.StringReader; +import java.util.ArrayList; +import java.util.Collection; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.dspace.importer.external.metadatamapping.contributor.JsonPathMetadataProcessor; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +public class CrossRefAbstractProcessor implements JsonPathMetadataProcessor { + + private final static Logger log = LogManager.getLogger(); + + private String path; + + @Override + public Collection processMetadata(String json) { + JsonNode rootNode = convertStringJsonToJsonNode(json); + JsonNode abstractNode = rootNode.at(path); + Collection values = new ArrayList<>(); + if (!abstractNode.isMissingNode()) { + String abstractValue = abstractNode.textValue(); + if (StringUtils.isNotEmpty(abstractValue)) { + abstractValue = prettifyAbstract(abstractValue); + if (abstractValue != null) { + values.add(abstractValue); + } + } + } + return values; + } + + /** + * remove JATS markup from abstract + * + * @param abstractValue abstract with JATS markup + * @return abstract without JATS markup + */ + private String prettifyAbstract(String abstractValue) { + if (!abstractValue.contains(""; + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + Document xmlDoc; + try { + DocumentBuilder builder = factory.newDocumentBuilder(); + InputSource is = new InputSource(new StringReader(xmlString)); + xmlDoc = builder.parse(is); + } catch (SAXException | IOException | ParserConfigurationException e) { + log.warn("unable to parse XML markup in CrossRef abstract field: " + e.getMessage()); + return null; + } + + StringBuilder sb = new StringBuilder(); + + NodeList rootElements = xmlDoc.getElementsByTagName("root"); + Node rootElement = rootElements.item(0); + NodeList childElements = rootElement.getChildNodes(); + for (int i = 0; i < childElements.getLength(); i++) { + Node childElement = childElements.item(i); + String nodeName = childElement.getNodeName(); + if (StringUtils.equals(nodeName, "jats:title")) { + if (! StringUtils.equals(childElement.getTextContent(), "Abstract")) { + sb.append(childElement.getTextContent()); + sb.append("\n"); + } + } else if (StringUtils.equals(nodeName, "jats:sec")) { + NodeList secElements = childElement.getChildNodes(); + for (int j = 0; j < secElements.getLength(); j++) { + Node secChildElement = secElements.item(j); + sb.append(secChildElement.getTextContent()); + sb.append("\n"); + } + sb.append("\n"); + } + } + + return sb.toString().trim(); + } + + private JsonNode convertStringJsonToJsonNode(String json) { + ObjectMapper mapper = new ObjectMapper(); + JsonNode body = null; + try { + body = mapper.readTree(json); + } catch (JsonProcessingException e) { + log.error("Unable to process json response.", e); + } + return body; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } +} 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 abf84f52d058..b9b384f8ed77 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 Collection processMetadata(String json) { 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 void setPathToArray(String pathToArray) { this.pathToArray = pathToArray; } -} \ No newline at end of file +} 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 5e879b4d266e..81571ed7bb5e 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 71b088ff162b..37e613d9c5ff 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.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; @@ -133,7 +133,7 @@ private class SearchByQueryCallable implements Callable> { private SearchByQueryCallable(String queryString, Integer maxResult, Integer start) { query = new Query(); - query.addParameter("query", queryString); + query.addParameter("query", StringUtils.trim(queryString)); query.addParameter("count", maxResult); query.addParameter("start", start); } @@ -186,14 +186,15 @@ private SearchByIdCallable(Query query) { private SearchByIdCallable(String id) { this.query = new Query(); - query.addParameter("id", id); + query.addParameter("id", StringUtils.trim(id)); } @Override public List call() throws Exception { List results = new ArrayList<>(); + URIBuilder uriBuilder = new URIBuilder(url); String ID = URLDecoder.decode(query.getParameterAsClass("id", String.class), "UTF-8"); - URIBuilder uriBuilder = new URIBuilder(url + "/" + ID); + uriBuilder.setPath(uriBuilder.getPath() + "/" + ID); Map> params = new HashMap>(); String responseString = liveImportClient.executeHttpGetRequest(1000, uriBuilder.toString(), params); JsonNode jsonNode = convertStringJsonToJsonNode(responseString); @@ -277,7 +278,7 @@ private class CountByQueryCallable implements Callable { private CountByQueryCallable(String queryString) { query = new Query(); - query.addParameter("query", queryString); + query.addParameter("query", StringUtils.trim(queryString)); } private CountByQueryCallable(Query query) { @@ -308,7 +309,7 @@ private class DoiCheckCallable implements Callable { private DoiCheckCallable(final String id) { final Query query = new Query(); - query.addParameter("id", id); + query.addParameter("id", StringUtils.trim(id)); this.query = query; } @@ -319,7 +320,8 @@ private DoiCheckCallable(final Query query) { @Override public Integer call() throws Exception { Map> params = new HashMap>(); - URIBuilder uriBuilder = new URIBuilder(url + "/" + query.getParameterAsClass("id", String.class)); + URIBuilder uriBuilder = new URIBuilder(url); + uriBuilder.setPath(uriBuilder.getPath() + "/" + query.getParameterAsClass("id", String.class)); String responseString = liveImportClient.executeHttpGetRequest(1000, uriBuilder.toString(), params); JsonNode jsonNode = convertStringJsonToJsonNode(responseString); return StringUtils.equals(jsonNode.at("/status").toString(), "ok") ? 1 : 0; 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 f8540307b916..a67b73480dfa 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 a11f2bc2471d..e00b2e2cea8f 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.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 @@ * 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) @@ -73,8 +73,32 @@ public ImportRecord getRecord(String recordId) throws MetadataSourceException { @Override public int getRecordsCount(String query) throws MetadataSourceException { - Collection records = getRecords(query, 0, -1); - return records == null ? 0 : records.size(); + String id = getID(query); + Map> params = new HashMap<>(); + Map uriParameters = new HashMap<>(); + params.put("uriParameters", uriParameters); + if (StringUtils.isBlank(id)) { + id = query; + } + uriParameters.put("query", id); + uriParameters.put("page[size]", "1"); + int timeoutMs = configurationService.getIntProperty("datacite.timeout", 180000); + String url = configurationService.getProperty("datacite.url", "https://api.datacite.org/dois/"); + String responseString = liveImportClient.executeHttpGetRequest(timeoutMs, url, params); + JsonNode jsonNode = convertStringJsonToJsonNode(responseString); + if (jsonNode == null) { + log.warn("DataCite returned invalid JSON"); + throw new MetadataSourceException("Could not read datacite source"); + } + JsonNode dataNode = jsonNode.at("/meta/total"); + if (dataNode != null) { + try { + return Integer.valueOf(dataNode.toString()); + } catch (Exception e) { + log.debug("Could not read integer value" + dataNode.toString()); + } + } + return 0; } @Override @@ -95,6 +119,17 @@ public Collection getRecords(String query, int start, int count) t id = query; } uriParameters.put("query", id); + // start = current dspace page / datacite page number starting with 1 + // dspace rounds up/down to the next configured pagination settings. + if (start > 0 && count > 0) { + uriParameters.put("page[number]", Integer.toString((start / count) + 1)); + } + + // count = dspace page size / default datacite page size is currently 25 https://support.datacite.org/docs/pagination + if (count > 0) { + uriParameters.put("page[size]", Integer.toString(count)); + } + int timeoutMs = configurationService.getIntProperty("datacite.timeout", 180000); String url = configurationService.getProperty("datacite.url", "https://api.datacite.org/dois/"); String responseString = liveImportClient.executeHttpGetRequest(timeoutMs, url, params); @@ -108,12 +143,16 @@ public Collection getRecords(String query, int start, int count) t Iterator iterator = dataNode.iterator(); while (iterator.hasNext()) { JsonNode singleDoiNode = iterator.next(); - String json = singleDoiNode.at("/attributes").toString(); - records.add(transformSourceRecords(json)); + JsonNode singleDoiNodeAttribute = singleDoiNode.at("/attributes"); + if (!singleDoiNodeAttribute.isMissingNode()) { + records.add(transformSourceRecords(singleDoiNodeAttribute.toString())); + } } } else { - String json = dataNode.at("/attributes").toString(); - records.add(transformSourceRecords(json)); + JsonNode singleDoiNodeAttribute = dataNode.at("/attributes"); + if (!singleDoiNodeAttribute.isMissingNode()) { + records.add(transformSourceRecords(singleDoiNodeAttribute.toString())); + } } return records; 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 64ec53ffb92b..955550ce0fe5 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 fbae302bca6a..70f726c5f9e5 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 String getConsumerKey() { } /** - * 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 b938a290c297..7a74551d8064 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 @@ /** * 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/AuthorMetadataContributor.java b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/AuthorMetadataContributor.java index 26063dc7441d..62b7fe81e399 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/AuthorMetadataContributor.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/AuthorMetadataContributor.java @@ -71,7 +71,7 @@ public Collection contributeMetadata(Element element) { } /** - * Retrieve the the ScopusID, orcid, author name and affiliationID + * Retrieve the ScopusID, orcid, author name and affiliationID * metadata associated with the given element object. * If the value retrieved from the element is empty * it is set PLACEHOLDER_PARENT_METADATA_VALUE @@ -82,11 +82,19 @@ public Collection contributeMetadata(Element element) { private List getMetadataOfAuthors(Element element) throws JaxenException { List metadatums = new ArrayList(); Element authname = element.getChild("authname", NAMESPACE); + Element surname = element.getChild("surname", NAMESPACE); + Element givenName = element.getChild("given-name", NAMESPACE); Element scopusId = element.getChild("authid", NAMESPACE); Element orcid = element.getChild("orcid", NAMESPACE); Element afid = element.getChild("afid", NAMESPACE); - addMetadatum(metadatums, getMetadata(getElementValue(authname), this.authname)); + if (authname != null) { + addMetadatum(metadatums, getMetadata(getElementValue(authname), this.authname)); + } else { + addMetadatum(metadatums, getMetadata(getElementValue(surname) + ", " + + getElementValue(givenName), this.authname)); + } + addMetadatum(metadatums, getMetadata(getElementValue(scopusId), this.scopusId)); addMetadatum(metadatums, getMetadata(getElementValue(orcid), this.orcid)); addMetadatum(metadatums, getMetadata(StringUtils.isNotBlank(afid.getValue()) @@ -170,4 +178,4 @@ public void setAffiliation(MetadataFieldConfig affiliation) { this.affiliation = affiliation; } -} \ No newline at end of file +} 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 b3953f60c577..7aaa5aa0b3ed 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 Collection contributeMetadata(PlainMetadataSourceDto t) { 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 @@ private String[] splitToRecord(String value) { 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 e32f45a4d5f3..64d2deaca3b3 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.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.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 MetadataFieldMapping> getMetadataFi * * @param metadataFieldMapping the new mapping. */ + @Override public void setMetadataFieldMapping( MetadataFieldMapping> metadataFieldMapping) { this.metadataFieldMapping = metadataFieldMapping; @@ -95,7 +96,8 @@ public void setPrefixToNamespaceMapping(Map prefixToNamespaceMap 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 EpoIdMetadataContributor(String query, Map prefixToNamesp * Empty constructor for EpoIdMetadataContributor */ public EpoIdMetadataContributor() { - } protected String query; @@ -132,7 +133,7 @@ public MetadataFieldConfig getField() { * * @param field MetadataFieldConfig used while retrieving MetadatumDTO */ - @Required + @Autowired(required = true) public void setField(MetadataFieldConfig field) { this.field = field; } @@ -146,7 +147,11 @@ public String getQuery() { 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 c8e93971f480..e4d42d22924d 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 @@ /** * 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/SimpleConcatContributor.java b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleConcatContributor.java index d84bc65701c6..9a2aa242c6b8 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 @@ * 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 590fc63283b9..03f3efced0be 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 1b9007f23cbd..70c6e0f3855d 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 void setMetadataFieldMapping( /** * Retrieve the metadata associated with the given object. * It match the key found in PlainMetadataSourceDto instance with the key passed to constructor. - * In case of success, new metadatum is constructer (using field elements and PlainMetadataSourceDto value) + * In case of success, new metadatum is constructor (using field elements and PlainMetadataSourceDto value) * and added to the list. * * @param t A class to retrieve metadata and key to match from. t and contained list "metadata" MUST be not null. diff --git a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleXpathMetadatumContributor.java b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleXpathMetadatumContributor.java index 05f8647d7867..3fa32376990c 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleXpathMetadatumContributor.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleXpathMetadatumContributor.java @@ -12,8 +12,8 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; -import javax.annotation.Resource; +import jakarta.annotation.Resource; import org.apache.logging.log4j.Logger; import org.dspace.importer.external.metadatamapping.MetadataFieldConfig; import org.dspace.importer.external.metadatamapping.MetadataFieldMapping; diff --git a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/WosAttribute2ValueContributor.java b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/WosAttribute2ValueContributor.java index 66e16f7ae866..9c10218a99bd 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/WosAttribute2ValueContributor.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/WosAttribute2ValueContributor.java @@ -12,8 +12,8 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; -import javax.annotation.Resource; +import jakarta.annotation.Resource; 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/openaire/metadatamapping/OpenAIREPublicationFieldMapping.java b/dspace-api/src/main/java/org/dspace/importer/external/openaire/metadatamapping/OpenAIREPublicationFieldMapping.java index d58ffc8ca9d5..cf326fb63d71 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/openaire/metadatamapping/OpenAIREPublicationFieldMapping.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/openaire/metadatamapping/OpenAIREPublicationFieldMapping.java @@ -8,8 +8,8 @@ package org.dspace.importer.external.openaire.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/openaire/service/OpenAireImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/openaire/service/OpenAireImportMetadataSourceServiceImpl.java index 0e59c9eb0e42..0e7bc5e532e8 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/openaire/service/OpenAireImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/openaire/service/OpenAireImportMetadataSourceServiceImpl.java @@ -14,13 +14,13 @@ 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.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.Response; import org.dspace.content.Item; import org.dspace.importer.external.datamodel.ImportRecord; import org.dspace.importer.external.datamodel.Query; @@ -187,7 +187,8 @@ public String getQueryParam() { public void init() throws Exception { Client client = ClientBuilder.newClient(); if (baseAddress == null) { - baseAddress = configurationService.getProperty("openaire.base.url"); + baseAddress = configurationService.getProperty("openaire.search.url", + "https://api.openaire.eu/search/publications"); } if (queryParam == null) { queryParam = "title"; @@ -252,10 +253,10 @@ public Integer call() throws Exception { Document document = saxBuilder.build(new StringReader(responseString)); Element root = document.getRootElement(); - XPathExpression xpath = XPathFactory.instance().compile("/header/total", + XPathExpression xpath = XPathFactory.instance().compile("//header/total", Filters.element(), null); - Element totalItem = (Element) xpath.evaluateFirst(root); + Element totalItem = xpath.evaluateFirst(root); return totalItem != null ? Integer.parseInt(totalItem.getText()) : null; } else { 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 2d315377669a..0d4dcf0c1e72 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 8c8e23fe989a..35d10af58443 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 92d7d9fbd3fe..5aae8ca8cf50 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.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 1f460c19e697..e4e027016e13 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.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 index d7caeffdbaf2..3096a92f95dd 100644 --- 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 @@ -8,8 +8,8 @@ package org.dspace.importer.external.ror.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/ror/service/RorImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/ror/service/RorImportMetadataSourceServiceImpl.java index 59063271f365..aa11ac0bb710 100644 --- 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 @@ -15,11 +15,11 @@ 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/scielo/service/ScieloFieldMapping.java b/dspace-api/src/main/java/org/dspace/importer/external/scielo/service/ScieloFieldMapping.java index 0d7183a1f058..f8c4f93a1757 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 4f83ffe978f7..ce0c20435ecf 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.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 c8143339b483..93e9753c42d5 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 944d467e3156..39b2be7ad5f8 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 @@ -14,6 +14,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -21,9 +22,11 @@ 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; import org.dspace.content.Item; import org.dspace.importer.external.datamodel.ImportRecord; import org.dspace.importer.external.datamodel.Query; @@ -62,6 +65,8 @@ public class ScopusImportMetadataSourceServiceImpl extends AbstractImportMetadat @Autowired private LiveImportClient liveImportClient; + private final static Logger log = LogManager.getLogger(); + public LiveImportClient getLiveImportClient() { return liveImportClient; } @@ -200,6 +205,9 @@ public Integer call() throws Exception { Map requestParams = getRequestParameters(query, null, null, null); params.put(URI_PARAMETERS, requestParams); String response = liveImportClient.executeHttpGetRequest(timeout, url, params); + if (StringUtils.isEmpty(response)) { + return 0; + } SAXBuilder saxBuilder = new SAXBuilder(); // disallow DTD parsing to ensure no XXE attacks can occur @@ -245,6 +253,10 @@ public List call() throws Exception { Map requestParams = getRequestParameters(queryString, viewMode, null, null); params.put(URI_PARAMETERS, requestParams); String response = liveImportClient.executeHttpGetRequest(timeout, url, params); + if (StringUtils.isEmpty(response)) { + return results; + } + List elements = splitToRecords(response); for (Element record : elements) { results.add(transformSourceRecords(record)); @@ -304,6 +316,10 @@ public List call() throws Exception { Map requestParams = getRequestParameters(queryString, viewMode, start, count); params.put(URI_PARAMETERS, requestParams); String response = liveImportClient.executeHttpGetRequest(timeout, url, params); + if (StringUtils.isEmpty(response)) { + return results; + } + List elements = splitToRecords(response); for (Element record : elements) { results.add(transformSourceRecords(record)); @@ -349,6 +365,10 @@ public List call() throws Exception { Map requestParams = getRequestParameters(queryString, viewMode, start, count); params.put(URI_PARAMETERS, requestParams); String response = liveImportClient.executeHttpGetRequest(timeout, url, params); + if (StringUtils.isEmpty(response)) { + return results; + } + List elements = splitToRecords(response); for (Element record : elements) { results.add(transformSourceRecords(record)); @@ -383,10 +403,16 @@ private List splitToRecords(String recordsSrc) { saxBuilder.setFeature("http://apache.org/xml/features/disallow-doctype-decl",true); Document document = saxBuilder.build(new StringReader(recordsSrc)); Element root = document.getRootElement(); - List records = root.getChildren("entry",Namespace.getNamespace("http://www.w3.org/2005/Atom")); + String totalResults = root.getChildText("totalResults", Namespace.getNamespace("http://a9.com/-/spec/opensearch/1.1/")); + if (totalResults != null && "0".equals(totalResults)) { + log.debug("got Scopus API with empty response"); + return Collections.emptyList(); + } + List records = root.getChildren("entry", Namespace.getNamespace("http://www.w3.org/2005/Atom")); return records; } catch (JDOMException | IOException e) { - return new ArrayList(); + log.warn("got unexpected XML response from Scopus API: " + e.getMessage()); + return Collections.emptyList(); } } @@ -422,4 +448,4 @@ public void setInstKey(String instKey) { this.instKey = instKey; } -} \ No newline at end of file +} diff --git a/dspace-api/src/main/java/org/dspace/importer/external/service/DoiCheck.java b/dspace-api/src/main/java/org/dspace/importer/external/service/DoiCheck.java index 95d42e3a27da..b07ebe3bff2e 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/service/DoiCheck.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/service/DoiCheck.java @@ -19,7 +19,16 @@ */ public class DoiCheck { - private static final List DOI_PREFIXES = Arrays.asList("http://dx.doi.org/", "https://dx.doi.org/"); + private static final List DOI_PREFIXES = Arrays.asList( + "http://dx.doi.org/", + "https://dx.doi.org/", + "http://www-dx.doi.org/", + "https://www-dx.doi.org/", + "http://doi.org/", + "https://doi.org/", + "www.dx.doi.org/", + "dx.doi.org/", + "doi:"); private static final Pattern PATTERN = Pattern.compile("10.\\d{4,9}/[-._;()/:A-Z0-9]+" + "|10.1002/[^\\s]+" + 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 5d83b9a7cce4..6e0a2d9f7a8a 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 @@ /** * 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 29801433e3b3..e15ffff6e6bb 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.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 @@ protected T retry(Callable callable) throws MetadataSourceException { } 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 @@ protected T retry(Callable callable) throws MetadataSourceException { // 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 a4f90fa5ba61..8933569a060f 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.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 b14927a14ccc..d96265372f31 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 be4acfbcea8c..6cf58fdd7bc9 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 f550b659952b..c7b5aaa49e27 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.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 07a79384c77c..4665fe4428be 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 65b66cd20c3e..dfcb531401d7 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.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 def289daf41e..3e54a68a23ec 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 3e7ca7b21029..2dc1d591fd58 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.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 @@ private T executeAndParseJson(HttpUriRequest httpUriRequest, Class clazz) * 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 d177e61607f1..97da341fb811 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.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.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 @@ * 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 @@ */ 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 @@ private void createDeletionRecordForNoMorePresentSignatures(Context context, Ite 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 0b2c7099ffac..fe300751d1dd 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 @@ 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 2114b2535759..c8e48e3f17d6 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 @@ 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 01b03fc35455..f94f5ad3a44f 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 4ca36c216919..9a821bef8dc7 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 2f47aa53d69d..1195d27c4fda 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 Optional createCountry(Context context, MetadataValue metadataVa 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 @@ private OrganizationAddress createOrganizationAddress(Item organizationItem) { 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 890b54f12b1c..fd2669935cab 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.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.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 @@ */ 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 53b46d8256d1..7018c221ba00 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.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.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 @@ */ 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 @@ private ExternalIDs getWorkExternalIds(Context context, Item item) { */ private List getWorkSelfExternalIds(Context context, Item item) { - List selfExternalIds = new ArrayList(); + List selfExternalIds = new ArrayList<>(); Map externalIdentifierFields = fieldMapping.getExternalIdentifierFields(); @@ -205,7 +205,7 @@ private ExternalID getExternalId(String type, String value, Relationship relatio } /** - * 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 0e6f856bfcee..7dceb473cc51 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 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.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 @@ */ 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 void internalRun() throws Exception { } /** - * Find all the Orcid Queue records that need to be synchronized and perfom the + * Find all the Orcid Queue records that need to be synchronized and perform the * synchronization. */ private void performBulkSynchronization() throws SQLException { @@ -130,7 +130,7 @@ private List findQueueRecordsToSynchronize() throws SQLException { } /** - * If the current script execution is configued to ignore the max attemps, + * If the current script execution is configured to ignore the max attempts, * returns all the ORCID Queue records, otherwise returns the ORCID Queue * records that has an attempts value less than the configured max attempts * value. diff --git a/dspace-api/src/main/java/org/dspace/orcid/service/OrcidSynchronizationService.java b/dspace-api/src/main/java/org/dspace/orcid/service/OrcidSynchronizationService.java index 575ce6811b24..914ed7172e2f 100644 --- a/dspace-api/src/main/java/org/dspace/orcid/service/OrcidSynchronizationService.java +++ b/dspace-api/src/main/java/org/dspace/orcid/service/OrcidSynchronizationService.java @@ -21,7 +21,7 @@ import org.dspace.profile.OrcidSynchronizationMode; /** - * Service that handle the the syncronization between a DSpace profile and the + * Service that handle the the synchronization between a DSpace profile and the * relative ORCID profile, if any. * * @author Luca Giamminonni (luca.giamminonni at 4science.it) diff --git a/dspace-api/src/main/java/org/dspace/orcid/service/impl/OrcidHistoryServiceImpl.java b/dspace-api/src/main/java/org/dspace/orcid/service/impl/OrcidHistoryServiceImpl.java index 0bec9a12e0ea..0e18b46f5db2 100644 --- a/dspace-api/src/main/java/org/dspace/orcid/service/impl/OrcidHistoryServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/orcid/service/impl/OrcidHistoryServiceImpl.java @@ -24,6 +24,8 @@ import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpStatus; +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.MetadataValue; @@ -48,8 +50,6 @@ import org.dspace.orcid.service.OrcidProfileSectionFactoryService; import org.dspace.orcid.service.OrcidTokenService; import org.orcid.jaxb.model.v3.release.record.Activity; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; /** @@ -61,7 +61,7 @@ */ public class OrcidHistoryServiceImpl implements OrcidHistoryService { - private static final Logger LOGGER = LoggerFactory.getLogger(OrcidHistoryServiceImpl.class); + private static final Logger LOGGER = LogManager.getLogger(); @Autowired private OrcidHistoryDAO orcidHistoryDAO; @@ -134,7 +134,7 @@ public Optional findLastPutCode(Context context, Item profileItem, Item @Override public Map 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 OrcidHistory synchronizeWithOrcid(Context context, OrcidQueue orcidQueue, } 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 97d832d3de82..59e4dea64145 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 void unlinkProfile(Context context, Item profile) throws SQLException { 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 @@ private boolean updatePreferenceForSynchronizingWithOrcid(Context context, Item } + 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 80bbd68fd19d..5de1ffa4ac93 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.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.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 @@ */ 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 @@ private Optional findConfiguredProfileCollection(Context context) th 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 9e52402f77e4..de01760f9f46 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 ResearcherProfile createAndReturn(Context context, EPerson ePerson) /** * 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 000000000000..771650746d03 --- /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 000000000000..d7c8f3681e56 --- /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/QANotifyPatterns.java b/dspace-api/src/main/java/org/dspace/qaevent/QANotifyPatterns.java new file mode 100644 index 000000000000..ede1990569ce --- /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 000000000000..f685222d3dfc --- /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 index e22f7d32a770..10849b47fc27 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/QASource.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/QASource.java @@ -8,6 +8,7 @@ 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). @@ -16,9 +17,16 @@ * */ 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 long totalEvents; private Date lastEvent; + private long totalEvents; public String getName() { return name; @@ -43,4 +51,17 @@ public Date getLastEvent() { 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 index 63e523b9cb5e..92fe3737f450 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/QATopic.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/QATopic.java @@ -8,19 +8,37 @@ 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; - private long totalEvents; + /** + * 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; } @@ -29,6 +47,14 @@ 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; } 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 000000000000..ee81988f635d --- /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 000000000000..3acaa726e0ea --- /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/QANotifyMetadataMapAction.java b/dspace-api/src/main/java/org/dspace/qaevent/action/QANotifyMetadataMapAction.java new file mode 100644 index 000000000000..a85a38655081 --- /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 000000000000..ffb70fce66cb --- /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 index e1fa23002fcb..427ad2bfdea0 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/action/QAOpenaireMetadataMapAction.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/action/QAOpenaireMetadataMapAction.java @@ -7,80 +7,25 @@ */ 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.OpenaireMessageDTO; 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 OPENAIRE message type. - * - * @author Andrea Bollini (andrea.bollini at 4science.it) + * Openaire Implementation {@link AMetadataMapAction} + * + * @author Francesco Bacchelli (francesco.bacchelli at 4science.it) * */ -public class QAOpenaireMetadataMapAction 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 class QAOpenaireMetadataMapAction extends AMetadataMapAction { - public void setTypes(Map types) { - this.types = types; + @Override + public String extractMetadataType(QAMessageDTO message) { + return ((OpenaireMessageDTO)message).getType(); } - /** - * 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) { - - if (!(message instanceof OpenaireMessageDTO)) { - throw new IllegalArgumentException("Unsupported message type: " + message.getClass()); - } - - OpenaireMessageDTO openaireMessage = (OpenaireMessageDTO) message; - - try { - String targetMetadata = types.get(openaireMessage.getType()); - if (targetMetadata == null) { - targetMetadata = types.get(DEFAULT); - } - String[] metadata = splitMetadata(targetMetadata); - itemService.addMetadata(context, item, metadata[0], metadata[1], metadata[2], null, - openaireMessage.getValue()); - itemService.update(context, item); - } catch (SQLException | AuthorizeException e) { - throw new RuntimeException(e); - } - + public String extractMetadataValue(QAMessageDTO message) { + return ((OpenaireMessageDTO)message).getValue(); } - 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/QAOpenaireSimpleMetadataAction.java b/dspace-api/src/main/java/org/dspace/qaevent/action/QAOpenaireSimpleMetadataAction.java index 2509b768aefb..3baa95ecedb6 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/action/QAOpenaireSimpleMetadataAction.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/action/QAOpenaireSimpleMetadataAction.java @@ -7,16 +7,9 @@ */ 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.OpenaireMessageDTO; import org.dspace.qaevent.service.dto.QAMessageDTO; -import org.springframework.beans.factory.annotation.Autowired; /** * Implementation of {@link QualityAssuranceAction} that add a simple metadata to the given @@ -25,40 +18,9 @@ * @author Andrea Bollini (andrea.bollini at 4science.it) * */ -public class QAOpenaireSimpleMetadataAction 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 class QAOpenaireSimpleMetadataAction extends ASimpleMetadataAction { - @Override - public void applyCorrection(Context context, Item item, Item relatedItem, QAMessageDTO message) { - try { - itemService.addMetadata(context, item, metadataSchema, metadataElement, metadataQualifier, null, - ((OpenaireMessageDTO) message).getAbstracts()); - itemService.update(context, item); - } catch (SQLException | AuthorizeException e) { - throw new RuntimeException(e); - } + 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 000000000000..7fa08dc6ecee --- /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 000000000000..a0463fdb1821 --- /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/impl/QAEventsDAOImpl.java b/dspace-api/src/main/java/org/dspace/qaevent/dao/impl/QAEventsDAOImpl.java index ac9b96045e42..2dbc47692010 100644 --- 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 @@ -10,8 +10,8 @@ import java.sql.SQLException; import java.util.Date; import java.util.List; -import javax.persistence.Query; +import jakarta.persistence.Query; import org.dspace.content.Item; import org.dspace.content.QAEventProcessed; import org.dspace.core.AbstractHibernateDAO; 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 index 9087606aa6e5..48f83d413dbf 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/script/OpenaireEventsImport.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/script/OpenaireEventsImport.java @@ -7,9 +7,9 @@ */ 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; @@ -28,10 +28,14 @@ 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; @@ -40,7 +44,7 @@ import org.dspace.utils.DSpace; /** - * Implementation of {@link DSpaceRunnable} to perfom a QAEvents import from a + * 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 @@ -71,6 +75,8 @@ public class OpenaireEventsImport extends DSpaceRunnable> { + private HandleService handleService; + private QAEventService qaEventService; private String[] topicsToImport; @@ -103,7 +109,9 @@ public void setup() throws ParseException { jsonMapper = new JsonMapper(); jsonMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - qaEventService = new DSpace().getSingletonService(QAEventService.class); + DSpace dspace = new DSpace(); + handleService = dspace.getSingletonService(HandleServiceImpl.class); + qaEventService = dspace.getSingletonService(QAEventService.class); configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); brokerClient = OpenaireClientFactory.getInstance().getBrokerClient(); @@ -236,14 +244,47 @@ private List readOpenaireQAEventsFromJson(InputStream inputStream) thro 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 e) { + } 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. * @@ -282,7 +323,7 @@ private List listEmailSubscriptions() { try { return brokerClient.listSubscriptions(openaireBrokerURL, email); } catch (Exception ex) { - throw new IllegalArgumentException("An error occurs retriving the subscriptions " + throw new IllegalArgumentException("An error occurs retrieving the subscriptions " + "from the OPENAIRE broker: " + getMessage(ex), ex); } } 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 index 60001e73507d..63dcfae740a0 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/script/OpenaireEventsImportScriptConfiguration.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/script/OpenaireEventsImportScriptConfiguration.java @@ -13,7 +13,7 @@ import org.dspace.scripts.configuration.ScriptConfiguration; /** - * Extension of {@link ScriptConfiguration} to perfom a QAEvents import from + * Extension of {@link ScriptConfiguration} to perform a QAEvents import from * file. * * @author Alessandro Martelli (alessandro.martelli at 4science.it) 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 000000000000..38cf40ce3989 --- /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 000000000000..44b00e7d9488 --- /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 000000000000..3d66d221e681 --- /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/QAEventSecurityService.java b/dspace-api/src/main/java/org/dspace/qaevent/service/QAEventSecurityService.java new file mode 100644 index 000000000000..7f6ef7a12cd7 --- /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 index 2332a55caf52..3254aecf7731 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/service/QAEventService.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/service/QAEventService.java @@ -12,6 +12,7 @@ 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; @@ -26,67 +27,82 @@ public interface QAEventService { /** * Find all the event's topics. * - * @param offset the offset to apply - * @return the topics list + * @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(long offset, long count, String orderField, boolean ascending); + 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 source the source to search for - * @param offset the offset to apply - * @param count the page size - * @return the topics list + * @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(String source, long offset, long count, + 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 + * @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(String source); + 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 pageSize the page size - * @param orderField the field to order for - * @param ascending true if the order should be ascending, false otherwise + * @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 findEventsByTopicAndPage(String topic, long offset, int pageSize, - String orderField, boolean ascending); - - /** - * Find all the events by topic. - * - * @param topic the topic to search for - * @return the events - */ - public List findEventsByTopic(String topic); + public List findEventsByTopic(Context context, String sourceName, String topic, long offset, int size, + String orderField, boolean ascending); /** * Find all the events by topic. * - * @param topic the topic to search for - * @return the events count + * @param context the DSpace context + * @param sourceName the source name + * @param topic the topic to search for + * @return the events count */ - public long countEventsByTopic(String topic); + public long countEventsByTopic(Context context, String sourceName, String topic); /** - * Find an event by the given id. + * 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 @@ -126,26 +142,58 @@ public List findEventsByTopicAndPage(String topic, long offset, int pag /** * Find a specific source by the given name. * - * @param source the source name - * @return the source + * @param context the DSpace context + * @param source the source name + * @return the source */ - public QASource findSource(String 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(long offset, int pageSize); + 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 countSources(); + public long countTopicsBySourceAndTarget(Context context, String source, UUID target); /** * Check if the given QA event supports a related item. @@ -155,4 +203,65 @@ public List findEventsByTopicAndPage(String topic, long offset, int pag */ 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 000000000000..e5e38c23966e --- /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 000000000000..2a5842589fde --- /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 index 59b4acf9db21..821f11f86914 100644 --- 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 @@ -7,6 +7,7 @@ */ package org.dspace.qaevent.service.dto; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; /** @@ -15,6 +16,7 @@ * @author Luca Giamminonni (luca.giamminonni at 4science.it) * */ +@JsonIgnoreProperties(ignoreUnknown = true) public class OpenaireMessageDTO implements QAMessageDTO { @JsonProperty("pids[0].value") 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 index 2a63f42e615c..ede32ef49757 100644 --- 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 @@ -17,5 +17,4 @@ */ public interface QAMessageDTO { - } 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 index cca70ecd0430..30875a5105b0 100644 --- 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 @@ -23,6 +23,7 @@ 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; @@ -41,7 +42,8 @@ * */ public class QAEventActionServiceImpl implements QAEventActionService { - private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(QAEventActionServiceImpl.class); + + private static final Logger log = LogManager.getLogger(QAEventActionServiceImpl.class); private ObjectMapper jsonMapper; @@ -78,12 +80,21 @@ public void accept(Context context, QAEvent qaevent) { 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(); } } 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 000000000000..854626b3ba9d --- /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 index 1dfcc1b6d96a..98077a1c0c76 100644 --- 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 @@ -16,14 +16,21 @@ 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; @@ -41,15 +48,24 @@ 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 @@ -62,9 +78,16 @@ */ 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; @@ -74,6 +97,13 @@ public class QAEventServiceImpl implements QAEventService { @Autowired private QAEventsDAOImpl qaEventsDao; + @Autowired(required = false) + @Qualifier("qaAutomaticProcessingMap") + private Map qaAutomaticProcessingMap; + + @Autowired + private QAEventActionService qaEventActionService; + private ObjectMapper jsonMapper; public QAEventServiceImpl() { @@ -124,14 +154,19 @@ public long countTopics() { } @Override - public long countTopicsBySource(String source) { + 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); - solrQuery.setQuery("*:*"); + Optional securityQuery = qaSecurityService.generateQAEventFilterQuery(context, currentUser, sourceName); + solrQuery.setQuery(securityQuery.orElse("*:*")); solrQuery.setFacet(true); solrQuery.setFacetMinCount(1); solrQuery.addFacetField(TOPIC); - solrQuery.addFilterQuery("source:" + source); + solrQuery.addFilterQuery(SOURCE + ":\"" + sourceName + "\""); QueryResponse response; try { response = getSolr().query(solrQuery); @@ -141,6 +176,47 @@ public long countTopicsBySource(String source) { 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 { @@ -189,36 +265,45 @@ public QATopic findTopicByTopicId(String topicId) { } @Override - public List findAllTopics(long offset, long count, String orderField, boolean ascending) { - return findAllTopicsBySource(null, offset, count, orderField, ascending); + 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(String source, long offset, long count, - String orderField, boolean ascending) { + public List findAllTopicsBySource(Context context, String source, long offset, + long count, String orderField, boolean ascending) { + return findAllTopicsBySourceAndTarget(context, source, null, offset, count, orderField, ascending); + } - if (source != null && isNotSupportedSource(source)) { - return null; + @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); - solrQuery.setSort(orderField, ascending ? ORDER.asc : ORDER.desc); - solrQuery.setFacetSort(FacetParams.FACET_SORT_INDEX); - solrQuery.setQuery("*:*"); + 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 + count)); + solrQuery.setFacetLimit((int) (offset + pageSize)); solrQuery.addFacetField(TOPIC); - if (source != null) { - solrQuery.addFilterQuery(SOURCE + ":" + source); + 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); - topics = new ArrayList<>(); int idx = 0; for (Count c : facetField.getValues()) { if (idx < offset) { @@ -226,7 +311,9 @@ public List findAllTopicsBySource(String source, long offset, long coun 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); @@ -261,18 +348,85 @@ public void store(Context context, QAEvent dto) { 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 param = new SolrQuery(EVENT_ID + ":" + eventId); - QueryResponse response; + SolrQuery solrQuery = new SolrQuery("*:*"); + solrQuery.addFilterQuery(EVENT_ID + ":\"" + eventId + "\""); try { - response = getSolr().query(param); + QueryResponse response = getSolr().query(solrQuery); if (response != null) { SolrDocumentList list = response.getResults(); if (list != null && list.size() == 1) { @@ -287,8 +441,12 @@ public QAEvent findEventByEventId(String eventId) { } @Override - public List findEventsByTopicAndPage(String topic, long offset, - int pageSize, String orderField, boolean ascending) { + 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()); @@ -296,75 +454,91 @@ public List findEventsByTopicAndPage(String topic, long offset, 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 + "\""); - QueryResponse response; try { - response = getSolr().query(solrQuery); + QueryResponse response = getSolr().query(solrQuery); if (response != null) { - SolrDocumentList list = response.getResults(); + SolrDocumentList solrDocuments = response.getResults(); List responseItem = new ArrayList<>(); - for (SolrDocument doc : list) { + for (SolrDocument doc : solrDocuments) { QAEvent item = getQAEventFromSOLR(doc); responseItem.add(item); } return responseItem; } } catch (SolrServerException | IOException e) { - throw new RuntimeException(e); + throw new RuntimeException(e.getMessage(), e); } - return List.of(); } @Override - public List findEventsByTopic(String topic) { - return findEventsByTopicAndPage(topic, 0, -1, TRUST, false); - } + 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); - @Override - public long countEventsByTopic(String topic) { SolrQuery solrQuery = new SolrQuery(); solrQuery.setRows(0); - solrQuery.setQuery(TOPIC + ":" + topic.replace("!", "/")); - QueryResponse response = null; + solrQuery.setQuery(securityQuery.orElse("*:*")); + solrQuery.addFilterQuery(SOURCE + ":\"" + sourceName + "\""); + solrQuery.setQuery(TOPIC + ":" + topic.replaceAll("!", "/")); try { - response = getSolr().query(solrQuery); - return response.getResults().getNumFound(); + return getSolr().query(solrQuery).getResults().getNumFound(); } catch (SolrServerException | IOException e) { - throw new RuntimeException(e); + throw new RuntimeException(e.getMessage(), e); } } @Override - public QASource findSource(String sourceName) { + public QASource findSource(Context context, String sourceName) { + String[] split = sourceName.split(":"); + return findSource(context, split[0], split.length == 2 ? UUID.fromString(split[1]) : null); + } - if (isNotSupportedSource(sourceName)) { + @Override + public QASource findSource(Context context, String sourceName, UUID target) { + EPerson currentUser = context.getCurrentUser(); + if (isNotSupportedSource(sourceName) || !qaSecurityService.canSeeSource(context, currentUser, sourceName)) { return null; } - SolrQuery solrQuery = new SolrQuery("*:*"); + Optional securityQuery = qaSecurityService.generateQAEventFilterQuery(context, currentUser, sourceName); + + SolrQuery solrQuery = new SolrQuery(); + solrQuery.setQuery(securityQuery.orElse("*:*")); solrQuery.setRows(0); - solrQuery.addFilterQuery(SOURCE + ":" + sourceName); + solrQuery.addFilterQuery(SOURCE + ":\"" + sourceName + "\""); + if (target != null) { + solrQuery.addFilterQuery("resource_uuid:" + target.toString()); + } solrQuery.setFacet(true); solrQuery.setFacetMinCount(1); solrQuery.addFacetField(SOURCE); - QueryResponse response; try { - response = getSolr().query(solrQuery); + 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); + throw new RuntimeException(e.getMessage(), e); } QASource source = new QASource(); @@ -375,18 +549,33 @@ public QASource findSource(String sourceName) { } @Override - public List findAllSources(long offset, int pageSize) { + 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(sourceName)) - .sorted(comparing(QASource::getTotalEvents).reversed()) - .skip(offset) - .limit(pageSize) - .collect(Collectors.toList()); + .map((sourceName) -> findSource(context, sourceName)) + .filter(Objects::nonNull) + .filter(source -> source.getTotalEvents() > 0) + .count(); } @Override - public long countSources() { - return getSupportedSources().length; + 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 @@ -405,10 +594,11 @@ private SolrInputDocument createSolrDocument(Context context, QAEvent dto, Strin doc.addField(TRUST, dto.getTrust()); doc.addField(MESSAGE, dto.getMessage()); doc.addField(LAST_UPDATE, new Date()); - final String resourceUUID = getResourceUUID(context, dto.getOriginalId()); + String resourceUUID = getResourceUUID(context, dto.getOriginalId()); if (resourceUUID == null) { - throw new IllegalArgumentException("Skipped event " + checksum + - " related to the oai record " + dto.getOriginalId() + " as the record was not found"); + 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()); @@ -437,7 +627,7 @@ private String getHandleFromOriginalId(String originalId) { if (startPosition != -1) { return originalId.substring(startPosition + 1, originalId.length()); } else { - return null; + return originalId; } } @@ -456,12 +646,128 @@ private QAEvent getQAEventFromSOLR(SolrDocument doc) { 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("qaevent.sources", new String[] { QAEvent.OPENAIRE_SOURCE }); + 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 34ab572d1b16..8b43ad69d78c 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.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 void consumeCommunityCollectionItem(Context ctx, Event event) throws SQLE } 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 1e9744aec5c5..7dea426228ce 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.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 @@ private 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 static Model convert(Context context, DSpaceObject dso) 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 ac4e341c5e75..7f0358dfbedd 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.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.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 void delete(DSpaceObject dso, boolean reset) } 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 @@ protected void dspaceDFS(DSpaceObject dso, Callback callback, boolean check, boo } 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 @@ protected Options createOptions() { 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 f0ad88c5827d..fa00374f9dd0 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 @@ 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 0d0f955e0ad3..ca9583806114 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 static String getURI() { 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 72ba03d99d27..c9e55cba1924 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.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 6286f3b87a66..4117f3549b48 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.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 boolean fulfills(String value) { } 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 @@ protected Property parsePredicate(Model m, Resource predicate, String dsoIRI, 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 @@ protected String parseValueProcessor(Resource valueProcessor, String value) { 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 d8e71856a1b3..7617355db667 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 @@ 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 93a9b6211d57..0842bf5e40c8 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 @@ 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 63382a7c26b0..4665625702dd 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.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 f86af753e690..f18074a1bdfe 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.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 6b2caa598dc7..f652838121a9 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 MediaRange(String 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 MediaRange(String 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 998f57ca4feb..d210803ba793 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 @@ 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 d011d305b166..07891b6d6d9f 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.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 @@ */ 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 31294323c745..52bee26d76f3 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 @@ 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 fd84db5d5f2d..2ea5c3c88082 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.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 void deleteAll() { 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 eab3ba460c09..375c39ca5587 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 @@ 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.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 @@ -51,8 +50,8 @@ public class Process implements ReloadableEntity { @Column(name = "process_id", unique = true, nullable = false) private Integer processId; - @ManyToOne - @JoinColumn(name = "user_id", nullable = false) + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") private EPerson ePerson; @Column(name = "start_time") @@ -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/ProcessServiceImpl.java b/dspace-api/src/main/java/org/dspace/scripts/ProcessServiceImpl.java index 2e14aeaa36c0..ab5147221cfc 100644 --- a/dspace-api/src/main/java/org/dspace/scripts/ProcessServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/scripts/ProcessServiceImpl.java @@ -45,14 +45,15 @@ import org.dspace.core.LogHelper; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; -import org.dspace.eperson.service.EPersonService; import org.dspace.scripts.service.ProcessService; +import org.dspace.services.ConfigurationService; +import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; /** * The implementation for the {@link ProcessService} class */ -public class ProcessServiceImpl implements ProcessService { +public class ProcessServiceImpl implements ProcessService, InitializingBean { private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(ProcessService.class); @@ -72,7 +73,34 @@ public class ProcessServiceImpl implements ProcessService { private MetadataFieldService metadataFieldService; @Autowired - private EPersonService ePersonService; + private ConfigurationService configurationService; + + @Override + public void afterPropertiesSet() throws Exception { + try { + Context context = new Context(); + + // Processes that were running or scheduled when tomcat crashed, should be cleaned up during startup. + List processesToBeFailed = findByStatusAndCreationTimeOlderThan( + context, List.of(ProcessStatus.RUNNING, ProcessStatus.SCHEDULED), new Date()); + for (Process process : processesToBeFailed) { + context.setCurrentUser(process.getEPerson()); + // Fail the process. + log.info("Process with ID {} did not complete before tomcat shutdown, failing it now.", + process.getID()); + fail(context, process); + // But still attach its log to the process. + appendLog(process.getID(), process.getName(), + "Process did not complete before tomcat shutdown.", + ProcessLogLevel.ERROR); + createLogBitstream(context, process); + } + + context.complete(); + } catch (Exception e) { + log.error("Unable to clean up Processes: ", e); + } + } @Override public Process create(Context context, EPerson ePerson, String scriptName, @@ -92,10 +120,17 @@ public Process create(Context context, EPerson ePerson, String scriptName, }); Process createdProcess = processDAO.create(context, process); - log.info(LogHelper.getHeader(context, "process_create", - "Process has been created for eperson with email " + ePerson.getEmail() - + " with ID " + createdProcess.getID() + " and scriptName " + - scriptName + " and parameters " + parameters)); + + if (ePerson != null) { + log.info(LogHelper.getHeader(context, "process_create", + "Process has been created for eperson with email " + ePerson.getEmail() + + " with ID " + createdProcess.getID() + " and scriptName " + + scriptName + " and parameters " + parameters)); + } else { + log.info(LogHelper.getHeader(context, "process_create", + "Process has been created for command-line user with ID " + createdProcess.getID() + + " and scriptName " + scriptName + " and parameters " + parameters)); + } return createdProcess; } @@ -286,8 +321,8 @@ public int countSearch(Context context, ProcessQueryParameterContainer processQu @Override public void appendLog(int processId, String scriptName, String output, ProcessLogLevel processLogLevel) throws IOException { - File tmpDir = FileUtils.getTempDirectory(); - File tempFile = new File(tmpDir, scriptName + processId + ".log"); + File logsDir = getLogsDirectory(); + File tempFile = new File(logsDir, processId + "-" + scriptName + ".log"); FileWriter out = new FileWriter(tempFile, true); try { try (BufferedWriter writer = new BufferedWriter(out)) { @@ -302,12 +337,15 @@ public void appendLog(int processId, String scriptName, String output, ProcessLo @Override public void createLogBitstream(Context context, Process process) throws IOException, SQLException, AuthorizeException { - File tmpDir = FileUtils.getTempDirectory(); - File tempFile = new File(tmpDir, process.getName() + process.getID() + ".log"); - FileInputStream inputStream = FileUtils.openInputStream(tempFile); - appendFile(context, process, inputStream, Process.OUTPUT_TYPE, process.getName() + process.getID() + ".log"); - inputStream.close(); - tempFile.delete(); + File logsDir = getLogsDirectory(); + File tempFile = new File(logsDir, process.getID() + "-" + process.getName() + ".log"); + if (tempFile.exists()) { + FileInputStream inputStream = FileUtils.openInputStream(tempFile); + appendFile(context, process, inputStream, Process.OUTPUT_TYPE, + process.getID() + "-" + process.getName() + ".log"); + inputStream.close(); + tempFile.delete(); + } } @Override @@ -336,4 +374,15 @@ private String formatLogLine(int processId, String scriptName, String output, Pr return sb.toString(); } + private File getLogsDirectory() { + String pathStr = configurationService.getProperty("dspace.dir") + + File.separator + "log" + File.separator + "processes"; + File logsDir = new File(pathStr); + if (!logsDir.exists()) { + if (!logsDir.mkdirs()) { + throw new RuntimeException("Couldn't create [dspace.dir]/log/processes/ directory."); + } + } + return logsDir; + } } 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 abb700cb10c9..1d9773f5d617 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.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 DSpaceRunnable createDSpaceRunnableForScriptConfiguration(ScriptConfigura 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 bbedab04e278..70debc172ae9 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,7 +107,7 @@ public boolean isAllowedToExecute(Context context, List 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 c5f7c46b586e..31a8ed12c138 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 ef2a612133c9..aef062bc5c38 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 @@ private static void 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 000000000000..63496ee5dc82 --- /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 000000000000..1971e408634c --- /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 5f976bbfd94b..90bc1751f9b7 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.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 @@ * @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 String text() { } 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 void process(SolrInputDocument doc) throws IOException, SolrServerExcepti } } - @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 void process(SolrInputDocument document) { 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 @@ protected String getDateView(String name, String type, Context context) { } catch (ParseException e1) { e1.printStackTrace(); } - // e.printStackTrace(); } String dateformatString = "dd-MM-yyyy"; if ("DAY".equals(type)) { @@ -1117,7 +1069,7 @@ public QueryResponse query(String query, String filterQuery, String facetField, String facetQuery = facetQueries.get(i); solrQuery.addFacetQuery(facetQuery); } - if (0 < facetQueries.size()) { + if (!facetQueries.isEmpty()) { solrQuery.setFacet(true); } } @@ -1135,13 +1087,7 @@ public QueryResponse query(String query, String filterQuery, String facetField, // 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 QueryResponse query(String query, String filterQuery, String facetField, 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 QueryResponse query(String query, String filterQuery, String facetField, 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 void shardSolrIndex() throws IOException, SolrServerException { 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 @@ protected void addAdditionalSolrYearCores(SolrQuery solrQuery) { * 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 @@ protected synchronized void initSolrYearCores() { 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 9ad72cbf313b..d7d249082bb4 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 @@ 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 85bff41593e6..2b4247e2a7f8 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 @@ 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 121e66af4875..ba1c51e2078a 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 @@ && getDatasetGenerators().get(1) != null && getDatasetGenerators() 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 cb8e64cc65e7..cf0972888e06 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 List findAll(Context context) throws SQLException { /** * 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 b853f255e841..47809ad2309b 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 85cb7bc14c14..41cf655dbcd5 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.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 609298779d34..434de459bad9 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.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 92adb67546ef..76ea30245dd7 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.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 9b482e3d5493..ed6f8bc3bf9e 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 61b2bb6013de..74b729c3536a 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.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 Map> queryField(String query, List oldFieldVals, String field) throws IOException; - public void markRobotsByIP(); - - public void markRobotByUserAgent(String agent); - - public void deleteRobotsByIsBotFlag(); - - public void deleteIP(String ip); + /** + * 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 deleteRobotsByIP(); + /** + * Delete all 'statistics' documents having 'isBot:true'. + */ + public void deleteRobots(); /* * update(String query, boolean addField, String fieldName, Object @@ -259,13 +262,6 @@ public QueryResponse query(String query, String filterQuery, 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 cb94dcc1a195..9d571cb7512b 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 static String longToIp(long ip) { * @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 c4c5765e0880..fa5f71e32631 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 @@ 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 @@ * @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 710e8472d2a8..cf5a5d4319cb 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 @@ 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 0b5149df7451..f1b03b5439fa 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.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 @@ */ 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 SpiderDetectorServiceImpl(ConfigurationService configurationService, Clie 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 boolean isSpider(String clientIP, String proxyIPs, String hostname, Strin 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 @@ private void loadPatterns(String directory, List patternList) { 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 @@ private void loadPatterns(String directory, List patternList) { } - 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 boolean isSpider(HttpServletRequest request) { 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 boolean isSpider(String ip) { 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 synchronized void loadSpiderIpAddresses() { } /** - * 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 @@ private boolean isUseCaseInsensitiveMatching() { 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 319fe437d648..95c4f1f81362 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 static void main(String[] args) throws Exception { 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 static void main(String[] args) throws Exception { 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 static void main(String[] args) throws Exception { } /** - * 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 956ac5a7f8f1..290e480a93f2 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.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 UUID store(Context context, Bitstream bitstream, InputStream is) throws S * @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 void cleanup(boolean deleteDbRecords, boolean verbose) throws SQLExceptio 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 7a09dd2e76df..4d32b50d67d0 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 @@ -10,6 +10,7 @@ import static java.lang.String.valueOf; import java.io.File; +import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; @@ -21,7 +22,6 @@ 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; @@ -38,6 +38,7 @@ 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; @@ -102,6 +103,11 @@ public class S3BitStoreService extends BaseBitStoreService { private String awsRegionName; private boolean useRelativePath; + /** + * The maximum size of individual chunk to download from S3 when a file is accessed. Default 5Mb + */ + private long bufferSize = 5 * 1024 * 1024; + /** * container for all the assets */ @@ -169,7 +175,7 @@ public boolean isEnabled() { @Override public void init() throws IOException { - if (this.isInitialized()) { + if (this.isInitialized() || !this.isEnabled()) { return; } @@ -258,20 +264,7 @@ public InputStream get(Bitstream bitstream) throws IOException { if (isRegisteredBitstream(key)) { key = key.substring(REGISTERED_FLAG.length()); } - try { - File tempFile = File.createTempFile("s3-disk-copy-" + UUID.randomUUID(), "temp"); - tempFile.deleteOnExit(); - - GetObjectRequest getObjectRequest = new GetObjectRequest(bucketName, key); - - Download download = tm.download(getObjectRequest, tempFile); - download.waitForCompletion(); - - return new DeleteOnCloseFileInputStream(tempFile); - } catch (AmazonClientException | InterruptedException e) { - log.error("get(" + key + ")", e); - throw new IOException(e); - } + return new S3LazyInputStream(key, bufferSize, bitstream.getSizeBytes()); } /** @@ -560,7 +553,7 @@ public static void main(String[] args) throws Exception { 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(); @@ -622,4 +615,84 @@ public boolean isRegisteredBitstream(String internalId) { return internalId.startsWith(REGISTERED_FLAG); } + public void setBufferSize(long bufferSize) { + this.bufferSize = bufferSize; + } + + /** + * This inner class represent an InputStream that uses temporary files to + * represent chunk of the object downloaded from S3. When the input stream is + * read the class look first to the current chunk and download a new one once if + * the current one as been fully read. The class is responsible to close a chunk + * as soon as a new one is retrieved, the last chunk is closed when the input + * stream itself is closed or the last byte is read (the first of the two) + */ + public class S3LazyInputStream extends InputStream { + private InputStream currentChunkStream; + private String objectKey; + private long endOfChunk = -1; + private long chunkMaxSize; + private long currPos = 0; + private long fileSize; + + public S3LazyInputStream(String objectKey, long chunkMaxSize, long fileSize) throws IOException { + this.objectKey = objectKey; + this.chunkMaxSize = chunkMaxSize; + this.endOfChunk = 0; + this.fileSize = fileSize; + downloadChunk(); + } + + @Override + public int read() throws IOException { + // is the current chunk completely read and other are available? + if (currPos == endOfChunk && currPos < fileSize) { + currentChunkStream.close(); + downloadChunk(); + } + + int byteRead = currPos < endOfChunk ? currentChunkStream.read() : -1; + // do we get any data or are we at the end of the file? + if (byteRead != -1) { + currPos++; + } else { + currentChunkStream.close(); + } + return byteRead; + } + + /** + * This method download the next chunk from S3 + * + * @throws IOException + * @throws FileNotFoundException + */ + private void downloadChunk() throws IOException, FileNotFoundException { + // Create a DownloadFileRequest with the desired byte range + long startByte = currPos; // Start byte (inclusive) + long endByte = Long.min(startByte + chunkMaxSize - 1, fileSize - 1); // End byte (inclusive) + GetObjectRequest getRequest = new GetObjectRequest(bucketName, objectKey) + .withRange(startByte, endByte); + + File currentChunkFile = File.createTempFile("s3-disk-copy-" + UUID.randomUUID(), "temp"); + currentChunkFile.deleteOnExit(); + try { + Download download = tm.download(getRequest, currentChunkFile); + download.waitForCompletion(); + currentChunkStream = new DeleteOnCloseFileInputStream(currentChunkFile); + endOfChunk = endOfChunk + download.getProgress().getBytesTransferred(); + } catch (AmazonClientException | InterruptedException e) { + currentChunkFile.delete(); + throw new IOException(e); + } + } + + @Override + public void close() throws IOException { + if (currentChunkStream != null) { + currentChunkStream.close(); + } + } + + } } 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 7f5ed8f9129f..8c3a67ee9a08 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.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 0732eea2a0b9..7a2d3a8b740a 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 @@ private synchronized static FluentConfiguration setupFlyway(DataSource datasourc // 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 0d049bb6ac00..38bb6399833d 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 @@ * @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 7debf3ba449b..1419910d5206 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.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.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 @@ private void updateRegistries() { 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 f0c4e4e17990..28df30d8702c 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 @@ protected static Integer dropDBConstraint(Connection connection, String tableNam // 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 758e745ddc86..801e3faadb83 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 @@ * 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 37100a17f926..2f24f4c25453 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 @@ * 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 8e2be91127c8..337190ec390c 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 @@ * 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 daf2269e922a..5893650a42fc 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 @@ private void removeDBMSBrowseTables(Connection connection) // 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 21ebcd5edd98..973134bfd1da 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.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 0361e6805356..d461da4d2db0 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.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 @@ */ 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/consumer/SubmissionConfigConsumer.java b/dspace-api/src/main/java/org/dspace/submit/consumer/SubmissionConfigConsumer.java index a593fe8ae066..0cf4ae92c2ca 100644 --- a/dspace-api/src/main/java/org/dspace/submit/consumer/SubmissionConfigConsumer.java +++ b/dspace-api/src/main/java/org/dspace/submit/consumer/SubmissionConfigConsumer.java @@ -8,15 +8,10 @@ package org.dspace.submit.consumer; import org.apache.logging.log4j.Logger; -import org.dspace.content.Collection; -import org.dspace.content.DSpaceObject; import org.dspace.core.Constants; import org.dspace.core.Context; -import org.dspace.discovery.IndexingService; -import org.dspace.discovery.indexobject.IndexableCollection; import org.dspace.event.Consumer; import org.dspace.event.Event; -import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.submit.factory.SubmissionServiceFactory; /** @@ -28,11 +23,9 @@ public class SubmissionConfigConsumer implements Consumer { /** * log4j logger */ - private static Logger log = org.apache.logging.log4j.LogManager.getLogger(SubmissionConfigConsumer.class); + private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(SubmissionConfigConsumer.class); - IndexingService indexer = DSpaceServicesFactory.getInstance().getServiceManager() - .getServiceByName(IndexingService.class.getName(), - IndexingService.class); + private boolean reloadNeeded = false; @Override public void initialize() throws Exception { @@ -42,37 +35,27 @@ public void initialize() throws Exception { @Override public void consume(Context ctx, Event event) throws Exception { int st = event.getSubjectType(); - int et = event.getEventType(); + if (st == Constants.COLLECTION) { + // NOTE: IndexEventConsumer ("discovery") should be declared before this consumer + // We don't reindex the collection because it will normally be reindexed by IndexEventConsumer + // before the submission configurations are reloaded - if ( st == Constants.COLLECTION ) { - switch (et) { - case Event.MODIFY_METADATA: - // Submission configuration it's based on solr - // for collection's entity type but, at this point - // that info isn't indexed yet, we need to force it - DSpaceObject subject = event.getSubject(ctx); - Collection collectionFromDSOSubject = (Collection) subject; - indexer.indexContent(ctx, new IndexableCollection (collectionFromDSOSubject), true, false, false); - indexer.commit(); - - log.debug("SubmissionConfigConsumer occured: " + event.toString()); - // reload submission configurations - SubmissionServiceFactory.getInstance().getSubmissionConfigService().reload(); - break; - - default: - log.debug("SubmissionConfigConsumer occured: " + event.toString()); - // reload submission configurations - SubmissionServiceFactory.getInstance().getSubmissionConfigService().reload(); - break; - } + log.debug("SubmissionConfigConsumer occurred: " + event); + // submission configurations should be reloaded + reloadNeeded = true; } } @Override public void end(Context ctx) throws Exception { - // No-op + if (reloadNeeded) { + // reload submission configurations + SubmissionServiceFactory.getInstance().getSubmissionConfigService().reload(); + + // Reset the boolean used + reloadNeeded = false; + } } @Override 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 a6421b3f7adb..757a01cb1cc4 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/subscriptions/ContentGenerator.java b/dspace-api/src/main/java/org/dspace/subscriptions/ContentGenerator.java index c3035614343b..e8f6996cbde8 100644 --- a/dspace-api/src/main/java/org/dspace/subscriptions/ContentGenerator.java +++ b/dspace-api/src/main/java/org/dspace/subscriptions/ContentGenerator.java @@ -89,6 +89,7 @@ private String generateBodyMail(Context context, List indexable .orElseGet(() -> entityType2Disseminator.get("Item")) .disseminate(context, item, out); } + out.close(); return out.toString(); } catch (Exception e) { log.error(e.getMessage(), e); 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 52d5dacb74bb..6b447361efb0 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 09cd0841e78f..ac056d553054 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 @@ 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 dc1aeb56c94b..9ed663c39de2 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.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 @@ private void init() { 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 synchronized void receiveEvent(Event event) { 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 ec9a2b12641a..90dd9e47e2e2 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 938f9e798452..f285a4f90aec 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/usage/package-info.java b/dspace-api/src/main/java/org/dspace/usage/package-info.java index 5883bcf358f4..26984ae0caa0 100644 --- a/dspace-api/src/main/java/org/dspace/usage/package-info.java +++ b/dspace-api/src/main/java/org/dspace/usage/package-info.java @@ -25,7 +25,7 @@ * {@code EventService}, as with the stock listeners. *

    * - * @see org.dspace.google.GoogleRecorderEventListener + * @see org.dspace.google.GoogleAsyncEventListener * @see org.dspace.statistics.SolrLoggerUsageEventListener */ 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 000000000000..8b982d454440 --- /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 a50baf910e77..d183ec7eace3 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 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.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 @@ private Optional generateUrlWithSearchService(Item item, String uiURLSte } } } 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 000000000000..7f74f0a728cf --- /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 87dc6d9db846..de3ef92619a0 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.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 @@ * @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 void setPatterns(Map patterns) { 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 void setPatterns(Map patterns) { 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 @@ static public Date parse(String dateString) { } } 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/SolrImportExport.java b/dspace-api/src/main/java/org/dspace/util/SolrImportExport.java index b23e4396e27c..c132af1855d1 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 @@ private static void reindex(String indexName, String exportDirName, boolean keep + ") 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 9342cb8b39e8..044d9792beac 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 @@ private enum FIELD { //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 @@ private static Options makeOptions() { 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 static void main(String[] args) throws ParseException { 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 @@ private long runReportQuery() throws SolrServerException, IOException { } 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 @@ private int updateRecords(String query) throws SolrServerException, SQLException 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 329332d31526..1dae5427f8d0 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 @@ protected void createBundlesAndAddBitstreams(Context c, Item itemNew, Item nativ // 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 d4590ae24ea2..c41b985690d2 100644 --- a/dspace-api/src/main/java/org/dspace/versioning/DefaultItemVersionProvider.java +++ b/dspace-api/src/main/java/org/dspace/versioning/DefaultItemVersionProvider.java @@ -52,7 +52,9 @@ public class DefaultItemVersionProvider extends AbstractVersionProvider implemen @Override public Item createNewItemAndAddItInWorkspace(Context context, Item nativeItem) { try { - WorkspaceItem workspaceItem = workspaceItemService.create(context, nativeItem.getOwningCollection(), false); + WorkspaceItem workspaceItem = workspaceItemService.create(context, nativeItem.getOwningCollection(), + false, + true); Item itemNew = workspaceItem.getItem(); itemService.update(context, itemNew); return itemNew; @@ -70,7 +72,7 @@ public void deleteVersionedItem(Context c, Version versionToDelete, VersionHisto 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 @@ -114,8 +116,8 @@ public Item updateItemState(Context c, Item itemNew, Item previousItem) { // 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 ee5c1c418338..ada857c1cf17 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 231ccc29d973..f207fe8d4f03 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 @@ 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 ece536e81b26..b9c3a524a1bf 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 @@ protected Version createVersion(Context c, VersionHistory vh, Item item, String 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 0e28e72d0752..0ef01b8262ed 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 @@ 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 List findVersionsWithItems(Context context, VersionHistory versi ) ); - List orderList = new LinkedList<>(); + List orderList = new LinkedList<>(); orderList.add(criteriaBuilder.desc(versionRoot.get(Version_.versionNumber))); criteriaQuery.orderBy(orderList); @@ -84,9 +84,9 @@ public List findVersionsWithItems(Context context, VersionHistory versi @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 eac78c3e6215..b71ef59f2f94 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 @@ 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 VersionHistory findByItem(Context context, Item item) throws SQLException 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 4bdf26c05381..a17d05002b62 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 @@ 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 be870ee33e76..675fbb03be2d 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.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 b133c51cfb52..8709481834ba 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 @@ 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 112e7c558500..94e92a67135d 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.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 951940d5bb77..a077bfddbeae 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 aecdccd55af3..82296b38f1b7 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.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 a81bebcd5a35..b4e9f3d7f130 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.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 bc91a1fd9298..06ded267d18a 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.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 WorkflowActionConfig processOutcome(Context c, EPerson user, Workflow wor || 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) { @@ -1177,7 +1177,7 @@ protected WorkspaceItem returnToWorkspace(Context c, XmlWorkflowItem wfi) public String getEPersonName(EPerson ePerson) { String submitter = ePerson.getFullName(); - submitter = submitter + "(" + ePerson.getEmail() + ")"; + submitter = submitter + " (" + ePerson.getEmail() + ")"; return submitter; } @@ -1191,14 +1191,20 @@ protected void recordStart(Context context, Item myitem, Action action) // 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 849010751831..c780d66a0c89 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.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 6b03803d8a10..6dce02de8347 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 @@ 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 1cfa33b12170..f16ef419e726 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.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 @@ protected List getAdvancedInfo() { * 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 67b400c6592e..b55df1a5db2b 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.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 9b83be5d7bfa..b287d0c3d1ad 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 @@ 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 7a1c62adbd1e..6a41f40398cb 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 @@ 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 bd74ab3c7152..ad2cbe06b13e 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.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 16d35b36683a..c8fd3ecb6692 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.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 43a3decacc7e..50f338499282 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 @@ -8,11 +8,12 @@ package org.dspace.xmlworkflow.state.actions.processingaction; import java.sql.SQLException; +import java.util.ArrayList; 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; @@ -20,6 +21,8 @@ import org.dspace.authorize.AuthorizeException; import org.dspace.content.MetadataFieldName; import org.dspace.core.Context; +import org.dspace.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.xmlworkflow.service.WorkflowRequirementsService; import org.dspace.xmlworkflow.state.Step; import org.dspace.xmlworkflow.state.actions.ActionAdvancedInfo; @@ -34,6 +37,9 @@ public class ScoreReviewAction extends ProcessingAction { private static final Logger log = LogManager.getLogger(ScoreReviewAction.class); + private final ConfigurationService configurationService + = DSpaceServicesFactory.getInstance().getConfigurationService(); + // Option(s) public static final String SUBMIT_SCORE = "submit_score"; @@ -114,7 +120,14 @@ private boolean checkRequestValid(int score, String review) { @Override public List getOptions() { - return List.of(SUBMIT_SCORE, RETURN_TO_POOL); + List options = new ArrayList<>(); + options.add(SUBMIT_SCORE); + if (configurationService.getBooleanProperty("workflow.reviewer.file-edit", false)) { + options.add(SUBMIT_EDIT_METADATA); + } + options.add(RETURN_TO_POOL); + + return options; } @Override 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 0e8ab40a5205..4adeb127eaed 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.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 b3fe896ace24..c46fa851e4f1 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.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; @@ -21,6 +21,8 @@ import org.dspace.content.factory.ContentServiceFactory; import org.dspace.core.Context; import org.dspace.eperson.EPerson; +import org.dspace.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.workflow.WorkflowException; import org.dspace.xmlworkflow.factory.XmlWorkflowServiceFactory; import org.dspace.xmlworkflow.state.Step; @@ -40,6 +42,9 @@ public class SingleUserReviewAction extends ProcessingAction { private static final Logger log = LogManager.getLogger(SingleUserReviewAction.class); + private final ConfigurationService configurationService + = DSpaceServicesFactory.getInstance().getConfigurationService(); + public static final int OUTCOME_REJECT = 1; protected static final String SUBMIT_DECLINE_TASK = "submit_decline_task"; @@ -95,6 +100,9 @@ public ActionResult processAccept(Context c, XmlWorkflowItem wfi) throws SQLExce public List getOptions() { List options = new ArrayList<>(); options.add(SUBMIT_APPROVE); + if (configurationService.getBooleanProperty("workflow.reviewer.file-edit", false)) { + options.add(SUBMIT_EDIT_METADATA); + } options.add(SUBMIT_REJECT); options.add(SUBMIT_DECLINE_TASK); return options; 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 e837a8a89394..4d645ae6a5c1 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 @@ 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 0cd82fe77084..79e2ffc90068 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.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 401a7c506b98..cf3ceff5752a 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.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 21fcf6f30996..385d6feea666 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.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 1ffce1afdb4e..25be8da25cfc 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 @@ 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 d23a98cedbbf..9606892554a8 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 @@ 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 6a0d3e9ca686..f405242aab4a 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 @@ 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 boolean isValidUserSelection(Context context, XmlWorkflowItem wf 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 8f4794cb3b45..adaf0ec06e2c 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 c9a7995e0390..4cbaf1c4112f 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 efbd26bde5f5..38f410566d1b 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 9cfc9ea06826..0c6b01f5c1fa 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 fb673725e181..d3c8f6334d8f 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.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 PoolTask findByWorkflowIdAndEPerson(Context context, XmlWorkflowItem work //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 cc6df9731baa..a0d46c27d1eb 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 f6ffe6049a5e..529a35bda3a2 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 956a4648c53a..70fe74de8a2f 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 @@ 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 b3cd32c74f0d..5c19fd5a8e59 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 @@ 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 783d403c054a..a02a947e2c68 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 @@ 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 int countInProgressUsers(Context context, XmlWorkflowItem workflowItem) t 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 int countFinishedUsers(Context context, XmlWorkflowItem workflowItem) thr 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 0857a325b5df..2e95c2f9d0bb 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 @@ 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 fdc2413b5ffd..6dc2fc9601ce 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 @@ 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 659a2123d90a..8994efae1af2 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 @@ 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 int countAll(Context context) throws SQLException { @Override public int countAllInCollection(Context context, Collection collection) throws SQLException { - - CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); CriteriaQuery 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 int countBySubmitter(Context context, EPerson ep) throws SQLException { CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); CriteriaQuery 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 efbbeedde053..3304e8ce8d71 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/V7.6_2023.09.28__enforce_group_or_eperson_for_resourcepolicy.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2023.09.28__enforce_group_or_eperson_for_resourcepolicy.sql new file mode 100644 index 000000000000..61f361625dd9 --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2023.09.28__enforce_group_or_eperson_for_resourcepolicy.sql @@ -0,0 +1,12 @@ +-- +-- The contents of this file are subject to the license and copyright +-- detailed in the LICENSE and NOTICE files at the root of the source +-- tree and available online at +-- +-- http://www.dspace.org/license/ +-- + +DELETE FROM ResourcePolicy WHERE eperson_id is null and epersongroup_id is null; + +ALTER TABLE ResourcePolicy ADD CONSTRAINT resourcepolicy_eperson_and_epersongroup_not_nullobject_chk + CHECK (eperson_id is not null or epersongroup_id is not null) ; diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2024.03.07__set_eperson_process_nullable.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2024.03.07__set_eperson_process_nullable.sql new file mode 100644 index 000000000000..39dc1115be49 --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2024.03.07__set_eperson_process_nullable.sql @@ -0,0 +1,9 @@ +-- +-- The contents of this file are subject to the license and copyright +-- detailed in the LICENSE and NOTICE files at the root of the source +-- tree and available online at +-- +-- http://www.dspace.org/license/ +-- + +ALTER TABLE process ALTER COLUMN user_id DROP NOT NULL; diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2024.05.07__process_eperson_constraint.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2024.05.07__process_eperson_constraint.sql new file mode 100644 index 000000000000..26de16f466ee --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2024.05.07__process_eperson_constraint.sql @@ -0,0 +1,13 @@ +-- +-- The contents of this file are subject to the license and copyright +-- detailed in the LICENSE and NOTICE files at the root of the source +-- tree and available online at +-- +-- http://www.dspace.org/license/ +-- + +-- If Process references an EPerson that no longer exists, set the "user_id" to null. +UPDATE process SET user_id = null WHERE NOT EXISTS (SELECT * FROM EPerson where uuid = process.user_id); + +-- Add new constraint where process.user_id is nullified if referenced EPerson is deleted. +ALTER TABLE process ADD CONSTRAINT process_eperson FOREIGN KEY (user_id) REFERENCES EPerson(uuid) ON DELETE SET NULL; 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 000000000000..f5ea59254f66 --- /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/V7.6_2023.09.28__enforce_group_or_eperson_for_resourcepolicy.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2023.09.28__enforce_group_or_eperson_for_resourcepolicy.sql new file mode 100644 index 000000000000..61f361625dd9 --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2023.09.28__enforce_group_or_eperson_for_resourcepolicy.sql @@ -0,0 +1,12 @@ +-- +-- The contents of this file are subject to the license and copyright +-- detailed in the LICENSE and NOTICE files at the root of the source +-- tree and available online at +-- +-- http://www.dspace.org/license/ +-- + +DELETE FROM ResourcePolicy WHERE eperson_id is null and epersongroup_id is null; + +ALTER TABLE ResourcePolicy ADD CONSTRAINT resourcepolicy_eperson_and_epersongroup_not_nullobject_chk + CHECK (eperson_id is not null or epersongroup_id is not null) ; diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2024.03.07__set_eperson_process_nullable.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2024.03.07__set_eperson_process_nullable.sql new file mode 100644 index 000000000000..39dc1115be49 --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2024.03.07__set_eperson_process_nullable.sql @@ -0,0 +1,9 @@ +-- +-- The contents of this file are subject to the license and copyright +-- detailed in the LICENSE and NOTICE files at the root of the source +-- tree and available online at +-- +-- http://www.dspace.org/license/ +-- + +ALTER TABLE process ALTER COLUMN user_id DROP NOT NULL; diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2024.05.07__process_eperson_constraint.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2024.05.07__process_eperson_constraint.sql new file mode 100644 index 000000000000..26de16f466ee --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2024.05.07__process_eperson_constraint.sql @@ -0,0 +1,13 @@ +-- +-- The contents of this file are subject to the license and copyright +-- detailed in the LICENSE and NOTICE files at the root of the source +-- tree and available online at +-- +-- http://www.dspace.org/license/ +-- + +-- If Process references an EPerson that no longer exists, set the "user_id" to null. +UPDATE process SET user_id = null WHERE NOT EXISTS (SELECT * FROM EPerson where uuid = process.user_id); + +-- Add new constraint where process.user_id is nullified if referenced EPerson is deleted. +ALTER TABLE process ADD CONSTRAINT process_eperson FOREIGN KEY (user_id) REFERENCES EPerson(uuid) ON DELETE SET NULL; 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 000000000000..f5ea59254f66 --- /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 184ee4bfb615..3c56e2a32399 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 7f6f872ce064..f7943fb2320c 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 + + + 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 921306ca2b56..feb3c7c12b3d 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/item-submission.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/item-submission.xml @@ -27,6 +27,8 @@ + + @@ -180,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 + + @@ -212,6 +227,9 @@ + + + @@ -272,6 +290,16 @@ + + + + + + + + + + diff --git a/dspace-api/src/test/data/dspaceFolder/config/local.cfg b/dspace-api/src/test/data/dspaceFolder/config/local.cfg index 05a4cc5add01..b44f319a35f6 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, qaeventsdelete +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, qaeventsdelete +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/item-filters.xml b/dspace-api/src/test/data/dspaceFolder/config/spring/api/item-filters.xml index 8bae32eaefd7..8af8bcff22fe 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 @@ -295,8 +295,7 @@ - + @@ -352,8 +351,7 @@ - + @@ -367,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 000000000000..8738d6cbef33 --- /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/solr-services.xml b/dspace-api/src/test/data/dspaceFolder/config/spring/api/solr-services.xml index 0a7d7c519629..d60dea2c3404 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 @@ - @@ -57,4 +55,10 @@ + + Connection to an embedded Solr instance. + + diff --git a/dspace-api/src/test/java/org/dspace/AbstractDSpaceIntegrationTest.java b/dspace-api/src/test/java/org/dspace/AbstractDSpaceIntegrationTest.java index 5a5ce8bf6d4c..516f13bfd84e 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.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 @@ protected 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 static void initTestEnvironment() { } } + @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 36477556d3de..136af83f076f 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 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 @@ protected 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 static void initKernel() { } } + @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 6884b949a66a..9bacbb97eec4 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 @@ 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; @@ -91,6 +94,14 @@ public static void initDatabase() { 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() @@ -179,29 +190,38 @@ public void destroy() throws Exception { 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(); - MockQAEventService qaEventService = serviceManager - .getServiceByName(QAEventService.class.getName(), MockQAEventService.class); - qaEventService.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(); @@ -210,7 +230,7 @@ public void destroy() throws Exception { // 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 51291ee9850d..bc1fa8a9bb22 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 @@ -262,10 +262,9 @@ public void testWithEmbargo() throws Exception { bitstream.setName(context, "primary"); bundle.setPrimaryBitstreamID(bitstream); List policies = new ArrayList<>(); - ResourcePolicy policy = resourcePolicyService.create(context); - policy.setRpName("Embargo"); Group group = groupService.findByName(context, Group.ANONYMOUS); - policy.setGroup(group); + ResourcePolicy policy = resourcePolicyService.create(context, null, group); + policy.setRpName("Embargo"); policy.setAction(Constants.READ); policy.setStartDate(dateFrom(9999, 12, 31)); policies.add(policy); @@ -291,10 +290,9 @@ public void testWithDateRestriction() throws Exception { bitstream.setName(context, "primary"); bundle.setPrimaryBitstreamID(bitstream); List policies = new ArrayList<>(); - ResourcePolicy policy = resourcePolicyService.create(context); - policy.setRpName("Restriction"); Group group = groupService.findByName(context, Group.ANONYMOUS); - policy.setGroup(group); + ResourcePolicy policy = resourcePolicyService.create(context, null, group); + policy.setRpName("Restriction"); policy.setAction(Constants.READ); policy.setStartDate(dateFrom(10000, 1, 1)); policies.add(policy); @@ -318,10 +316,9 @@ public void testWithGroupRestriction() throws Exception { bitstream.setName(context, "primary"); bundle.setPrimaryBitstreamID(bitstream); List policies = new ArrayList<>(); - ResourcePolicy policy = resourcePolicyService.create(context); - policy.setRpName("Restriction"); Group group = groupService.findByName(context, Group.ADMIN); - policy.setGroup(group); + ResourcePolicy policy = resourcePolicyService.create(context, null, group); + policy.setRpName("Restriction"); policy.setAction(Constants.READ); policies.add(policy); authorizeService.removeAllPolicies(context, bitstream); @@ -381,10 +378,9 @@ public void testWithPrimaryAndMultipleBitstreams() throws Exception { new ByteArrayInputStream("1".getBytes(StandardCharsets.UTF_8))); bundle.setPrimaryBitstreamID(primaryBitstream); List policies = new ArrayList<>(); - ResourcePolicy policy = resourcePolicyService.create(context); - policy.setRpName("Embargo"); Group group = groupService.findByName(context, Group.ANONYMOUS); - policy.setGroup(group); + ResourcePolicy policy = resourcePolicyService.create(context, null, group); + policy.setRpName("Embargo"); policy.setAction(Constants.READ); policy.setStartDate(dateFrom(9999, 12, 31)); policies.add(policy); @@ -412,10 +408,9 @@ public void testWithNoPrimaryAndMultipleBitstreams() throws Exception { Bitstream anotherBitstream = bitstreamService.create(context, bundle, new ByteArrayInputStream("1".getBytes(StandardCharsets.UTF_8))); List policies = new ArrayList<>(); - ResourcePolicy policy = resourcePolicyService.create(context); - policy.setRpName("Embargo"); Group group = groupService.findByName(context, Group.ANONYMOUS); - policy.setGroup(group); + ResourcePolicy policy = resourcePolicyService.create(context, null, group); + policy.setRpName("Embargo"); policy.setAction(Constants.READ); policy.setStartDate(dateFrom(9999, 12, 31)); policies.add(policy); 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 63340698ac00..ead338bc8e70 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 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.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 @@ * @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 static void tearDownClass() { * @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 void testImportStructureWithHandles() * @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 73f02e40494c..c115c04f6c9d 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 void performBulkAccessForSingleItemWithBitstreamConstraintsTest() throws 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 void performBulkAccessForSingleItemWithBitstreamConstraintsTest() throws 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 void performBulkAccessWithReplaceModeAndEmptyAccessConditionsTest() throw 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 void performBulkAccessWithAddModeTest() throws Exception { 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 void performBulkAccessWithReplaceModeTest() throws Exception { 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 void performBulkAccessWithReplaceModeOnItemsWithMultipleBundlesTest() thr 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/MetadataExportSearchIT.java b/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataExportSearchIT.java index 3a972692efeb..63a87a48f554 100644 --- a/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataExportSearchIT.java +++ b/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataExportSearchIT.java @@ -23,7 +23,8 @@ import com.google.common.io.Files; import com.opencsv.CSVReader; import com.opencsv.exceptions.CsvException; -import org.apache.log4j.Logger; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.AbstractIntegrationTestWithDatabase; import org.dspace.app.launcher.ScriptLauncher; import org.dspace.app.scripts.handler.impl.TestDSpaceRunnableHandler; @@ -51,7 +52,7 @@ public class MetadataExportSearchIT extends AbstractIntegrationTestWithDatabase private Item[] itemsSubject2 = new Item[numberItemsSubject2]; private String filename; private Collection collection; - private Logger logger = Logger.getLogger(MetadataExportSearchIT.class); + private Logger logger = LogManager.getLogger(MetadataExportSearchIT.class); private ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); private SearchService searchService; 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 e50f7913ad70..de1dcc91c9a1 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 @@ -9,12 +9,12 @@ import static junit.framework.TestCase.assertEquals; import static junit.framework.TestCase.assertTrue; +import static junit.framework.TestCase.fail; import java.io.BufferedWriter; import java.io.File; import java.io.FileOutputStream; import java.io.OutputStreamWriter; -import java.sql.SQLException; import java.util.List; import org.apache.commons.cli.ParseException; @@ -75,6 +75,31 @@ public void setUp() throws Exception { 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", @@ -218,9 +243,10 @@ public void personMetadataImportTest() throws Exception { @Test public void metadataImportRemovingValueTest() throws Exception { - context.turnOffAuthorisationSystem(); - Item item = ItemBuilder.createItem(context,personCollection).withAuthor("TestAuthorToRemove").withTitle("title") + String itemTitle = "Testing removing author"; + Item item = ItemBuilder.createItem(context,personCollection).withAuthor("TestAuthorToRemove") + .withTitle(itemTitle) .build(); context.restoreAuthSystemState(); @@ -229,22 +255,24 @@ public void metadataImportRemovingValueTest() throws Exception { 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("title"); + item = findItemByName(itemTitle); assertEquals(0, itemService.getMetadata(item, "dc", "contributor", "author", Item.ANY).size()); } - private Item findItemByName(String name) throws SQLException { - Item importedItem = null; - List allItems = IteratorUtils.toList(itemService.findAll(context)); - for (Item item : allItems) { - if (item.getName().equals(name)) { - importedItem = item; - } + private Item findItemByName(String name) throws Exception { + List items = + IteratorUtils.toList(itemService.findByMetadataField(context, "dc", "title", null, name)); + + if (items != null && !items.isEmpty()) { + // Just return first matching Item. Tests should ensure name/title is unique. + return items.get(0); + } else { + fail("Could not find expected Item with dc.title = '" + name + "'"); + return null; } - return importedItem; } public void performImportScript(String[] csv) throws Exception { 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 aee4b4d267cc..4329092b98d6 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 int performImportScript(String[] csv, boolean validateOnly) throws Except * * @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 6db37bdbcd05..af12cfd5538a 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 void setUp() throws Exception { @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 08ae3af4ae06..d6daa06aaa42 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 void setUp() throws Exception { @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 000000000000..4f5fa6762d31 --- /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 000000000000..73f97b2a6a7c --- /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 new file mode 100644 index 000000000000..938dc92de4b1 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/app/mediafilter/MediaFilterIT.java @@ -0,0 +1,237 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.mediafilter; + +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.io.InputStream; +import java.sql.SQLException; +import java.util.Iterator; +import java.util.List; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; +import org.dspace.AbstractIntegrationTestWithDatabase; +import org.dspace.authorize.AuthorizeException; +import org.dspace.builder.BitstreamBuilder; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.content.Bitstream; +import org.dspace.content.Bundle; +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.BitstreamService; +import org.dspace.content.service.ItemService; +import org.junit.Before; +import org.junit.Test; + +/** + * Tests of {@link MediaFilterScript}. + * + * @author Andrea Bollini + */ +public class MediaFilterIT extends AbstractIntegrationTestWithDatabase { + + private ItemService itemService = ContentServiceFactory.getInstance().getItemService(); + private BitstreamService bitstreamService = ContentServiceFactory.getInstance().getBitstreamService(); + protected Community topComm1; + protected Community topComm2; + protected Community childComm1_1; + protected Community childComm1_2; + protected Collection col1_1; + protected Collection col1_2; + protected Collection col1_1_1; + protected Collection col1_1_2; + protected Collection col1_2_1; + protected Collection col1_2_2; + protected Collection col2_1; + protected Item item1_1_a; + protected Item item1_1_b; + protected Item item1_2_a; + protected Item item1_2_b; + protected Item item1_1_1_a; + protected Item item1_1_1_b; + protected Item item1_1_2_a; + protected Item item1_1_2_b; + protected Item item1_2_1_a; + protected Item item1_2_1_b; + protected Item item1_2_2_a; + protected Item item1_2_2_b; + protected Item item2_1_a; + protected Item item2_1_b; + + @Before + public void setup() throws IOException, SQLException, AuthorizeException { + context.turnOffAuthorisationSystem(); + topComm1 = CommunityBuilder.createCommunity(context).withName("Parent Community1").build(); + topComm2 = CommunityBuilder.createCommunity(context).withName("Parent Community2").build(); + childComm1_1 = CommunityBuilder.createCommunity(context).withName("Child Community1_1") + .addParentCommunity(context, topComm1).build(); + childComm1_2 = CommunityBuilder.createCommunity(context).withName("Child Community1_2") + .addParentCommunity(context, topComm1).build(); + col1_1 = CollectionBuilder.createCollection(context, topComm1).withName("Collection 1_1").build(); + col1_2 = CollectionBuilder.createCollection(context, topComm1).withName("Collection 1_2").build(); + col1_1_1 = CollectionBuilder.createCollection(context, childComm1_1).withName("Collection 1_1_1").build(); + col1_1_2 = CollectionBuilder.createCollection(context, childComm1_1).withName("Collection 1_1_2").build(); + col1_2_1 = CollectionBuilder.createCollection(context, childComm1_2).withName("Collection 1_1_1").build(); + col1_2_2 = CollectionBuilder.createCollection(context, childComm1_2).withName("Collection 1_2").build(); + col2_1 = CollectionBuilder.createCollection(context, topComm2).withName("Collection 2_1").build(); + + // Create two items in each collection, one with the test.csv file and one with the test.txt file + item1_1_a = ItemBuilder.createItem(context, col1_1).withTitle("Item 1_1_a").withIssueDate("2017-10-17").build(); + item1_1_b = ItemBuilder.createItem(context, col1_1).withTitle("Item 1_1_b").withIssueDate("2017-10-17").build(); + item1_1_1_a = ItemBuilder.createItem(context, col1_1_1).withTitle("Item 1_1_1_a").withIssueDate("2017-10-17") + .build(); + item1_1_1_b = ItemBuilder.createItem(context, col1_1_1).withTitle("Item 1_1_1_b").withIssueDate("2017-10-17") + .build(); + item1_1_2_a = ItemBuilder.createItem(context, col1_1_2).withTitle("Item 1_1_2_a").withIssueDate("2017-10-17") + .build(); + item1_1_2_b = ItemBuilder.createItem(context, col1_1_2).withTitle("Item 1_1_2_b").withIssueDate("2017-10-17") + .build(); + item1_2_a = ItemBuilder.createItem(context, col1_2).withTitle("Item 1_2_a").withIssueDate("2017-10-17").build(); + item1_2_b = ItemBuilder.createItem(context, col1_2).withTitle("Item 1_2_b").withIssueDate("2017-10-17").build(); + item1_2_1_a = ItemBuilder.createItem(context, col1_2_1).withTitle("Item 1_2_1_a").withIssueDate("2017-10-17") + .build(); + item1_2_1_b = ItemBuilder.createItem(context, col1_2_1).withTitle("Item 1_2_1_b").withIssueDate("2017-10-17") + .build(); + item1_2_2_a = ItemBuilder.createItem(context, col1_2_2).withTitle("Item 1_2_2_a").withIssueDate("2017-10-17") + .build(); + item1_2_2_b = ItemBuilder.createItem(context, col1_2_2).withTitle("Item 1_2_2_b").withIssueDate("2017-10-17") + .build(); + item2_1_a = ItemBuilder.createItem(context, col2_1).withTitle("Item 2_1_a").withIssueDate("2017-10-17").build(); + item2_1_b = ItemBuilder.createItem(context, col2_1).withTitle("Item 2_1_b").withIssueDate("2017-10-17").build(); + addBitstream(item1_1_a, "test.csv"); + addBitstream(item1_1_b, "test.txt"); + addBitstream(item1_2_a, "test.csv"); + addBitstream(item1_2_b, "test.txt"); + addBitstream(item1_1_1_a, "test.csv"); + addBitstream(item1_1_1_b, "test.txt"); + addBitstream(item1_1_2_a, "test.csv"); + addBitstream(item1_1_2_b, "test.txt"); + addBitstream(item1_2_1_a, "test.csv"); + addBitstream(item1_2_1_b, "test.txt"); + addBitstream(item1_2_2_a, "test.csv"); + addBitstream(item1_2_2_b, "test.txt"); + addBitstream(item2_1_a, "test.csv"); + addBitstream(item2_1_b, "test.txt"); + context.restoreAuthSystemState(); + } + + private void addBitstream(Item item, String filename) throws SQLException, AuthorizeException, IOException { + BitstreamBuilder.createBitstream(context, item, getClass().getResourceAsStream(filename)).withName(filename) + .guessFormat().build(); + } + + @Test + public void mediaFilterScriptAllItemsTest() throws Exception { + performMediaFilterScript(null); + Iterator items = itemService.findAll(context); + while (items.hasNext()) { + Item item = items.next(); + checkItemHasBeenProcessed(item); + } + } + + @Test + public void mediaFilterScriptIdentifiersTest() throws Exception { + // process the item 1_1_a and verify that no other items has been processed using the "closer" one + performMediaFilterScript(item1_1_a); + checkItemHasBeenProcessed(item1_1_a); + checkItemHasBeenNotProcessed(item1_1_b); + // process the collection 1_1_1 and verify that items in another collection has not been processed + performMediaFilterScript(col1_1_1); + checkItemHasBeenProcessed(item1_1_1_a); + checkItemHasBeenProcessed(item1_1_1_b); + checkItemHasBeenNotProcessed(item1_1_2_a); + checkItemHasBeenNotProcessed(item1_1_2_b); + // process a top community with only collections + performMediaFilterScript(topComm2); + checkItemHasBeenProcessed(item2_1_a); + checkItemHasBeenProcessed(item2_1_b); + // verify that the other items have not been processed yet + checkItemHasBeenNotProcessed(item1_1_b); + checkItemHasBeenNotProcessed(item1_2_a); + checkItemHasBeenNotProcessed(item1_2_b); + checkItemHasBeenNotProcessed(item1_1_2_a); + checkItemHasBeenNotProcessed(item1_1_2_b); + checkItemHasBeenNotProcessed(item1_2_1_a); + checkItemHasBeenNotProcessed(item1_2_1_b); + checkItemHasBeenNotProcessed(item1_2_2_a); + checkItemHasBeenNotProcessed(item1_2_2_b); + // process a more structured community and verify that all the items at all levels are processed + performMediaFilterScript(topComm1); + // items that were already processed should stay processed + checkItemHasBeenProcessed(item1_1_a); + checkItemHasBeenProcessed(item1_1_1_a); + checkItemHasBeenProcessed(item1_1_1_b); + // residual items should have been processed as well now + checkItemHasBeenProcessed(item1_1_b); + checkItemHasBeenProcessed(item1_2_a); + checkItemHasBeenProcessed(item1_2_b); + checkItemHasBeenProcessed(item1_1_2_a); + checkItemHasBeenProcessed(item1_1_2_b); + checkItemHasBeenProcessed(item1_2_1_a); + checkItemHasBeenProcessed(item1_2_1_b); + checkItemHasBeenProcessed(item1_2_2_a); + checkItemHasBeenProcessed(item1_2_2_b); + } + + private void checkItemHasBeenNotProcessed(Item item) throws IOException, SQLException, AuthorizeException { + List textBundles = item.getBundles("TEXT"); + assertTrue("The item " + item.getName() + " should NOT have the TEXT bundle", textBundles.size() == 0); + } + + private void checkItemHasBeenProcessed(Item item) throws IOException, SQLException, AuthorizeException { + String expectedFileName = StringUtils.endsWith(item.getName(), "_a") ? "test.csv.txt" : "test.txt.txt"; + String expectedContent = StringUtils.endsWith(item.getName(), "_a") ? "data3,3" : "quick brown fox"; + List textBundles = item.getBundles("TEXT"); + assertTrue("The item " + item.getName() + " has NOT the TEXT bundle", textBundles.size() == 1); + 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 bitstream in the " + item.getName() + " is NOT named properly [" + expectedFileName + "]", + StringUtils.equals(bitstreams.get(0).getName(), expectedFileName)); + assertTrue("The text bitstream in the " + item.getName() + " doesn't contain the proper content [" + + expectedContent + "]", StringUtils.contains(getContent(bitstreams.get(0)), expectedContent)); + } + + private CharSequence getContent(Bitstream bitstream) throws IOException, SQLException, AuthorizeException { + try (InputStream input = bitstreamService.retrieve(context, bitstream)) { + return IOUtils.toString(input, "UTF-8"); + } + } + + private void performMediaFilterScript(DSpaceObject dso) throws Exception { + if (dso != null) { + runDSpaceScript("filter-media", "-i", dso.getHandle()); + } else { + runDSpaceScript("filter-media"); + } + // reload our items to see the changes + item1_1_a = context.reloadEntity(item1_1_a); + item1_1_b = context.reloadEntity(item1_1_b); + item1_2_a = context.reloadEntity(item1_2_a); + item1_2_b = context.reloadEntity(item1_2_b); + item1_1_1_a = context.reloadEntity(item1_1_1_a); + item1_1_1_b = context.reloadEntity(item1_1_1_b); + item1_1_2_a = context.reloadEntity(item1_1_2_a); + item1_1_2_b = context.reloadEntity(item1_1_2_b); + item1_2_1_a = context.reloadEntity(item1_2_1_a); + item1_2_1_b = context.reloadEntity(item1_2_1_b); + item1_2_2_a = context.reloadEntity(item1_2_2_a); + item1_2_2_b = context.reloadEntity(item1_2_2_b); + item2_1_a = context.reloadEntity(item2_1_a); + item2_1_b = context.reloadEntity(item2_1_b); + + } +} 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 7d808ab8715c..d9ee0fb9bf52 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.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 void packagerUUIDAlreadyExistWithoutForceTest() throws Exception { performExportScript(article.getHandle(), tempFile); UUID id = article.getID(); itemService.delete(context, article); - WorkspaceItem workspaceItem = workspaceItemService.create(context, col1, id, 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 void packagerUUIDAlreadyExistWithoutForceTest() throws Exception { } 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 96cf00c312ba..f6fbfe02f376 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 713e007c58a2..0868017ed944 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.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.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 static void setUpClass() { = 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 void testSendResponse() throws Exception { 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 void testSendRejection() 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 b03d7576f991..816756f9d556 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 static void setUpClass() } @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 f485a591b079..964b3fe303f9 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 static void setUpClass() } @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/sherpa/MockSHERPAService.java b/dspace-api/src/test/java/org/dspace/app/sherpa/MockSHERPAService.java index 239d2864bfb1..5c5e9f736436 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 SHERPAResponse performRequest(String type, String field, String predicate "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 SHERPAPublisherResponse performPublisherRequest(String type, String field "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 6b9666c83038..cbea55ea0787 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 void testSearchPublisherExternalObjects() { // 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/solrdatabaseresync/SolrDatabaseResyncIT.java b/dspace-api/src/test/java/org/dspace/app/solrdatabaseresync/SolrDatabaseResyncIT.java index 4fa881257e0f..d1fa0db9089b 100644 --- a/dspace-api/src/test/java/org/dspace/app/solrdatabaseresync/SolrDatabaseResyncIT.java +++ b/dspace-api/src/test/java/org/dspace/app/solrdatabaseresync/SolrDatabaseResyncIT.java @@ -35,6 +35,9 @@ import org.junit.Before; import org.junit.Test; +/** + * IT for {@link org.dspace.app.solrdatabaseresync.SolrDatabaseResyncIT} + */ public class SolrDatabaseResyncIT extends AbstractIntegrationTestWithDatabase { private final ConfigurationService configurationService = @@ -48,11 +51,21 @@ public class SolrDatabaseResyncIT extends AbstractIntegrationTestWithDatabase { private Collection col; private Item item1; private Item item2; + private Item item3; + private Item item4; + private Item item5; + private Item item6; + private Item item7; + private Item item8; + private Item item9; + private Item item10; + private Item item11; @Before public void setUp() throws Exception { super.setUp(); configurationService.setProperty("solr-database-resync.time-until-reindex", 1); + configurationService.setProperty("script.solr-database-resync.batch-size", 5); ServiceManager serviceManager = DSpaceServicesFactory.getInstance().getServiceManager(); searchService = serviceManager.getServiceByName(null, MockSolrSearchCore.class); @@ -75,6 +88,16 @@ public void setUp() throws Exception { .withSubject("TestingForMore") .build(); + item3 = ItemBuilder.createItem(context, col).withTitle("Public item 3").build(); + item4 = ItemBuilder.createItem(context, col).withTitle("Public item 4").build(); + item5 = ItemBuilder.createItem(context, col).withTitle("Public item 5").build(); + item6 = ItemBuilder.createItem(context, col).withTitle("Public item 6").build(); + item7 = ItemBuilder.createItem(context, col).withTitle("Public item 7").build(); + item8 = ItemBuilder.createItem(context, col).withTitle("Public item 8").build(); + item9 = ItemBuilder.createItem(context, col).withTitle("Public item 9").build(); + item10 = ItemBuilder.createItem(context, col).withTitle("Public item 10").build(); + item11 = ItemBuilder.createItem(context, col).withTitle("Public item 11").build(); + context.setDispatcher("noindex"); } @@ -83,12 +106,30 @@ public void solrPreDBStatusExistingItemTest() throws Exception { // Items were created, they should contain a predb status in solr assertHasPreDBStatus(item1); assertHasPreDBStatus(item2); + assertHasPreDBStatus(item3); + assertHasPreDBStatus(item4); + assertHasPreDBStatus(item5); + assertHasPreDBStatus(item6); + assertHasPreDBStatus(item7); + assertHasPreDBStatus(item8); + assertHasPreDBStatus(item9); + assertHasPreDBStatus(item10); + assertHasPreDBStatus(item11); performSolrDatabaseResyncScript(); // Database status script was performed, their predb status should be removed assertHasNoPreDBStatus(item1); assertHasNoPreDBStatus(item2); + assertHasNoPreDBStatus(item3); + assertHasNoPreDBStatus(item4); + assertHasNoPreDBStatus(item5); + assertHasNoPreDBStatus(item6); + assertHasNoPreDBStatus(item7); + assertHasNoPreDBStatus(item8); + assertHasNoPreDBStatus(item9); + assertHasNoPreDBStatus(item10); + assertHasNoPreDBStatus(item11); context.restoreAuthSystemState(); } @@ -98,22 +139,50 @@ public void solrPreDBStatusRemovedItemTest() throws Exception { // Items were created, they should contain a predb status in solr assertHasPreDBStatus(item1); assertHasPreDBStatus(item2); + assertHasPreDBStatus(item3); + assertHasPreDBStatus(item4); + assertHasPreDBStatus(item5); + assertHasPreDBStatus(item6); + assertHasPreDBStatus(item7); + assertHasPreDBStatus(item8); + assertHasPreDBStatus(item9); + assertHasPreDBStatus(item10); + assertHasPreDBStatus(item11); collectionService.delete(context, col); // Items were deleted, they should still contain a predb status in solr for now assertHasPreDBStatus(item1); assertHasPreDBStatus(item2); + assertHasPreDBStatus(item3); + assertHasPreDBStatus(item4); + assertHasPreDBStatus(item5); + assertHasPreDBStatus(item6); + assertHasPreDBStatus(item7); + assertHasPreDBStatus(item8); + assertHasPreDBStatus(item9); + assertHasPreDBStatus(item10); + assertHasPreDBStatus(item11); performSolrDatabaseResyncScript(); // Database status script was performed, their solr document should have been removed assertNoSolrDocument(item1); assertNoSolrDocument(item2); + assertNoSolrDocument(item3); + assertNoSolrDocument(item4); + assertNoSolrDocument(item5); + assertNoSolrDocument(item6); + assertNoSolrDocument(item7); + assertNoSolrDocument(item8); + assertNoSolrDocument(item9); + assertNoSolrDocument(item10); + assertNoSolrDocument(item11); context.restoreAuthSystemState(); } + public void assertHasNoPreDBStatus(Item item) throws Exception { assertNotEquals(STATUS_FIELD_PREDB, getStatus(item)); } 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 index dd9c0d8f5f76..98abfdb7d02e 100644 --- a/dspace-api/src/test/java/org/dspace/app/suggestion/SuggestionUtilsIT.java +++ b/dspace-api/src/test/java/org/dspace/app/suggestion/SuggestionUtilsIT.java @@ -24,9 +24,9 @@ import java.util.HashMap; import java.util.List; import java.util.Optional; -import javax.xml.bind.JAXBContext; -import javax.xml.bind.Unmarshaller; +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; 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 78142c925899..4058974d4136 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 void testAddingNewFormat() { "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 c2543ca17b8c..9baf0fe3e963 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 @@ -125,19 +125,17 @@ public void testGetPDFURLDifferentMimeTypes() throws Exception { 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 void testGetPDFURLSameMimeTypes() throws Exception { 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 void testGetPDFURLSameMimeTypesSameSize() throws Exception { 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 void testGetPDFURLWithPrimaryBitstream() throws Exception { 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 void testGetPDFURLWithUndefinedMimeTypes() throws Exception { 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(); @@ -328,18 +314,15 @@ public void testGetPDFURLWithEmptyBitstreams() throws Exception { 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(); @@ -361,8 +344,7 @@ public void testGetPdfUrlOfEmbargoed() throws Exception { 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 Period period = Period.ofMonths(3); 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 0db4926d2283..73d4434abf5f 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 @@ -51,6 +51,12 @@ public void testSubmissionMapByCommunityHandleSubmissionConfig() // 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(); + // 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(); @@ -69,5 +75,8 @@ public void testSubmissionMapByCommunityHandleSubmissionConfig() 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()); } } diff --git a/dspace-api/src/test/java/org/dspace/authority/AuthorityValueTest.java b/dspace-api/src/test/java/org/dspace/authority/AuthorityValueTest.java index 07c4b65f40f2..0857d07fde37 100644 --- a/dspace-api/src/test/java/org/dspace/authority/AuthorityValueTest.java +++ b/dspace-api/src/test/java/org/dspace/authority/AuthorityValueTest.java @@ -32,7 +32,7 @@ public void testStringToDate() { // Test an invalid date. actual = AuthorityValue.stringToDate("not a date"); - assertNull("Unparseable date should return null", actual); + 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) 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 013a18cd526a..93c23564cb7d 100644 --- a/dspace-api/src/test/java/org/dspace/builder/AbstractBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/AbstractBuilder.java @@ -14,6 +14,11 @@ 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; @@ -46,6 +51,8 @@ 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; @@ -109,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; @@ -116,8 +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; @@ -168,6 +181,7 @@ public static void init() { requestItemService = RequestItemServiceFactory.getInstance().getRequestItemService(); versioningService = DSpaceServicesFactory.getInstance().getServiceManager() .getServiceByName(VersioningService.class.getName(), VersioningService.class); + doiService = IdentifierServiceFactory.getInstance().getDOIService(); // Temporarily disabled claimedTaskService = XmlWorkflowServiceFactory.getInstance().getClaimedTaskService(); @@ -186,8 +200,12 @@ public static void init() { } 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(); } @@ -220,12 +238,17 @@ public static void destroy() { 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 e7ebd8768e7d..fa7306ad9955 100644 --- a/dspace-api/src/test/java/org/dspace/builder/AbstractDSpaceObjectBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/AbstractDSpaceObjectBuilder.java @@ -18,7 +18,6 @@ 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; @@ -103,7 +102,7 @@ protected > B setMetadataSingleValue(fi 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); } 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 08045325b8a5..987a367282ff 100644 --- a/dspace-api/src/test/java/org/dspace/builder/BitstreamBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/BitstreamBuilder.java @@ -21,6 +21,7 @@ import org.dspace.content.MetadataField; import org.dspace.content.MetadataValue; import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.content.service.BitstreamFormatService; import org.dspace.content.service.DSpaceObjectService; import org.dspace.content.service.MetadataValueService; import org.dspace.core.Constants; @@ -168,18 +169,25 @@ public BitstreamBuilder withMimeType(String mimeType) throws SQLException { return this; } - public BitstreamBuilder withFormat(String format) throws SQLException { - - bitstreamService.addMetadata(context, bitstream, "dc", "format", null, null, format); - + /** + * Guess the bitstream format as during the submission via the + * {@link BitstreamFormatService#guessFormat(Context, Bitstream)} + * + * @return the BitstreamBuilder with the format set according to + * {@link BitstreamFormatService#guessFormat(Context, Bitstream)} + * @throws SQLException + */ + public BitstreamBuilder guessFormat() throws SQLException { + bitstream.setFormat(context, bitstreamFormatService.guessFormat(context, bitstream)); return this; } - public BitstreamBuilder withProvenance(String provenance) throws SQLException { - - bitstreamService.addMetadata(context, bitstream, "dc", "description", "provenance", null, provenance); + public BitstreamBuilder withFormat(String format) throws SQLException { + return withMetadata("dc", "format", null, null, format); + } - return this; + public BitstreamBuilder withProvenance(String provenance) throws SQLException { + return withMetadata("dc", "description", "provenance", null, provenance); } @@ -189,22 +197,24 @@ public BitstreamBuilder withIIIFDisabled() throws SQLException { } 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; } diff --git a/dspace-api/src/test/java/org/dspace/builder/CollectionBuilder.java b/dspace-api/src/test/java/org/dspace/builder/CollectionBuilder.java index f287c7aa8d32..74d745c07c75 100644 --- a/dspace-api/src/test/java/org/dspace/builder/CollectionBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/CollectionBuilder.java @@ -253,8 +253,7 @@ public CollectionBuilder withAdminGroup(EPerson... members) throws SQLException, public CollectionBuilder withDefaultItemRead(Group group) throws SQLException, AuthorizeException { resourcePolicyService.removePolicies(context, collection, DEFAULT_ITEM_READ); - ResourcePolicy resourcePolicy = resourcePolicyService.create(context); - resourcePolicy.setGroup(group); + ResourcePolicy resourcePolicy = resourcePolicyService.create(context, null, group); resourcePolicy.setAction(DEFAULT_ITEM_READ); resourcePolicy.setdSpaceObject(collection); resourcePolicyService.update(context, resourcePolicy); 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 000000000000..cbfcd798c323 --- /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 b3447dd8bd9a..c16fb696b0c3 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.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 static GroupBuilder createGroup(final Context context) { 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 @@ private GroupBuilder create(final Context context) { 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/LDNMessageBuilder.java b/dspace-api/src/test/java/org/dspace/builder/LDNMessageBuilder.java new file mode 100644 index 000000000000..1ed369457365 --- /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 000000000000..44cf0be09215 --- /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 000000000000..5ae20b00016c --- /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/OrcidHistoryBuilder.java b/dspace-api/src/test/java/org/dspace/builder/OrcidHistoryBuilder.java index 199f412f8506..d811d03f5358 100644 --- a/dspace-api/src/test/java/org/dspace/builder/OrcidHistoryBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/OrcidHistoryBuilder.java @@ -11,7 +11,8 @@ import java.sql.SQLException; import java.util.Date; -import org.apache.log4j.Logger; +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.orcid.OrcidHistory; @@ -24,7 +25,7 @@ */ public class OrcidHistoryBuilder extends AbstractBuilder { - private static final Logger log = Logger.getLogger(OrcidHistoryBuilder.class); + private static final Logger log = LogManager.getLogger(OrcidHistoryBuilder.class); private OrcidHistory orcidHistory; 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 66e6245ff625..8481b17e1458 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 @@ 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/ResourcePolicyBuilder.java b/dspace-api/src/test/java/org/dspace/builder/ResourcePolicyBuilder.java index 70b1f8d73daf..c3b1b658ee1c 100644 --- a/dspace-api/src/test/java/org/dspace/builder/ResourcePolicyBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/ResourcePolicyBuilder.java @@ -110,31 +110,23 @@ public static void delete(Integer id) indexingService.commit(); } - public static ResourcePolicyBuilder createResourcePolicy(Context context) + public static ResourcePolicyBuilder createResourcePolicy(Context context, EPerson ePerson, + Group group) throws SQLException, AuthorizeException { ResourcePolicyBuilder resourcePolicyBuilder = new ResourcePolicyBuilder(context); - return resourcePolicyBuilder.create(context); + return resourcePolicyBuilder.create(context, ePerson, group); } - private ResourcePolicyBuilder create(Context context) + private ResourcePolicyBuilder create(Context context, final EPerson ePerson, + final Group epersonGroup) throws SQLException, AuthorizeException { this.context = context; - resourcePolicy = resourcePolicyService.create(context); + resourcePolicy = resourcePolicyService.create(context, ePerson, epersonGroup); return this; } - public ResourcePolicyBuilder withUser(EPerson ePerson) throws SQLException { - resourcePolicy.setEPerson(ePerson); - return this; - } - - public ResourcePolicyBuilder withGroup(Group epersonGroup) throws SQLException { - resourcePolicy.setGroup(epersonGroup); - return this; - } - public ResourcePolicyBuilder withAction(int action) throws SQLException { resourcePolicy.setAction(action); return this; 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 9d786d4761f0..67d8894338eb 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 @@ 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 @@ protected WorkspaceItemBuilder(Context context) { public static WorkspaceItemBuilder createWorkspaceItem(final Context context, final Collection col) { WorkspaceItemBuilder builder = new WorkspaceItemBuilder(context); - return builder.create(context, col); + return builder.create(context, col, null); } - private WorkspaceItemBuilder create(final Context context, final Collection col) { + public static WorkspaceItemBuilder createWorkspaceItem(final Context context, final Collection col, UUID uuid) { + WorkspaceItemBuilder builder = new WorkspaceItemBuilder(context); + return builder.create(context, col, uuid); + } + + /** + * Create with a specific UUID (e.g. restoring items with Packager import) + * + * @param context DSpace context + * @param col Parent collection + * @param uuid Item UUID + * @return WorkspaceItemBuilder + */ + private WorkspaceItemBuilder create(final Context context, final Collection col, UUID uuid) { this.context = context; try { - workspaceItem = workspaceItemService.create(context, col, false); + if (uuid == null) { + workspaceItem = workspaceItemService.create(context, col, false); + } else { + workspaceItem = workspaceItemService.create(context, col, uuid, false, false); + } item = workspaceItem.getItem(); } catch (Exception e) { return handleException(e); @@ -114,10 +134,12 @@ public void cleanup() throws Exception { delete(c, workspaceItem); } else { item = c.reloadEntity(item); - // check if the wsi has been pushed to the workflow - XmlWorkflowItem wfi = workflowItemService.findByItem(c, item); - if (wfi != null) { - workflowItemService.delete(c, wfi); + if (item != null) { + // check if the wsi has been pushed to the workflow + XmlWorkflowItem wfi = workflowItemService.findByItem(c, item); + if (wfi != null) { + workflowItemService.delete(c, wfi); + } } } item = c.reloadEntity(item); @@ -219,4 +241,20 @@ public WorkspaceItemBuilder withFulltext(String name, String source, InputStream } return this; } + + public WorkspaceItemBuilder withCOARNotifyService(NotifyServiceEntity notifyService, String pattern) { + Item item = workspaceItem.getItem(); + + try { + NotifyPatternToTrigger notifyPatternToTrigger = notifyPatternToTriggerService.create(context); + notifyPatternToTrigger.setItem(item); + notifyPatternToTrigger.setNotifyService(notifyService); + notifyPatternToTrigger.setPattern(pattern); + notifyPatternToTriggerService.update(context, notifyPatternToTrigger); + } catch (Exception e) { + handleException(e); + } + return this; + } + } diff --git a/dspace-api/src/test/java/org/dspace/checker/dao/impl/ChecksumHistoryDAOImplTest.java b/dspace-api/src/test/java/org/dspace/checker/dao/impl/ChecksumHistoryDAOImplTest.java index 860df5e887a5..03b274a67ade 100644 --- a/dspace-api/src/test/java/org/dspace/checker/dao/impl/ChecksumHistoryDAOImplTest.java +++ b/dspace-api/src/test/java/org/dspace/checker/dao/impl/ChecksumHistoryDAOImplTest.java @@ -15,8 +15,8 @@ import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; -import javax.persistence.Query; +import jakarta.persistence.Query; import org.dspace.AbstractUnitTest; import org.dspace.checker.ChecksumResultCode; import org.dspace.content.Bitstream; @@ -122,7 +122,7 @@ public void testDeleteByDateAndCode() // See if matching old row is gone. qry = dbc.getSession().createQuery( - "SELECT COUNT(*) FROM ChecksumHistory WHERE process_end_date = :date"); + "SELECT COUNT(*) FROM ChecksumHistory WHERE processEndDate = :date"); long count; qry.setParameter("date", matchDate); diff --git a/dspace-api/src/test/java/org/dspace/content/BitstreamFormatTest.java b/dspace-api/src/test/java/org/dspace/content/BitstreamFormatTest.java index ff99a820b5b7..1ddc8fb8ab55 100644 --- a/dspace-api/src/test/java/org/dspace/content/BitstreamFormatTest.java +++ b/dspace-api/src/test/java/org/dspace/content/BitstreamFormatTest.java @@ -35,7 +35,7 @@ import org.springframework.test.util.ReflectionTestUtils; /** - * This class tests BitstreamFormat. Due to it being tighly coupled with the + * This class tests BitstreamFormat. Due to it being tightly coupled with the * database, most of the methods use mock objects, which only proved a very * basic test level (ensure the method doesn't throw an exception). The real * testing of the class will be done in the Integration Tests. @@ -222,6 +222,7 @@ public void testCreateAdmin() throws SQLException, AuthorizeException { assertThat("testCreate 3", found.getSupportLevel(), equalTo(-1)); assertFalse("testCreate 4", found.isInternal()); bitstreamFormatService.delete(context, found); + context.commit(); } /** @@ -229,7 +230,7 @@ public void testCreateAdmin() throws SQLException, AuthorizeException { */ @Test(expected = AuthorizeException.class) public void testCreateNotAdmin() throws SQLException, AuthorizeException { - // Disalow full Admin perms + // Disallow full Admin perms when(authorizeServiceSpy.isAdmin(context)).thenReturn(false); bitstreamFormatService.create(context); @@ -497,6 +498,7 @@ public void testDeleteAdmin() throws SQLException, AuthorizeException { BitstreamFormat bitstreamFormat = bitstreamFormatService.create(context); int toDeleteIdentifier = bitstreamFormat.getID(); bitstreamFormatService.delete(context, bitstreamFormat); + context.commit(); BitstreamFormat b = bitstreamFormatService.find(context, toDeleteIdentifier); assertThat("testDeleteAdmin 0", b, nullValue()); } diff --git a/dspace-api/src/test/java/org/dspace/content/BundleTest.java b/dspace-api/src/test/java/org/dspace/content/BundleTest.java index 4af64b81cb0c..4b40043fc494 100644 --- a/dspace-api/src/test/java/org/dspace/content/BundleTest.java +++ b/dspace-api/src/test/java/org/dspace/content/BundleTest.java @@ -633,9 +633,9 @@ public void testInheritCollectionDefaultPolicies() throws AuthorizeException, SQ @Test public void testReplaceAllBitstreamPolicies() throws SQLException, AuthorizeException { List newpolicies = new ArrayList(); - newpolicies.add(resourcePolicyService.create(context)); - newpolicies.add(resourcePolicyService.create(context)); - newpolicies.add(resourcePolicyService.create(context)); + newpolicies.add(resourcePolicyService.create(context, eperson, null)); + newpolicies.add(resourcePolicyService.create(context, eperson, null)); + newpolicies.add(resourcePolicyService.create(context, eperson, null)); bundleService.replaceAllBitstreamPolicies(context, b, newpolicies); List bspolicies = bundleService.getBundlePolicies(context, b); diff --git a/dspace-api/src/test/java/org/dspace/content/CollectionTest.java b/dspace-api/src/test/java/org/dspace/content/CollectionTest.java index a177571ffa46..6e7290434566 100644 --- a/dspace-api/src/test/java/org/dspace/content/CollectionTest.java +++ b/dspace-api/src/test/java/org/dspace/content/CollectionTest.java @@ -435,7 +435,7 @@ public void testSetWorkflowGroup() throws SQLException, AuthorizeException { /** * Test of setWorkflowGroup method, of class Collection. - * The setWorkflowGroup ajust the policies for the basic Workflow. This test + * The setWorkflowGroup adjust the policies for the basic Workflow. This test * shall assure that now exception (e.g. ConcurrentModificationException is * thrown during these adjustments. */ diff --git a/dspace-api/src/test/java/org/dspace/content/CommunityTest.java b/dspace-api/src/test/java/org/dspace/content/CommunityTest.java index 939a7cc62088..7c0d1dc934f3 100644 --- a/dspace-api/src/test/java/org/dspace/content/CommunityTest.java +++ b/dspace-api/src/test/java/org/dspace/content/CommunityTest.java @@ -884,7 +884,7 @@ public void testDeleteHierachyAuth() throws Exception { context.turnOffAuthorisationSystem(); Community parent = communityService.create(null, context); - // 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, parent); Community grandchild = communityService.createSubcommunity(context, child); Collection childCol = collectionService.create(context, child); diff --git a/dspace-api/src/test/java/org/dspace/content/DCDateTest.java b/dspace-api/src/test/java/org/dspace/content/DCDateTest.java index 9f534b5dcf98..c3c84990ae4e 100644 --- a/dspace-api/src/test/java/org/dspace/content/DCDateTest.java +++ b/dspace-api/src/test/java/org/dspace/content/DCDateTest.java @@ -281,6 +281,23 @@ public void testDCDateString() { assertThat("testDCDateString 10", dc.getHourUTC(), equalTo(0)); assertThat("testDCDateString 11", dc.getMinuteUTC(), equalTo(0)); assertThat("testDCDateString 12", dc.getSecondUTC(), equalTo(1)); + + // test additional ISO format + dc = new DCDate("2010-04-14T00:00:01.000"); + assertThat("testDCDateString 1", dc.getYear(), equalTo(2010)); + assertThat("testDCDateString 2", dc.getMonth(), equalTo(04)); + assertThat("testDCDateString 3", dc.getDay(), equalTo(13)); + assertThat("testDCDateString 4", dc.getHour(), equalTo(16)); + assertThat("testDCDateString 5", dc.getMinute(), equalTo(0)); + assertThat("testDCDateIntBits 6", dc.getSecond(), equalTo(1)); + + assertThat("testDCDateString 7", dc.getYearUTC(), equalTo(2010)); + assertThat("testDCDateString 8", dc.getMonthUTC(), equalTo(04)); + assertThat("testDCDateString 9", dc.getDayUTC(), equalTo(14)); + assertThat("testDCDateString 10", dc.getHourUTC(), equalTo(0)); + assertThat("testDCDateString 11", dc.getMinuteUTC(), equalTo(0)); + assertThat("testDCDateString 12", dc.getSecondUTC(), equalTo(1)); + } @@ -381,6 +398,11 @@ public void testDisplayDate() { assertThat("testDisplayDate 7 ", dc.displayDate(false, false, new Locale("en_GB")), equalTo("14-Apr-2010")); + + dc = new DCDate("2010-04-14T00:00:01.000"); + assertThat("testDisplayDate 8 ", dc.displayDate(false, false, + new Locale("en_GB")), + equalTo("14-Apr-2010")); } /** diff --git a/dspace-api/src/test/java/org/dspace/content/DuplicateDetectionTest.java b/dspace-api/src/test/java/org/dspace/content/DuplicateDetectionTest.java new file mode 100644 index 000000000000..3ee25afe88c3 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/content/DuplicateDetectionTest.java @@ -0,0 +1,430 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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 junit.framework.TestCase.assertEquals; +import static junit.framework.TestCase.assertNull; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.fail; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Optional; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.dspace.AbstractIntegrationTestWithDatabase; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.builder.WorkflowItemBuilder; +import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.content.service.DuplicateDetectionService; +import org.dspace.content.virtual.PotentialDuplicate; +import org.dspace.discovery.SearchServiceException; +import org.dspace.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; +import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; +import org.junit.Before; +import org.junit.Test; + +/** + * + * Integration tests for the duplicate detection service + * + * @author Kim Shepherd + */ +public class DuplicateDetectionTest extends AbstractIntegrationTestWithDatabase { + private DuplicateDetectionService duplicateDetectionService = ContentServiceFactory.getInstance() + .getDuplicateDetectionService(); + private ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); + private Collection col; + private Collection workflowCol; + private Item item1; + private Item item2; + private Item item3; + private final String item1IssueDate = "2011-10-17"; + private final String item1Subject = "ExtraEntry 1"; + private final String item1Title = "Public item I"; + private final String item1Author = "Smith, Donald"; + + private static final Logger log = LogManager.getLogger(); + + @Before + public void setUp() throws Exception { + super.setUp(); + // Temporarily enable duplicate detection and set comparison distance to 1 + configurationService.setProperty("duplicate.enable", true); + configurationService.setProperty("duplicate.comparison.distance", 1); + configurationService.setProperty("duplicate.comparison.normalise.lowercase", true); + configurationService.setProperty("duplicate.comparison.normalise.whitespace", true); + configurationService.setProperty("duplicate.comparison.solr.field", "deduplication_keyword"); + configurationService.setProperty("duplicate.comparison.metadata.field", new String[]{"dc.title"}); + configurationService.setProperty("duplicate.preview.metadata.field", + new String[]{"dc.date.issued", "dc.subject"}); + + context.turnOffAuthorisationSystem(); + context.setDispatcher("default"); + + parentCommunity = CommunityBuilder.createCommunity(context).withName("Parent Community").build(); + col = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection").build(); + workflowCol = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Workflow Collection") + .withWorkflowGroup("reviewer", admin) + .build(); + + // Ingest three example items with slightly different titles + // item2 is 1 edit distance from item1 and item3 + // item1 and item3 are 2 edit distance from each other + item1 = ItemBuilder.createItem(context, col) + .withTitle(item1Title) // Public item I + .withIssueDate(item1IssueDate) + .withAuthor(item1Author) + .withSubject(item1Subject) + .build(); + item2 = ItemBuilder.createItem(context, col) + .withTitle("Public item II") + .withIssueDate("2012-10-17") + .withAuthor("Smith, Donald X.") + .withSubject("ExtraEntry 2") + .build(); + item3 = ItemBuilder.createItem(context, col) + .withTitle("Public item III") + .withIssueDate("2013-10-17") + .withAuthor("Smith, Donald Y.") + .withSubject("ExtraEntry 3") + .build(); + + + } + + /** + * Test instantiation of simple potential duplicate object + */ + @Test + public void testPotentialDuplicateInstantatation() { + PotentialDuplicate potentialDuplicate = new PotentialDuplicate(); + // The constructor should instantiate a new list for metadata + assertEquals("Metadata value list size should be 0", + 0, potentialDuplicate.getMetadataValueList().size()); + // Other properties should not be set + assertNull("Title should be null", potentialDuplicate.getTitle()); + //StringUtils.getLevenshteinDistance() + } + + /** + * Test instantiation of simple potential duplicate object given an item as a constructor argument + */ + @Test + public void testPotentialDuplicateInstantiationWithItem() { + PotentialDuplicate potentialDuplicate = new PotentialDuplicate(item1); + // We should have title, uuid, owning collection name set and metadata value list instantiated to empty + assertEquals("UUID should match item1 uuid", item1.getID(), potentialDuplicate.getUuid()); + assertEquals("Title should match item1 title", item1Title, potentialDuplicate.getTitle()); + assertEquals("Owning collection should match item1 owning collection", + item1.getOwningCollection().getName(), potentialDuplicate.getOwningCollectionName()); + assertEquals("Metadata value list size should be 0", + 0, potentialDuplicate.getMetadataValueList().size()); + } + + /** + * Test that a search for getPotentialDuplicates returns the expected results, populated with the expected + * preview values and metadata. This is the core method used by the duplicate item link repository and + * detect duplicates submission step. + * + * @throws Exception + */ + @Test + public void testSearchDuplicates() throws Exception { + + // Get potential duplicates of item 1: + // Expected: Public item II should appear as it has the configured levenshtein distance of 1 + List 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 305ebff69710..11fbbb9f9119 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 void testGetAllRelationshipTypes() throws Exception { // 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 void testGetAllRelationshipTypes() throws Exception { // 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 void testGetLeftRelationshipTypes() throws Exception { // 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 void testGetRightRelationshipTypes() throws Exception { // 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 c54f0fc955b2..113721910379 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 void testDelete() throws Exception { } /** - * 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 03d0391ab8b3..668944fc977b 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 void testCommunityAdminDeletions() throws SQLException, AuthorizeExceptio 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 54ff9ce02624..dc78008f3e41 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 void testCompare() throws SQLException { 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 void testCompare() throws SQLException { //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 d440597ec416..00dbf2994d98 100644 --- a/dspace-api/src/test/java/org/dspace/content/ItemTest.java +++ b/dspace-api/src/test/java/org/dspace/content/ItemTest.java @@ -13,6 +13,8 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +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 static org.mockito.ArgumentMatchers.any; @@ -516,11 +518,11 @@ public void testAddMetadata_5args_1() throws SQLException { 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(), @@ -537,6 +539,17 @@ public void testAddMetadata_5args_1() throws SQLException { assertThat("testAddMetadata_5args_1 11", dc.get(1).getValue(), equalTo(values[1])); } + @Test(expected = IllegalArgumentException.class) + public void testAddMetadata_5args_no_values() throws Exception { + String schema = "dc"; + String element = "contributor"; + String qualifier = "author"; + String lang = null; + String[] values = {}; + itemService.addMetadata(context, it, schema, element, qualifier, lang, Arrays.asList(values)); + fail("IllegalArgumentException expected"); + } + /** * Test of addMetadata method, of class Item. */ @@ -550,13 +563,13 @@ public void testAddMetadata_7args_1_authority() 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(), @@ -587,13 +600,13 @@ public void testAddMetadata_7args_1_noauthority() throws SQLException { 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(), @@ -614,33 +627,85 @@ public void testAddMetadata_7args_1_noauthority() throws SQLException { assertThat("testAddMetadata_7args_1 15", dc.get(1).getConfidence(), equalTo(-1)); } - /** - * Test of addMetadata method, of class Item. + @Test(expected = IllegalArgumentException.class) + public void testAddMetadata_7args_no_values() throws Exception { + String schema = "dc"; + String element = "contributor"; + String qualifier = "author"; + String lang = null; + List values = new ArrayList(); + List authorities = new ArrayList(); + List confidences = new ArrayList(); + itemService.addMetadata(context, it, schema, element, qualifier, lang, values, authorities, confidences); + fail("IllegalArgumentException expected"); + } + + @Test + public void testAddMetadata_list_with_virtual_metadata() throws Exception { + String schema = "dc"; + String element = "contributor"; + String qualifier = "author"; + 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", + Constants.VIRTUAL_AUTHORITY_PREFIX + "relationship-2")); + List confidences = new ArrayList<>(Arrays.asList(-1, -1)); + + // Virtual metadata values will be IGNORED. No metadata should be added as we are calling addMetadata() + // with two virtual metadata values. + List valuesAdded = itemService.addMetadata(context, it, schema, element, qualifier, lang, + values, authorities, confidences); + assertNotNull(valuesAdded); + assertTrue(valuesAdded.isEmpty()); + + // Now, update tests values to append a third value which is NOT virtual metadata + String newValue = "new-metadata-value"; + String newAuthority = "auth0"; + Integer newConfidence = 0; + values.add(newValue); + authorities.add(newAuthority); + confidences.add(newConfidence); + + // Call addMetadata again, and this time only one value (the new, non-virtual metadata) should be added + valuesAdded = itemService.addMetadata(context, it, schema, element, qualifier, lang, + values, authorities, confidences); + assertNotNull(valuesAdded); + 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, Item.ANY); + assertNotNull(dc); + assertEquals(1, dc.size()); + assertEquals(schema, dc.get(0).getMetadataField().getMetadataSchema().getName()); + assertEquals(element, dc.get(0).getMetadataField().getElement()); + assertEquals(qualifier, dc.get(0).getMetadataField().getQualifier()); + assertEquals(newValue, dc.get(0).getValue()); + assertNull(dc.get(0).getAuthority()); + assertEquals(-1, dc.get(0).getConfidence()); + } + + /** + * This is the same as testAddMetadata_5args_1 except it is adding a *single* value as a String, not a List. */ @Test public void testAddMetadata_5args_2() throws SQLException { String schema = "dc"; String element = "contributor"; String qualifier = "author"; - String lang = Item.ANY; - List values = Arrays.asList("value0", "value1"); - itemService.addMetadata(context, it, schema, element, qualifier, lang, values); + 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() == 2); + assertTrue("testAddMetadata_5args_2 1", dc.size() == 1); assertThat("testAddMetadata_5args_2 2", dc.get(0).getMetadataField().getMetadataSchema().getName(), equalTo(schema)); assertThat("testAddMetadata_5args_2 3", dc.get(0).getMetadataField().getElement(), equalTo(element)); assertThat("testAddMetadata_5args_2 4", dc.get(0).getMetadataField().getQualifier(), equalTo(qualifier)); assertThat("testAddMetadata_5args_2 5", dc.get(0).getLanguage(), equalTo(lang)); - assertThat("testAddMetadata_5args_2 6", dc.get(0).getValue(), equalTo(values.get(0))); - assertThat("testAddMetadata_5args_2 7", dc.get(1).getMetadataField().getMetadataSchema().getName(), - equalTo(schema)); - assertThat("testAddMetadata_5args_2 8", dc.get(1).getMetadataField().getElement(), equalTo(element)); - assertThat("testAddMetadata_5args_2 9", dc.get(1).getMetadataField().getQualifier(), equalTo(qualifier)); - assertThat("testAddMetadata_5args_2 10", dc.get(1).getLanguage(), equalTo(lang)); - assertThat("testAddMetadata_5args_2 11", dc.get(1).getValue(), equalTo(values.get(1))); + assertThat("testAddMetadata_5args_2 6", dc.get(0).getValue(), equalTo(value)); } /** @@ -654,13 +719,13 @@ public void testAddMetadata_7args_2_authority() throws SQLException { 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(), @@ -683,13 +748,13 @@ public void testAddMetadata_7args_2_noauthority() throws SQLException { 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(), @@ -702,6 +767,42 @@ public void testAddMetadata_7args_2_noauthority() throws SQLException { assertThat("testAddMetadata_7args_2 8", dc.get(0).getConfidence(), equalTo(-1)); } + @Test + public void testAddMetadata_single_virtual_metadata() throws Exception { + String schema = "dc"; + String element = "contributor"; + String qualifier = "author"; + 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"; + Integer confidence = -1; + + // Virtual metadata values will be IGNORED. No metadata should be added as we are calling addMetadata() + // with a virtual metadata value. + MetadataValue valuesAdded = itemService.addMetadata(context, it, schema, element, qualifier, lang, + value, authority, confidence); + // Returned object will be null when no metadata was added + assertNull(valuesAdded); + + // Verify this metadata field does NOT exist on the item + List mv = itemService.getMetadata(it, schema, element, qualifier, Item.ANY); + assertNotNull(mv); + assertTrue(mv.isEmpty()); + + // Also try calling addMetadata() with MetadataField object + MetadataField metadataField = metadataFieldService.findByElement(context, schema, element, qualifier); + valuesAdded = itemService.addMetadata(context, it, metadataField, lang, value, authority, confidence); + // Returned object should still be null + assertNull(valuesAdded); + + // Verify this metadata field does NOT exist on the item + mv = itemService.getMetadata(it, schema, element, qualifier, Item.ANY); + assertNotNull(mv); + assertTrue(mv.isEmpty()); + } + + /** * Test of clearMetadata method, of class Item. */ @@ -710,13 +811,13 @@ public void testClearMetadata() throws SQLException { 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); } @@ -758,11 +859,11 @@ public void testGetCollections() throws Exception { 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()); @@ -1257,7 +1358,7 @@ public void testGetType() { @Test public void testReplaceAllItemPolicies() throws Exception { List newpolicies = new ArrayList(); - ResourcePolicy pol1 = resourcePolicyService.create(context); + ResourcePolicy pol1 = resourcePolicyService.create(context, eperson, null); newpolicies.add(pol1); itemService.replaceAllItemPolicies(context, it, newpolicies); @@ -1284,9 +1385,9 @@ public void testReplaceAllBitstreamPolicies() throws Exception { bundleService.addBitstream(context, created, result); List newpolicies = new ArrayList(); - newpolicies.add(resourcePolicyService.create(context)); - newpolicies.add(resourcePolicyService.create(context)); - newpolicies.add(resourcePolicyService.create(context)); + newpolicies.add(resourcePolicyService.create(context, eperson, null)); + newpolicies.add(resourcePolicyService.create(context, eperson, null)); + newpolicies.add(resourcePolicyService.create(context, eperson, null)); context.restoreAuthSystemState(); itemService.replaceAllBitstreamPolicies(context, it, newpolicies); @@ -1316,9 +1417,8 @@ public void testRemoveGroupPolicies() throws Exception { context.turnOffAuthorisationSystem(); List newpolicies = new ArrayList(); Group g = groupService.create(context); - ResourcePolicy pol1 = resourcePolicyService.create(context); + ResourcePolicy pol1 = resourcePolicyService.create(context, null, g); newpolicies.add(pol1); - pol1.setGroup(g); itemService.replaceAllItemPolicies(context, it, newpolicies); itemService.removeGroupPolicies(context, it, g); @@ -1672,7 +1772,7 @@ public void testFindByMetadataField() throws Exception { // 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(); @@ -1737,7 +1837,7 @@ public void testFindByAuthorityValue() throws Exception { // 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 3e36f77c68b9..b33063a1fab7 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 void AddAndRemoveMetadataAndRelationshipsTest() throws Exception { 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 44653300e0de..3acc4ca146ee 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.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.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 void test_createNewVersionOfItemOnLeftSideOfRelationships() throws Except // 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 void test_createNewVersionOfItemAndModifyRelationships() throws Exception // 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 void test_createNewVersionOfItemOnRightSideOfRelationships() throws Excep // 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 void test_createNewVersionOfItemAndVerifyMetadataOrder() throws Exception // 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 void test_createNewVersionOfItemWithAddRemoveMove() throws Exception { // 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 void test_placeRecalculationAfterDelete() throws Exception { // 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 void test_placeRecalculationAfterDelete() throws Exception { // 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 void test_placeRecalculationAfterDelete_complex() throws Exception { // 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 void test_placeRecalculationAfterDelete_complex() throws Exception { // 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 void test_placeRecalculationNoUseForPlace() throws Exception { // 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 void test_placeRecalculationNoUseForPlace() throws Exception { // 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 void test_virtualMetadataPreserved() throws Exception { // 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 void test_virtualMetadataPreserved() throws Exception { // 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 void test_virtualMetadataPreserved() throws Exception { // 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 2d08223b2e3e..8e48c7088463 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 void init() { 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 void init() { } /** - * 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 ff7d03b49f6d..315fabba9e88 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 void init() { 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 void init() { } /** - * 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 a634b98130a6..b9cbc36f7111 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 static void setUpClass() { 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" @@ -386,9 +386,8 @@ public void testRestoreRestrictedCommunity() throws Exception { // Create a custom resource policy for this community List policies = new ArrayList<>(); - ResourcePolicy policy = resourcePolicyService.create(context); + ResourcePolicy policy = resourcePolicyService.create(context, null, group); policy.setRpName("Special Read Only"); - policy.setGroup(group); policy.setAction(Constants.READ); policies.add(policy); @@ -600,9 +599,8 @@ public void testRestoreRestrictedCollection() throws Exception { // Create a custom resource policy for this Collection List policies = new ArrayList<>(); - ResourcePolicy policy = resourcePolicyService.create(context); + ResourcePolicy policy = resourcePolicyService.create(context, null, group); policy.setRpName("Special Read Only"); - policy.setGroup(group); policy.setAction(Constants.READ); policies.add(policy); @@ -822,10 +820,9 @@ public void testRestoreRestrictedItem() throws Exception { // Create a custom resource policy for this Item List policies = new ArrayList<>(); - ResourcePolicy admin_policy = resourcePolicyService.create(context); - admin_policy.setRpName("Admin Read-Only"); Group adminGroup = groupService.findByName(context, Group.ADMIN); - admin_policy.setGroup(adminGroup); + ResourcePolicy admin_policy = resourcePolicyService.create(context, null, adminGroup); + admin_policy.setRpName("Admin Read-Only"); admin_policy.setAction(Constants.READ); policies.add(admin_policy); itemService.replaceAllItemPolicies(context, item, policies); 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 ae6860012457..06020be51349 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 static void setUpClass() { 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 25eb0361592e..eee445b3334f 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.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.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.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 void setUp() throws Exception { 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 void preserveMetadataOrder() throws Exception { // 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 void preserveMetadataOrder() throws Exception { // 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 void preserveMetadataOrder() throws Exception { 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 void preserveMetadataOrder() throws Exception { // 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 void preserveMetadataOrder() throws Exception { // 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 void preserveMetadataOrder() throws Exception { // 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)); @@ -675,8 +692,7 @@ public void testFindItemsWithEditNoRights() throws Exception { @Test public void testFindAndCountItemsWithEditEPerson() throws Exception { - ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context) - .withUser(eperson) + ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context, eperson, null) .withDspaceObject(item) .withAction(Constants.WRITE) .build(); @@ -689,8 +705,7 @@ public void testFindAndCountItemsWithEditEPerson() throws Exception { @Test public void testFindAndCountItemsWithAdminEPerson() throws Exception { - ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context) - .withUser(eperson) + ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context, eperson, null) .withDspaceObject(item) .withAction(Constants.ADMIN) .build(); @@ -709,8 +724,7 @@ public void testFindAndCountItemsWithEditGroup() throws Exception { .build(); context.restoreAuthSystemState(); - ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context) - .withGroup(group) + ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context, null, group) .withDspaceObject(item) .withAction(Constants.WRITE) .build(); @@ -729,8 +743,7 @@ public void testFindAndCountItemsWithAdminGroup() throws Exception { .build(); context.restoreAuthSystemState(); - ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context) - .withGroup(group) + ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context, null, group) .withDspaceObject(item) .withAction(Constants.ADMIN) .build(); @@ -916,4 +929,65 @@ private void assertMetadataValue(String authorQualifier, String contributorEleme 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 52457a23d77d..f39f2d8fd998 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 void testSetFields() { @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 void testSetSeperator() { // 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 void testSetUseForPlace() { // 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 2a07799deee5..8038a7153325 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 @@ 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.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 @@ */ 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 void testPerform() /* * 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 void destroy() throws Exception { 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 489161bf8de7..0f57e187315e 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.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.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 @@ */ 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 55be531418ae..6bc79cad558b 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.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 f2a759fa091e..ff4174d048be 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 void testBitstreamThrowingExceptionShouldNotStopIndexing() throws Excepti 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 0aa549e8c829..a9cc213ad9a3 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 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 void testDeleteUserWhenOnlyUserInGroup4() throws Exception { * 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 void testDeleteUserWhenOnlyUserInGroup6() throws Exception { * 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 3780afcf6393..4439e39e70b1 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.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 index c67402dfdcc6..9e1362e2311f 100644 --- a/dspace-api/src/test/java/org/dspace/external/MockOpenaireRestConnector.java +++ b/dspace-api/src/test/java/org/dspace/external/MockOpenaireRestConnector.java @@ -8,7 +8,6 @@ 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; @@ -30,7 +29,7 @@ public MockOpenaireRestConnector(String url) { 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 Response searchProjectByKeywords(int page, int size, String... keywords) 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 Response searchProjectByIDAndFunder(String projectID, String projectFunde 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/OrcidPublicationDataProviderIT.java b/dspace-api/src/test/java/org/dspace/external/provider/impl/OrcidPublicationDataProviderIT.java index dae14115b8e0..671fe9467961 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.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 09387acd3ee3..734713b92c02 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 void testMint_DOI_withNonMatchingFilter() /** * 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 void testReserve_DOI() 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 void testRegister_unreserved_DOI() 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 void testRegister_reserved_DOI() 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 void testCreate_and_Register_DOI() 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 7e549f6cae33..57acf1f1c453 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.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 void setUp() throws Exception { .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/matcher/NotifyServiceEntityMatcher.java b/dspace-api/src/test/java/org/dspace/matcher/NotifyServiceEntityMatcher.java new file mode 100644 index 000000000000..7b20a1c8be94 --- /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 index 52f3704a74b7..61affd0ec87d 100644 --- a/dspace-api/src/test/java/org/dspace/matcher/QAEventMatcher.java +++ b/dspace-api/src/test/java/org/dspace/matcher/QAEventMatcher.java @@ -74,7 +74,7 @@ private QAEventMatcher(Matcher eventIdMatcher, Matcher originalI * @param message the message to match * @param topic the topic to match * @param trust the trust to match - * @return the matcher istance + * @return the matcher instance */ public static QAEventMatcher pendingOpenaireEventWith(String originalId, Item target, String title, String message, String topic, Double trust) { 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 17bc6ee531c3..912efcfcf323 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 @@ private Predicate externalId(String type, String value, Relationship 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/process/ProcessIT.java b/dspace-api/src/test/java/org/dspace/process/ProcessIT.java index d6640652121c..6cf840ea6abe 100644 --- a/dspace-api/src/test/java/org/dspace/process/ProcessIT.java +++ b/dspace-api/src/test/java/org/dspace/process/ProcessIT.java @@ -9,6 +9,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import java.util.HashSet; import java.util.LinkedList; @@ -87,4 +88,14 @@ public void removeOneGroupTest() throws Exception { assertFalse(isPresent); } + + @Test + public void addProcessWithNullEPersonTest() throws Exception { + try { + ProcessBuilder.createProcess(context, null, "mock-script", new LinkedList<>(), + new HashSet<>()).build(); + } catch (NullPointerException e) { + fail("Should not have thrown NullPointerException"); + } + } } 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 index 6bb979f48be8..b63c85eeb66e 100644 --- a/dspace-api/src/test/java/org/dspace/qaevent/script/OpenaireEventsImportIT.java +++ b/dspace-api/src/test/java/org/dspace/qaevent/script/OpenaireEventsImportIT.java @@ -8,13 +8,16 @@ 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; @@ -32,6 +35,7 @@ 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; @@ -43,14 +47,19 @@ 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; /** @@ -61,9 +70,8 @@ */ public class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase { - private static final String BASE_JSON_DIR_PATH = "org/dspace/app/openaire-events/"; - 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); @@ -73,11 +81,17 @@ public class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase 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(); @@ -87,7 +101,7 @@ public void setup() { .build(); context.restoreAuthSystemState(); - + configurationService.setProperty(QAEVENTS_SOURCES, new String[] { QAEvent.OPENAIRE_SOURCE }); ((OpenaireClientFactoryImpl) OpenaireClientFactory.getInstance()).setBrokerClient(mockBrokerClient); } @@ -136,7 +150,6 @@ public void testWithBothFileAndEmailParameters() throws Exception { } @Test - @SuppressWarnings("unchecked") public void testManyEventsImportFromFile() throws Exception { context.turnOffAuthorisationSystem(); @@ -157,14 +170,17 @@ public void testManyEventsImportFromFile() throws Exception { "Trying to read the QA events from the provided file", "Found 5 events in the given file")); - assertThat(qaEventService.findAllSources(0, 20), contains(QASourceMatcher.with(OPENAIRE_SOURCE, 5L))); + assertThat(qaEventService.findAllSources(context, 0, 20), + hasItem(QASourceMatcher.with(OPENAIRE_SOURCE, 5L)) + ); - assertThat(qaEventService.findAllTopics(0, 20, ORDER_FIELD, false), containsInAnyOrder( - QATopicMatcher.with("ENRICH/MORE/PROJECT", 1L), - QATopicMatcher.with("ENRICH/MORE/PID", 1L), - QATopicMatcher.with("ENRICH/MISSING/PID", 1L), - QATopicMatcher.with("ENRICH/MISSING/PROJECT", 1L), - QATopicMatcher.with("ENRICH/MISSING/ABSTRACT", 1L))); + 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\"," @@ -172,19 +188,23 @@ public void testManyEventsImportFromFile() throws Exception { + "\"projects[0].openaireId\":\"40|corda__h2020::6e32f5eb912688f2424c68b851483ea4\"," + "\"projects[0].title\":\"Tracking Papyrus and Parchment Paths\"}"; - assertThat(qaEventService.findEventsByTopic("ENRICH/MORE/PROJECT"), contains( + 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, - "ENRICH/MORE/PROJECT", 1.00d))); + QANotifyPatterns.TOPIC_ENRICH_MORE_PROJECT, 1.00d))); String abstractMessage = "{\"abstracts[0]\":\"Missing Abstract\"}"; - assertThat(qaEventService.findEventsByTopic("ENRICH/MISSING/ABSTRACT"), contains( - pendingOpenaireEventWith("oai:www.openstarts.units.it:123456789/99999", secondItem, "Test Publication", - abstractMessage, "ENRICH/MISSING/ABSTRACT", 1.00d))); + 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 @@ -203,32 +223,33 @@ public void testManyEventsImportFromFileWithUnknownHandle() throws Exception { assertThat(handler.getErrorMessages(), empty()); assertThat(handler.getWarningMessages(), - contains("An error occurs storing the event with id b4e09c71312cd7c397969f56c900823f: " - + "Skipped event b4e09c71312cd7c397969f56c900823f 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 d050d2c4399c6c6ccf27d52d479d26e4: " - + "Skipped event d050d2c4399c6c6ccf27d52d479d26e4 related to the oai record " - + "oai:www.openstarts.units.it:123456789/99998 as the record was not found")); + 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(0, 20), contains(QASourceMatcher.with(OPENAIRE_SOURCE, 3L))); + assertThat(qaEventService.findAllSources(context, 0, 20), hasItem(QASourceMatcher.with(OPENAIRE_SOURCE, 3L))); - assertThat(qaEventService.findAllTopics(0, 20, ORDER_FIELD, false), containsInAnyOrder( - QATopicMatcher.with("ENRICH/MISSING/ABSTRACT", 1L), - QATopicMatcher.with("ENRICH/MISSING/PROJECT", 1L), - QATopicMatcher.with("ENRICH/MORE/PID", 1L) - )); + 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("ENRICH/MISSING/ABSTRACT"), contains( + 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, "ENRICH/MISSING/ABSTRACT", 1.00d))); + abstractMessage, QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 1.00d))); verifyNoInteractions(mockBrokerClient); - } @Test @@ -248,24 +269,26 @@ public void testManyEventsImportFromFileWithUnknownTopic() throws Exception { assertThat(handler.getErrorMessages(), empty()); assertThat(handler.getWarningMessages(), - contains("Event for topic ENRICH/MORE/UNKNOWN is not allowed in the qaevents.cfg")); + 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(0, 20), contains(QASourceMatcher.with(OPENAIRE_SOURCE, 1L))); + assertThat(qaEventService.findAllSources(context, 0, 20), hasItem(QASourceMatcher.with(OPENAIRE_SOURCE, 1L))); - assertThat(qaEventService.findAllTopics(0, 20, ORDER_FIELD, false), - contains(QATopicMatcher.with("ENRICH/MISSING/ABSTRACT", 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("ENRICH/MISSING/ABSTRACT"), contains( + 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, "ENRICH/MISSING/ABSTRACT", 1.00d))); + abstractMessage, org.dspace.qaevent.QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 1.00d))); verifyNoInteractions(mockBrokerClient); - } @Test @@ -281,15 +304,14 @@ public void testImportFromFileWithoutEvents() throws Exception { assertThat(handler.getWarningMessages(),empty()); assertThat(handler.getInfoMessages(), contains("Trying to read the QA events from the provided file")); - assertThat(qaEventService.findAllSources(0, 20), contains(QASourceMatcher.with(OPENAIRE_SOURCE, 0L))); + assertThat(qaEventService.findAllSources(context, 0, 20), hasItem(QASourceMatcher.with(OPENAIRE_SOURCE, 0L))); - assertThat(qaEventService.findAllTopics(0, 20, ORDER_FIELD, false), empty()); + assertThat(qaEventService.findAllTopics(context, 0, 20, ORDER_FIELD, false), empty()); verifyNoInteractions(mockBrokerClient); } @Test - @SuppressWarnings("unchecked") public void testImportFromOpenaireBroker() throws Exception { context.turnOffAuthorisationSystem(); @@ -328,14 +350,14 @@ public void testImportFromOpenaireBroker() throws Exception { "Found 0 events from the subscription sub2", "Found 2 events from the subscription sub3")); - assertThat(qaEventService.findAllSources(0, 20), contains(QASourceMatcher.with(OPENAIRE_SOURCE, 6L))); + assertThat(qaEventService.findAllSources(context, 0, 20), hasItem(QASourceMatcher.with(OPENAIRE_SOURCE, 6L))); - assertThat(qaEventService.findAllTopics(0, 20, ORDER_FIELD, false), containsInAnyOrder( - QATopicMatcher.with("ENRICH/MORE/PROJECT", 1L), - QATopicMatcher.with("ENRICH/MORE/PID", 1L), - QATopicMatcher.with("ENRICH/MISSING/PID", 1L), - QATopicMatcher.with("ENRICH/MISSING/PROJECT", 1L), - QATopicMatcher.with("ENRICH/MISSING/ABSTRACT", 2L))); + 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\"," @@ -343,18 +365,22 @@ public void testImportFromOpenaireBroker() throws Exception { + "\"projects[0].openaireId\":\"40|corda__h2020::6e32f5eb912688f2424c68b851483ea4\"," + "\"projects[0].title\":\"Tracking Papyrus and Parchment Paths\"}"; - assertThat(qaEventService.findEventsByTopic("ENRICH/MORE/PROJECT"), contains( + 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, - "ENRICH/MORE/PROJECT", 1.00d))); + QANotifyPatterns.TOPIC_ENRICH_MORE_PROJECT, 1.00d))); String abstractMessage = "{\"abstracts[0]\":\"Missing Abstract\"}"; - assertThat(qaEventService.findEventsByTopic("ENRICH/MISSING/ABSTRACT"), containsInAnyOrder( + 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, "ENRICH/MISSING/ABSTRACT", 1.00d), + abstractMessage, QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 1.00d), pendingOpenaireEventWith("oai:www.openstarts.units.it:123456789/999991", thirdItem, "Test Publication 2", - abstractMessage, "ENRICH/MISSING/ABSTRACT", 1.00d))); + abstractMessage, QANotifyPatterns.TOPIC_ENRICH_MISSING_ABSTRACT, 1.00d))); verify(mockBrokerClient).listSubscriptions(openaireURL, "user@test.com"); verify(mockBrokerClient).downloadEvents(eq(openaireURL), eq("sub1"), any()); @@ -382,9 +408,9 @@ public void testImportFromOpenaireBrokerWithErrorDuringListSubscription() throws assertThat(handler.getWarningMessages(), empty()); assertThat(handler.getInfoMessages(), contains("Trying to read the QA events from the OPENAIRE broker")); - assertThat(qaEventService.findAllSources(0, 20), contains(QASourceMatcher.with(OPENAIRE_SOURCE, 0L))); + assertThat(qaEventService.findAllSources(context, 0, 20), hasItem(QASourceMatcher.with(OPENAIRE_SOURCE, 0L))); - assertThat(qaEventService.findAllTopics(0, 20, ORDER_FIELD, false), empty()); + assertThat(qaEventService.findAllTopics(context, 0, 20, ORDER_FIELD, false), empty()); verify(mockBrokerClient).listSubscriptions(openaireURL, "user@test.com"); @@ -393,7 +419,6 @@ public void testImportFromOpenaireBrokerWithErrorDuringListSubscription() throws } @Test - @SuppressWarnings("unchecked") public void testImportFromOpenaireBrokerWithErrorDuringEventsDownload() throws Exception { context.turnOffAuthorisationSystem(); @@ -433,17 +458,20 @@ public void testImportFromOpenaireBrokerWithErrorDuringEventsDownload() throws E "Found 0 events from the subscription sub2", "Found 2 events from the subscription sub3")); - assertThat(qaEventService.findAllSources(0, 20), contains(QASourceMatcher.with(OPENAIRE_SOURCE, 6L))); + assertThat(qaEventService.findAllSources(context, 0, 20), hasItem(QASourceMatcher.with(OPENAIRE_SOURCE, 6L))); - assertThat(qaEventService.findAllTopics(0, 20, ORDER_FIELD, false), containsInAnyOrder( - QATopicMatcher.with("ENRICH/MORE/PROJECT", 1L), - QATopicMatcher.with("ENRICH/MISSING/PID", 1L), - QATopicMatcher.with("ENRICH/MORE/PID", 1L), - QATopicMatcher.with("ENRICH/MISSING/PROJECT", 1L), - QATopicMatcher.with("ENRICH/MISSING/ABSTRACT", 2L))); + 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("ENRICH/MORE/PROJECT"), hasSize(1)); - assertThat(qaEventService.findEventsByTopic("ENRICH/MISSING/ABSTRACT"), hasSize(2)); + 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()); @@ -451,7 +479,35 @@ public void testImportFromOpenaireBrokerWithErrorDuringEventsDownload() throws E 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) { @@ -486,3 +542,4 @@ private String getFileLocation(String fileName) throws Exception { 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 aed0c088c362..094275743b34 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 SolrClient getSolrServer() { /** * 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 @@ private static synchronized SolrClient initSolrServerForCore(final String coreNa 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 @@ private static synchronized SolrClient initSolrServerForCore(final String coreNa * 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 void destroy() throws Exception { 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 000000000000..073734e87515 --- /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 000000000000..82f8680aea27 --- /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 e28e8284a218..0c861a0d293d 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.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 7a9d031a0a60..054b612d6372 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 void testAddNewFailedUrl() throws Exception { } /** - * 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 a690b1a1c6ef..605264e4baef 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 @@ 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 void setUp() throws Exception { */ 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 e42003e4fc8b..fb53d0c83c54 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 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 61325c652cc1..0059ce4f3fae 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.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 void setRemoteHost(String host) { } /* (non-Javadoc) - * @see javax.servlet.http.HttpServletRequest#changeSessionId + * @see jakarta.servlet.http.HttpServletRequest#changeSessionId */ @Override public String changeSessionId() { @@ -73,7 +74,7 @@ public String changeSessionId() { } /* (non-Javadoc) - * @see javax.servlet.http.HttpServletRequest#getAuthType() + * @see jakarta.servlet.http.HttpServletRequest#getAuthType() */ @Override public String getAuthType() { @@ -82,7 +83,7 @@ public String getAuthType() { } /* (non-Javadoc) - * @see javax.servlet.http.HttpServletRequest#getContextPath() + * @see jakarta.servlet.http.HttpServletRequest#getContextPath() */ @Override public String getContextPath() { @@ -91,7 +92,7 @@ public String getContextPath() { } /* (non-Javadoc) - * @see javax.servlet.http.HttpServletRequest#getCookies() + * @see jakarta.servlet.http.HttpServletRequest#getCookies() */ @Override public Cookie[] getCookies() { @@ -100,7 +101,7 @@ public Cookie[] getCookies() { } /* (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 void addHeader(String headerName, String headerValue) { 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 DispatcherType getDispatcherType() { } /* (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 String getHeader(String key) { } /* (non-Javadoc) - * @see javax.servlet.http.HttpServletRequest#getHeaderNames() + * @see jakarta.servlet.http.HttpServletRequest#getHeaderNames() */ @Override public Enumeration getHeaderNames() { @@ -147,7 +172,7 @@ public Enumeration getHeaderNames() { } /* (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 Enumeration getHeaders(String arg0) { } /* (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 int getIntHeader(String arg0) { } /* (non-Javadoc) - * @see javax.servlet.http.HttpServletRequest#getMethod() + * @see jakarta.servlet.http.HttpServletRequest#getMethod() */ @Override public String getMethod() { @@ -172,7 +197,7 @@ public String getMethod() { } /* (non-Javadoc) - * @see javax.servlet.http.HttpServletRequest#getPathInfo() + * @see jakarta.servlet.http.HttpServletRequest#getPathInfo() */ @Override public String getPathInfo() { @@ -181,7 +206,7 @@ public String getPathInfo() { } /* (non-Javadoc) - * @see javax.servlet.http.HttpServletRequest#getPathTranslated() + * @see jakarta.servlet.http.HttpServletRequest#getPathTranslated() */ @Override public String getPathTranslated() { @@ -190,7 +215,7 @@ public String getPathTranslated() { } /* (non-Javadoc) - * @see javax.servlet.http.HttpServletRequest#getQueryString() + * @see jakarta.servlet.http.HttpServletRequest#getQueryString() */ @Override public String getQueryString() { @@ -199,7 +224,7 @@ public String getQueryString() { } /* (non-Javadoc) - * @see javax.servlet.http.HttpServletRequest#getRemoteUser() + * @see jakarta.servlet.http.HttpServletRequest#getRemoteUser() */ @Override public String getRemoteUser() { @@ -208,7 +233,7 @@ public String getRemoteUser() { } /* (non-Javadoc) - * @see javax.servlet.http.HttpServletRequest#getRequestURI() + * @see jakarta.servlet.http.HttpServletRequest#getRequestURI() */ @Override public String getRequestURI() { @@ -217,7 +242,7 @@ public String getRequestURI() { } /* (non-Javadoc) - * @see javax.servlet.http.HttpServletRequest#getRequestURL() + * @see jakarta.servlet.http.HttpServletRequest#getRequestURL() */ @Override public StringBuffer getRequestURL() { @@ -226,7 +251,7 @@ public StringBuffer getRequestURL() { } /* (non-Javadoc) - * @see javax.servlet.http.HttpServletRequest#getRequestedSessionId() + * @see jakarta.servlet.http.HttpServletRequest#getRequestedSessionId() */ @Override public String getRequestedSessionId() { @@ -235,7 +260,7 @@ public String getRequestedSessionId() { } /* (non-Javadoc) - * @see javax.servlet.http.HttpServletRequest#getServletPath() + * @see jakarta.servlet.http.HttpServletRequest#getServletPath() */ @Override public String getServletPath() { @@ -244,7 +269,7 @@ public String getServletPath() { } /* (non-Javadoc) - * @see javax.servlet.http.HttpServletRequest#getSession() + * @see jakarta.servlet.http.HttpServletRequest#getSession() */ @Override public HttpSession getSession() { @@ -253,7 +278,7 @@ public HttpSession getSession() { } /* (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 HttpSession getSession(boolean arg0) { } /* (non-Javadoc) - * @see javax.servlet.http.HttpServletRequest#getUserPrincipal() + * @see jakarta.servlet.http.HttpServletRequest#getUserPrincipal() */ @Override public Principal getUserPrincipal() { @@ -271,7 +296,7 @@ public Principal getUserPrincipal() { } /* (non-Javadoc) - * @see javax.servlet.http.HttpServletRequest#isRequestedSessionIdFromCookie() + * @see jakarta.servlet.http.HttpServletRequest#isRequestedSessionIdFromCookie() */ @Override public boolean isRequestedSessionIdFromCookie() { @@ -280,7 +305,7 @@ public boolean isRequestedSessionIdFromCookie() { } /* (non-Javadoc) - * @see javax.servlet.http.HttpServletRequest#isRequestedSessionIdFromURL() + * @see jakarta.servlet.http.HttpServletRequest#isRequestedSessionIdFromURL() */ @Override public boolean isRequestedSessionIdFromURL() { @@ -289,17 +314,7 @@ public boolean isRequestedSessionIdFromURL() { } /* (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 boolean authenticate(HttpServletResponse httpServletResponse) { } /* (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 void login(String s, String s1) { } /* (non-Javadoc) - * @see javax.servlet.http.HttpServletRequest#logout() + * @see jakarta.servlet.http.HttpServletRequest#logout() */ @Override public void logout() { @@ -323,7 +338,7 @@ public void logout() { } /* (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 Part getPart(String arg0) { } /* (non-Javadoc) - * @see javax.servlet.http.HttpServletRequest#getParts() + * @see jakarta.servlet.http.HttpServletRequest#getParts() */ @Override public Collection getParts() { @@ -340,7 +355,7 @@ public Collection getParts() { } /* (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 T upgrade(Class aClass) throws IOExcept } /* (non-Javadoc) - * @see javax.servlet.http.HttpServletRequest#isRequestedSessionIdValid() + * @see jakarta.servlet.http.HttpServletRequest#isRequestedSessionIdValid() */ @Override public boolean isRequestedSessionIdValid() { @@ -357,7 +372,7 @@ public boolean isRequestedSessionIdValid() { } /* (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 boolean isUserInRole(String arg0) { } /* (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 Object getAttribute(String arg0) { } /* (non-Javadoc) - * @see javax.servlet.ServletRequest#getAttributeNames() + * @see jakarta.servlet.ServletRequest#getAttributeNames() */ @Override public Enumeration getAttributeNames() { @@ -384,7 +399,7 @@ public Enumeration getAttributeNames() { } /* (non-Javadoc) - * @see javax.servlet.ServletRequest#getCharacterEncoding() + * @see jakarta.servlet.ServletRequest#getCharacterEncoding() */ @Override public String getCharacterEncoding() { @@ -393,7 +408,7 @@ public String getCharacterEncoding() { } /* (non-Javadoc) - * @see javax.servlet.ServletRequest#getContentLength() + * @see jakarta.servlet.ServletRequest#getContentLength() */ @Override public int getContentLength() { @@ -402,7 +417,7 @@ public int getContentLength() { } /* (non-Javadoc) - * @see javax.servlet.ServletRequest#getContentLengthLong() + * @see jakarta.servlet.ServletRequest#getContentLengthLong() */ @Override public long getContentLengthLong() { @@ -410,7 +425,7 @@ public long getContentLengthLong() { } /* (non-Javadoc) - * @see javax.servlet.ServletRequest#getContentType() + * @see jakarta.servlet.ServletRequest#getContentType() */ @Override public String getContentType() { @@ -419,7 +434,7 @@ public String getContentType() { } /* (non-Javadoc) - * @see javax.servlet.ServletRequest#getInputStream() + * @see jakarta.servlet.ServletRequest#getInputStream() */ @Override public ServletInputStream getInputStream() throws IOException { @@ -428,7 +443,7 @@ public ServletInputStream getInputStream() throws IOException { } /* (non-Javadoc) - * @see javax.servlet.ServletRequest#getLocale() + * @see jakarta.servlet.ServletRequest#getLocale() */ @Override public Locale getLocale() { @@ -437,7 +452,7 @@ public Locale getLocale() { } /* (non-Javadoc) - * @see javax.servlet.ServletRequest#getLocales() + * @see jakarta.servlet.ServletRequest#getLocales() */ @Override public Enumeration getLocales() { @@ -446,7 +461,7 @@ public Enumeration getLocales() { } /* (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 String getParameter(String arg0) { } /* (non-Javadoc) - * @see javax.servlet.ServletRequest#getParameterMap() + * @see jakarta.servlet.ServletRequest#getParameterMap() */ @Override public Map getParameterMap() { @@ -464,7 +479,7 @@ public Map getParameterMap() { } /* (non-Javadoc) - * @see javax.servlet.ServletRequest#getParameterNames() + * @see jakarta.servlet.ServletRequest#getParameterNames() */ @Override public Enumeration getParameterNames() { @@ -473,7 +488,7 @@ public Enumeration getParameterNames() { } /* (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 String[] getParameterValues(String arg0) { } /* (non-Javadoc) - * @see javax.servlet.ServletRequest#getProtocol() + * @see jakarta.servlet.ServletRequest#getProtocol() */ @Override public String getProtocol() { @@ -491,7 +506,7 @@ public String getProtocol() { } /* (non-Javadoc) - * @see javax.servlet.ServletRequest#getReader() + * @see jakarta.servlet.ServletRequest#getReader() */ @Override public BufferedReader getReader() throws IOException { @@ -500,17 +515,7 @@ public BufferedReader getReader() throws IOException { } /* (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 String getRemoteAddr() { } /* (non-Javadoc) - * @see javax.servlet.ServletRequest#getRemoteHost() + * @see jakarta.servlet.ServletRequest#getRemoteHost() */ @Override public String getRemoteHost() { @@ -526,7 +531,7 @@ public String getRemoteHost() { } /* (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 RequestDispatcher getRequestDispatcher(String arg0) { } /* (non-Javadoc) - * @see javax.servlet.ServletRequest#getScheme() + * @see jakarta.servlet.ServletRequest#getScheme() */ @Override public String getScheme() { @@ -544,7 +549,7 @@ public String getScheme() { } /* (non-Javadoc) - * @see javax.servlet.ServletRequest#getServerName() + * @see jakarta.servlet.ServletRequest#getServerName() */ @Override public String getServerName() { @@ -553,7 +558,7 @@ public String getServerName() { } /* (non-Javadoc) - * @see javax.servlet.ServletRequest#getServerPort() + * @see jakarta.servlet.ServletRequest#getServerPort() */ @Override public int getServerPort() { @@ -562,7 +567,7 @@ public int getServerPort() { } /* (non-Javadoc) - * @see javax.servlet.ServletRequest#isSecure() + * @see jakarta.servlet.ServletRequest#isSecure() */ @Override public boolean isSecure() { @@ -571,7 +576,7 @@ public boolean isSecure() { } /* (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 void removeAttribute(String arg0) { } /* (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 void setAttribute(String arg0, Object arg1) { } /* (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 void setCharacterEncoding(String arg0) } /* (non-Javadoc) - * @see javax.servlet.ServletRequest#startAsync + * @see jakarta.servlet.ServletRequest#startAsync */ @Override public AsyncContext startAsync() throws IllegalStateException { @@ -604,7 +609,7 @@ public AsyncContext startAsync() throws IllegalStateException { } /* (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 AsyncContext startAsync(ServletRequest servletRequest, ServletResponse se } /* (non-Javadoc) - * @see javax.servlet.ServletRequest#isAsyncStarted + * @see jakarta.servlet.ServletRequest#isAsyncStarted */ @Override public boolean isAsyncStarted() { @@ -621,7 +626,7 @@ public boolean isAsyncStarted() { } /* (non-Javadoc) - * @see javax.servlet.ServletRequest#isAsyncSupported + * @see jakarta.servlet.ServletRequest#isAsyncSupported */ @Override public boolean isAsyncSupported() { @@ -629,7 +634,7 @@ public boolean isAsyncSupported() { } /* (non-Javadoc) - * @see javax.servlet.ServletRequest#getAsyncContext + * @see jakarta.servlet.ServletRequest#getAsyncContext */ @Override public AsyncContext getAsyncContext() { @@ -657,7 +662,7 @@ public int getLocalPort() { } /* (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 24f8c0f124be..0b311a3115dd 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 void testCaseInsensitiveMatching() throws Exception { /** * 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 63046b32b6d4..ba638e4ed796 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 void testGetSpiderIpAddresses() { /** * 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/storage/bitstore/S3BitStoreServiceIT.java b/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIT.java index 7aae1cf2719c..6ea21eac8d6d 100644 --- a/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIT.java +++ b/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIT.java @@ -19,6 +19,7 @@ import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.startsWith; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; @@ -42,6 +43,7 @@ import io.findify.s3mock.S3Mock; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; +import org.apache.commons.lang.BooleanUtils; import org.dspace.AbstractIntegrationTestWithDatabase; import org.dspace.app.matcher.LambdaMatcher; import org.dspace.authorize.AuthorizeException; @@ -53,6 +55,8 @@ import org.dspace.content.Collection; import org.dspace.content.Item; import org.dspace.core.Utils; +import org.dspace.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; import org.hamcrest.Matcher; import org.hamcrest.Matchers; import org.junit.After; @@ -60,6 +64,7 @@ import org.junit.Test; + /** * @author Luca Giamminonni (luca.giamminonni at 4science.com) */ @@ -77,9 +82,13 @@ public class S3BitStoreServiceIT extends AbstractIntegrationTestWithDatabase { private File s3Directory; + private ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); + + @Before public void setup() throws Exception { + configurationService.setProperty("assetstore.s3.enabled", "true"); s3Directory = new File(System.getProperty("java.io.tmpdir"), "s3"); s3Mock = S3Mock.create(8001, s3Directory.getAbsolutePath()); @@ -88,7 +97,9 @@ public void setup() throws Exception { amazonS3Client = createAmazonS3Client(); s3BitStoreService = new S3BitStoreService(amazonS3Client); - + s3BitStoreService.setEnabled(BooleanUtils.toBoolean( + configurationService.getProperty("assetstore.s3.enabled"))); + s3BitStoreService.setBufferSize(22); context.turnOffAuthorisationSystem(); parentCommunity = CommunityBuilder.createCommunity(context) @@ -119,12 +130,25 @@ public void testBitstreamPutAndGetWithAlreadyPresentBucket() throws IOException assertThat(amazonS3Client.listBuckets(), contains(bucketNamed(bucketName))); context.turnOffAuthorisationSystem(); - String content = "Test bitstream content"; + String content = "Test bitstream content"; + String contentOverOneSpan = "This content span two chunks"; + String contentExactlyTwoSpans = "Test bitstream contentTest bitstream content"; + String contentOverOneTwoSpans = "Test bitstream contentThis content span three chunks"; Bitstream bitstream = createBitstream(content); + Bitstream bitstreamOverOneSpan = createBitstream(contentOverOneSpan); + Bitstream bitstreamExactlyTwoSpans = createBitstream(contentExactlyTwoSpans); + Bitstream bitstreamOverOneTwoSpans = createBitstream(contentOverOneTwoSpans); context.restoreAuthSystemState(); - s3BitStoreService.put(bitstream, toInputStream(content)); + checkGetPut(bucketName, content, bitstream); + checkGetPut(bucketName, contentOverOneSpan, bitstreamOverOneSpan); + checkGetPut(bucketName, contentExactlyTwoSpans, bitstreamExactlyTwoSpans); + checkGetPut(bucketName, contentOverOneTwoSpans, bitstreamOverOneTwoSpans); + + } + private void checkGetPut(String bucketName, String content, Bitstream bitstream) throws IOException { + s3BitStoreService.put(bitstream, toInputStream(content)); String expectedChecksum = Utils.toHex(generateChecksum(content)); assertThat(bitstream.getSizeBytes(), is((long) content.length())); @@ -137,7 +161,6 @@ public void testBitstreamPutAndGetWithAlreadyPresentBucket() throws IOException String key = s3BitStoreService.getFullKey(bitstream.getInternalId()); ObjectMetadata objectMetadata = amazonS3Client.getObjectMetadata(bucketName, key); assertThat(objectMetadata.getContentMD5(), is(expectedChecksum)); - } @Test @@ -382,6 +405,17 @@ public void givenBitStreamIdentifierWithSlashesWhenSanitizedThenSlashesMustBeRem assertThat(computedPath, Matchers.not(Matchers.containsString(File.separator))); } + @Test + public void testDoNotInitializeConfigured() throws Exception { + String assetstores3enabledOldValue = configurationService.getProperty("assetstore.s3.enabled"); + configurationService.setProperty("assetstore.s3.enabled", "false"); + s3BitStoreService = new S3BitStoreService(amazonS3Client); + s3BitStoreService.init(); + assertFalse(s3BitStoreService.isInitialized()); + assertFalse(s3BitStoreService.isEnabled()); + configurationService.setProperty("assetstore.s3.enabled", assetstores3enabledOldValue); + } + private byte[] generateChecksum(String content) { try { MessageDigest m = MessageDigest.getInstance("MD5"); 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 60407823485b..aa4cd8bd4e49 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.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 void createSupervisionOrderTest() throws Exception { .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 void findSupervisionOrderTest() throws Exception { .build(); SupervisionOrder supervisionOrderOne = - supervisionOrderService.create(context, workspaceItem.getItem(), groupA); + SupervisionOrderBuilder.createSupervisionOrder(context, workspaceItem.getItem(), groupA) + .build(); context.restoreAuthSystemState(); @@ -205,9 +207,12 @@ public void findAllSupervisionOrdersTest() throws Exception { .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 void findSupervisionOrderByItemTest() throws Exception { .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 void findSupervisionOrderByItemAndGroupTest() throws Exception { .addMember(eperson) .build(); - supervisionOrderService.create(context, item, groupA); + SupervisionOrderBuilder.createSupervisionOrder(context, item, groupA) + .build(); context.restoreAuthSystemState(); @@ -370,7 +379,8 @@ public void isSupervisorTest() throws Exception { .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/DSpaceConfigurationInitializer.java b/dspace-api/src/test/java/org/dspace/util/DSpaceConfigurationInitializer.java index e2e0355f123a..e5a8adb2fdf7 100644 --- a/dspace-api/src/test/java/org/dspace/util/DSpaceConfigurationInitializer.java +++ b/dspace-api/src/test/java/org/dspace/util/DSpaceConfigurationInitializer.java @@ -8,7 +8,7 @@ package org.dspace.util; import org.apache.commons.configuration2.Configuration; -import org.apache.commons.configuration2.spring.ConfigurationPropertySource; +import org.dspace.servicemanager.config.DSpaceConfigurationPropertySource; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; import org.springframework.context.ApplicationContextInitializer; @@ -38,8 +38,8 @@ public void initialize(final ConfigurableApplicationContext applicationContext) Configuration configuration = configurationService.getConfiguration(); // Create an Apache Commons Configuration Property Source from our configuration - ConfigurationPropertySource apacheCommonsConfigPropertySource = - new ConfigurationPropertySource(configuration.getClass().getName(), configuration); + DSpaceConfigurationPropertySource apacheCommonsConfigPropertySource = + new DSpaceConfigurationPropertySource(configuration.getClass().getName(), configuration); // Prepend it to the Environment's list of PropertySources // NOTE: This is added *first* in the list so that settings in DSpace's 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 000000000000..cd37a6fb31d9 --- /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 a6f381bafbae..8f9169875ab3 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 void initialize(final ConfigurableApplicationContext applicationContext) * 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/workflow/CurationTaskConfigTest.java b/dspace-api/src/test/java/org/dspace/workflow/CurationTaskConfigTest.java index 92d6ff91481b..61a283c89ef2 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.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 66dd2cee807f..d3866d534b24 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 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 865abaca2152..20fd0cde26b8 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 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 ae387c97495b..f5e4521fcdc5 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.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-iiif/pom.xml b/dspace-iiif/pom.xml index 2f34671139c8..9c4c174bf39f 100644 --- a/dspace-iiif/pom.xml +++ b/dspace-iiif/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 8.0-SNAPSHOT + 9.0-SNAPSHOT .. @@ -28,6 +28,12 @@ + + org.apache.logging.log4j + log4j-api + ${log4j.version} + + org.springframework.boot @@ -38,6 +44,11 @@ org.springframework.boot spring-boot-starter-logging + + + org.springframework + spring-jcl + @@ -69,6 +80,13 @@ org.springframework.boot spring-boot-starter-security ${spring-boot.version} + + + + io.micrometer + micrometer-observation + + @@ -93,7 +111,7 @@ de.digitalcollections.iiif iiif-apis - 0.3.10 + 0.3.11 org.javassist 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 3947df35337f..f6d7aa8950bb 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 @@ 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 da977a5ccc0d..924cf48a9f57 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 @@ 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 f064a1b974ce..701a79aa4bba 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 @@ 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 28cc13c07d36..80af636d3589 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 @@ 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 94c18283753c..709983e9c5a8 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 aef979b6353e..235e7413f606 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 807269264088..dd196154ec6c 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 @@ 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.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 fe8acf31c43f..5b022bc91631 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 @@ 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 782a5a985292..1a1005993ec2 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 boolean isSearchable(Item item) { } /** - * 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 @@ private String getToCBundleLabel(Bundle bundle) { * * @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 db8b55c79b5d..9ba9ff3e2f71 100644 --- a/dspace-oai/pom.xml +++ b/dspace-oai/pom.xml @@ -8,7 +8,7 @@ dspace-parent org.dspace - 8.0-SNAPSHOT + 9.0-SNAPSHOT .. @@ -65,9 +65,8 @@ - javax.inject - javax.inject - 1 + jakarta.inject + jakarta.inject-api @@ -80,6 +79,11 @@ org.springframework.boot spring-boot-starter-logging + + + org.springframework + spring-jcl + @@ -94,22 +98,9 @@ org.springframework.boot spring-boot-starter-web - - - org.parboiled - parboiled-java - - - - org.parboiled - parboiled-java - 1.3.1 - - org.dspace @@ -118,8 +109,8 @@ - javax.servlet - javax.servlet-api + jakarta.servlet + jakarta.servlet-api provided @@ -128,23 +119,6 @@ org.apache.logging.log4j log4j-api - - org.apache.logging.log4j - log4j-core - - - org.apache.logging.log4j - log4j-web - - - org.apache.logging.log4j - log4j-slf4j-impl - runtime - - - org.apache.logging.log4j - log4j-1.2-api - 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 806ecf9d07f4..dc4efde880d5 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.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(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/CCElementItemCompilePlugin.java b/dspace-oai/src/main/java/org/dspace/xoai/app/CCElementItemCompilePlugin.java index 225d56a4c982..370543029d8b 100644 --- a/dspace-oai/src/main/java/org/dspace/xoai/app/CCElementItemCompilePlugin.java +++ b/dspace-oai/src/main/java/org/dspace/xoai/app/CCElementItemCompilePlugin.java @@ -11,7 +11,7 @@ import com.lyncode.xoai.dataprovider.xml.xoai.Element; import com.lyncode.xoai.dataprovider.xml.xoai.Metadata; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; import org.dspace.content.Item; import org.dspace.core.Context; import org.dspace.license.factory.LicenseServiceFactory; 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 25cc1ee3655f..ad138ca9f2ad 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 @@ -9,7 +9,7 @@ import static com.lyncode.xoai.dataprovider.core.Granularity.Second; import static java.util.Objects.nonNull; -import static org.apache.commons.lang.StringUtils.EMPTY; +import static org.apache.commons.lang3.StringUtils.EMPTY; import static org.apache.solr.common.params.CursorMarkParams.CURSOR_MARK_PARAM; import static org.apache.solr.common.params.CursorMarkParams.CURSOR_MARK_START; import static org.dspace.xoai.util.ItemUtils.retrieveMetadata; 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 379f2fa18134..3d826152c6ba 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.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.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 index 3599c5b9e168..9bf1c65dc9d9 100644 --- a/dspace-oai/src/main/java/org/dspace/xoai/filter/ItemsWithBitstreamFilter.java +++ b/dspace-oai/src/main/java/org/dspace/xoai/filter/ItemsWithBitstreamFilter.java @@ -9,8 +9,8 @@ import java.sql.SQLException; -import org.apache.log4j.LogManager; -import org.apache.log4j.Logger; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.content.Bundle; import org.dspace.content.Item; import org.dspace.handle.factory.HandleServiceFactory; 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 6c378ff6d101..733c1b67e0c3 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 @@ 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 6ed44bdcc233..fdce0a23a6ab 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 904b7f888539..e86d15a4fa4e 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 2a000f43ea06..8c9841dfb949 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.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/test/java/org/dspace/xoai/tests/integration/xoai/PipelineTest.java b/dspace-oai/src/test/java/org/dspace/xoai/tests/integration/xoai/PipelineTest.java index 0f48824159c2..0f7ffde0bd00 100644 --- a/dspace-oai/src/test/java/org/dspace/xoai/tests/integration/xoai/PipelineTest.java +++ b/dspace-oai/src/test/java/org/dspace/xoai/tests/integration/xoai/PipelineTest.java @@ -13,13 +13,14 @@ import static org.hamcrest.MatcherAssert.assertThat; import java.io.InputStream; +import java.nio.charset.Charset; import javax.xml.transform.TransformerFactory; import javax.xml.transform.stream.StreamSource; import com.lyncode.xoai.util.XSLPipeline; +import org.apache.commons.io.IOUtils; import org.dspace.xoai.tests.support.XmlMatcherBuilder; import org.junit.Test; -import org.parboiled.common.FileUtils; public class PipelineTest { private static TransformerFactory factory = TransformerFactory.newInstance(); @@ -28,9 +29,9 @@ public class PipelineTest { public void pipelineTest() throws Exception { InputStream input = PipelineTest.class.getClassLoader().getResourceAsStream("item.xml"); InputStream xslt = PipelineTest.class.getClassLoader().getResourceAsStream("oai_dc.xsl"); - String output = FileUtils.readAllText(new XSLPipeline(input, true) - .apply(factory.newTemplates(new StreamSource(xslt))) - .getTransformed()); + String output = IOUtils.toString(new XSLPipeline(input, true) + .apply(factory.newTemplates(new StreamSource(xslt))) + .getTransformed(), Charset.defaultCharset()); assertThat(output, oai_dc().withXPath("/oai_dc:dc/dc:title", equalTo("Teste"))); 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 53fc6434490c..b1ae4d209f58 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 void setUp() { @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-rdf/pom.xml b/dspace-rdf/pom.xml index f21381eb4ea3..7f214dc088d4 100644 --- a/dspace-rdf/pom.xml +++ b/dspace-rdf/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - 8.0-SNAPSHOT + 9.0-SNAPSHOT .. @@ -67,12 +67,17 @@ org.springframework.boot spring-boot-starter-logging + + + org.springframework + spring-jcl + - javax.servlet - javax.servlet-api + jakarta.servlet + jakarta.servlet-api provided @@ -80,14 +85,6 @@ org.apache.logging.log4j log4j-api - - org.apache.logging.log4j - log4j-core - - - org.apache.logging.log4j - log4j-web - org.apache.commons 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 007f865fb700..7344b2c74eb3 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 @@ 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 @@ protected void serveNamedGraph(String uri, String lang, String contentType, } 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 7224bb9bfb05..d985740f520d 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 @@ 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-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index 8e713bb6af8b..b115cff73417 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -14,7 +14,7 @@ org.dspace dspace-parent - 8.0-SNAPSHOT + 9.0-SNAPSHOT .. @@ -31,7 +31,7 @@ org.codehaus.mojo properties-maven-plugin - 1.1.0 + 1.2.1 initialize @@ -213,7 +213,7 @@ - ${agnostic.build.dir}/testing/dspace/ + ${agnostic.build.dir}/testing/dspace true ${agnostic.build.dir}/testing/dspace/solr/ @@ -272,7 +272,7 @@ - ${agnostic.build.dir}/testing/dspace/ + ${agnostic.build.dir}/testing/dspace true ${agnostic.build.dir}/testing/dspace/solr/ @@ -335,6 +335,18 @@ org.springframework.boot spring-boot-starter-actuator ${spring-boot.version} + + + + io.micrometer + micrometer-observation + + + + io.micrometer + micrometer-commons + + @@ -368,6 +380,13 @@ org.webjars.bowergithub.codeseven toastr 2.1.4 + + + + org.webjars.bowergithub.jquery + jquery-dist + + @@ -389,6 +408,13 @@ org.webjars.bowergithub.jashkenas backbone 1.4.1 + + + + org.webjars.bowergithub.jashkenas + underscore + + + + io.micrometer + micrometer-observation + + @@ -421,19 +454,30 @@ org.springframework.boot spring-boot-starter - ${spring-boot.version} + ${spring-boot.version} org.springframework.boot spring-boot-starter-logging + + + org.springframework + spring-jcl + org.springframework.boot spring-boot-starter-log4j2 - ${spring-boot.version} + ${spring-boot.version} + + + + org.apache.logging.log4j + log4j-api + ${log4j.version} @@ -494,6 +538,21 @@ com.nimbusds nimbus-jose-jwt ${nimbus-jose-jwt.version} + + + + net.minidev + json-smart + + + + + + + net.minidev + json-smart + 2.5.1 @@ -527,6 +586,13 @@ spring-boot-starter-test test + + + org.apache.httpcomponents.client5 + httpclient5 + 5.4.1 + test + org.springframework.security spring-security-test @@ -536,6 +602,13 @@ com.jayway.jsonpath json-path + + + + net.minidev + json-smart + + com.jayway.jsonpath @@ -568,6 +641,13 @@ solr-core ${solr.client.version} test + + + + org.antlr + antlr4-runtime + + org.apache.lucene @@ -591,8 +671,8 @@ test - javax.annotation - javax.annotation-api + jakarta.annotation + jakarta.annotation-api diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/AuthenticationRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/AuthenticationRestController.java index e8b8eb8e70da..63ac50b6ea06 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/AuthenticationRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/AuthenticationRestController.java @@ -14,9 +14,9 @@ import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.dspace.app.rest.converter.ConverterService; import org.dspace.app.rest.converter.EPersonConverter; import org.dspace.app.rest.link.HalLinkService; @@ -36,8 +36,6 @@ import org.dspace.app.rest.utils.Utils; import org.dspace.core.Context; import org.dspace.service.ClientInfoService; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; @@ -64,8 +62,6 @@ @RestController public class AuthenticationRestController implements InitializingBean { - private static final Logger log = LoggerFactory.getLogger(AuthenticationRestController.class); - @Autowired DiscoverableEndpointsService discoverableEndpointsService; @@ -224,7 +220,7 @@ private AuthenticationTokenResource shortLivedTokenResponse(HttpServletRequest r * @return ResponseEntity */ @RequestMapping(value = "/login", method = { RequestMethod.GET, RequestMethod.PUT, RequestMethod.PATCH, - RequestMethod.DELETE }) + RequestMethod.DELETE }) public ResponseEntity login() { return ResponseEntity.status(HttpStatus.METHOD_NOT_ALLOWED).body("Only POST is allowed for login requests."); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamBundleController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamBundleController.java index 38fb4f4150ad..a26087b49cfe 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamBundleController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamBundleController.java @@ -14,9 +14,9 @@ import java.sql.SQLException; import java.util.List; 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.dspace.app.rest.converter.ConverterService; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.link.HalLinkService; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamCategoryRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamCategoryRestController.java index aa511bcb9282..81968269fac6 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamCategoryRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamCategoryRestController.java @@ -10,9 +10,9 @@ import static org.dspace.app.rest.utils.ContextUtil.obtainContext; import java.sql.SQLException; -import javax.servlet.http.HttpServletRequest; import com.fasterxml.jackson.databind.JsonNode; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.model.BitstreamRest; import org.dspace.app.rest.repository.BitstreamRestRepository; import org.dspace.authorize.AuthorizeException; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java index 9b5ede37c85f..db9d26a5f6ca 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java @@ -15,11 +15,12 @@ import java.sql.SQLException; import java.util.List; import java.util.UUID; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.ws.rs.core.Response; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.ws.rs.core.Response; import org.apache.catalina.connector.ClientAbortException; +import org.apache.commons.collections4.ListUtils; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.dspace.app.rest.converter.ConverterService; @@ -204,12 +205,39 @@ private boolean isNotAnErrorResponse(HttpServletResponse response) { || responseCode.equals(Response.Status.Family.REDIRECTION); } + /** + * Check if a Bitstream of the specified format should always be downloaded (i.e. "content-disposition: attachment") + * or can be opened inline (i.e. "content-disposition: inline"). + *

    + * NOTE that downloading via "attachment" is more secure, as the user's browser will not attempt to process or + * display the file. But, downloading via "inline" may be seen as more user-friendly for common formats. + * @param format BitstreamFormat + * @return true if always download ("attachment"). false if can be opened inline ("inline") + */ private boolean checkFormatForContentDisposition(BitstreamFormat format) { - // never automatically download undefined formats - if (format == null) { - return false; + // Undefined or Unknown formats should ALWAYS be downloaded for additional security. + if (format == null || format.getSupportLevel() == BitstreamFormat.UNKNOWN) { + return true; + } + + // Load additional formats configured to require download + List configuredFormats = List.of(configurationService. + getArrayProperty("webui.content_disposition_format")); + + // If configuration includes "*", then all formats will always be downloaded. + if (configuredFormats.contains("*")) { + return true; } - List formats = List.of((configurationService.getArrayProperty("webui.content_disposition_format"))); + + // Define a download list of formats which DSpace forces to ALWAYS be downloaded. + // These formats can embed JavaScript which may be run in the user's browser if the file is opened inline. + // Therefore, DSpace blocks opening these formats inline as it could be used for an XSS attack. + List downloadOnlyFormats = List.of("text/html", "text/javascript", "text/xml", "rdf"); + + // Combine our two lists + List formats = ListUtils.union(downloadOnlyFormats, configuredFormats); + + // See if the passed in format's MIME type or file extension is listed. boolean download = formats.contains(format.getMIMEType()); if (!download) { for (String ext : format.getExtensions()) { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/BundleUploadBitstreamController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/BundleUploadBitstreamController.java index 0cb6bc47e033..d86c54e4fccc 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/BundleUploadBitstreamController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/BundleUploadBitstreamController.java @@ -13,8 +13,8 @@ import java.io.InputStream; import java.sql.SQLException; import java.util.UUID; -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.rest.converter.ConverterService; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/CollectionGroupRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/CollectionGroupRestController.java index d85685a188b0..6aa6f7537895 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/CollectionGroupRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/CollectionGroupRestController.java @@ -13,9 +13,9 @@ import java.sql.SQLException; import java.util.List; 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.commons.lang3.StringUtils; import org.dspace.app.rest.converter.ConverterService; import org.dspace.app.rest.exception.UnprocessableEntityException; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/CollectionHarvestSettingsController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/CollectionHarvestSettingsController.java index 856804808c46..2ff1a851932b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/CollectionHarvestSettingsController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/CollectionHarvestSettingsController.java @@ -9,9 +9,9 @@ import java.sql.SQLException; 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.dspace.app.rest.converter.ConverterService; import org.dspace.app.rest.converter.HarvestedCollectionConverter; import org.dspace.app.rest.link.HalLinkService; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/CollectionItemTemplateController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/CollectionItemTemplateController.java index 6a0890fabc44..66a3965d869e 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/CollectionItemTemplateController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/CollectionItemTemplateController.java @@ -12,11 +12,11 @@ import java.io.IOException; import java.sql.SQLException; import java.util.UUID; -import javax.servlet.http.HttpServletRequest; -import javax.ws.rs.BadRequestException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.ws.rs.BadRequestException; import org.dspace.app.rest.converter.ConverterService; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.CollectionRest; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/CollectionLogoController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/CollectionLogoController.java index c3243d8887e1..7d0535858f1e 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/CollectionLogoController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/CollectionLogoController.java @@ -7,11 +7,13 @@ */ package org.dspace.app.rest; +import static org.dspace.app.rest.utils.RegexUtils.REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID; + import java.io.IOException; import java.sql.SQLException; import java.util.UUID; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.BitstreamRest; import org.dspace.app.rest.model.CollectionRest; @@ -44,15 +46,9 @@ */ @RestController @RequestMapping("/api/" + CollectionRest.CATEGORY + "/" + CollectionRest.PLURAL_NAME - + CollectionLogoController.REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID + "/logo") + + REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID + "/logo") public class CollectionLogoController { - /** - * Regular expression in the request mapping to accept UUID as identifier - */ - protected static final String REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID = - "/{uuid:[0-9a-fxA-FX]{8}-[0-9a-fxA-FX]{4}-[0-9a-fxA-FX]{4}-[0-9a-fxA-FX]{4}-[0-9a-fxA-FX]{12}}"; - @Autowired protected Utils utils; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/CommunityAdminGroupRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/CommunityAdminGroupRestController.java index 2265ac941e6b..77c5d96295a2 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/CommunityAdminGroupRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/CommunityAdminGroupRestController.java @@ -12,9 +12,9 @@ import java.io.IOException; import java.sql.SQLException; 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.dspace.app.rest.converter.ConverterService; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.GroupRest; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/CommunityLogoController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/CommunityLogoController.java index 52c0f000b6c6..72bfc0e5bc64 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/CommunityLogoController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/CommunityLogoController.java @@ -7,11 +7,13 @@ */ package org.dspace.app.rest; +import static org.dspace.app.rest.utils.RegexUtils.REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID; + import java.io.IOException; import java.sql.SQLException; import java.util.UUID; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.BitstreamRest; import org.dspace.app.rest.model.CommunityRest; @@ -44,15 +46,9 @@ */ @RestController @RequestMapping("/api/" + CommunityRest.CATEGORY + "/" + CommunityRest.PLURAL_NAME - + CommunityLogoController.REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID + "/logo") + + REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID + "/" + CommunityRest.LOGO) public class CommunityLogoController { - /** - * Regular expression in the request mapping to accept UUID as identifier - */ - protected static final String REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID = - "/{uuid:[0-9a-fxA-FX]{8}-[0-9a-fxA-FX]{4}-[0-9a-fxA-FX]{4}-[0-9a-fxA-FX]{4}-[0-9a-fxA-FX]{12}}"; - @Autowired protected Utils utils; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ContentReportRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ContentReportRestController.java new file mode 100644 index 000000000000..c3df859e955e --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ContentReportRestController.java @@ -0,0 +1,226 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.rest; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +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.app.rest.converter.ConverterService; +import org.dspace.app.rest.model.ContentReportSupportRest; +import org.dspace.app.rest.model.FilteredCollectionsQuery; +import org.dspace.app.rest.model.FilteredCollectionsRest; +import org.dspace.app.rest.model.FilteredItemsQueryPredicate; +import org.dspace.app.rest.model.FilteredItemsQueryRest; +import org.dspace.app.rest.model.FilteredItemsRest; +import org.dspace.app.rest.model.RestModel; +import org.dspace.app.rest.model.hateoas.ContentReportSupportResource; +import org.dspace.app.rest.model.hateoas.FilteredCollectionsResource; +import org.dspace.app.rest.model.hateoas.FilteredItemsResource; +import org.dspace.app.rest.repository.ContentReportRestRepository; +import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.contentreport.Filter; +import org.dspace.contentreport.service.ContentReportService; +import org.dspace.core.Context; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.rest.webmvc.ControllerUtils; +import org.springframework.hateoas.Link; +import org.springframework.hateoas.RepresentationModel; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +/** + * This controller receives and dispatches requests related to the + * contents reports ported from DSpace 6.x (Filtered Collections + * and Filtered Items). + * @author Jean-François Morin (Université Laval) + */ +@RestController +@RequestMapping("/api/" + RestModel.CONTENT_REPORT) +public class ContentReportRestController implements InitializingBean { + + private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(); + + @Autowired + private DiscoverableEndpointsService discoverableEndpointsService; + @Autowired + private ConverterService converter; + @Autowired + private ContentReportRestRepository contentReportRestRepository; + @Autowired + private ContentReportService contentReportService; + + @Override + public void afterPropertiesSet() throws Exception { + discoverableEndpointsService + .register(this, List.of(Link.of("/api/" + RestModel.CONTENT_REPORT, RestModel.CONTENT_REPORT))); + } + + @RequestMapping(method = RequestMethod.GET) + public ContentReportSupportResource getContentReportSupport() { + ContentReportSupportRest contentReportSupportRest = contentReportRestRepository.getContentReportSupport(); + return converter.toResource(contentReportSupportRest); + } + + /** + * GET-based endpoint for the Filtered Collections contents report. + * This method also serves as a feed for the HAL Browser infrastructure. + * @param filters querying filters received as a comma-separated string + * or as a multivalued parameter + * @param request HTTP request + * @param response HTTP response + * @return the list of collections with their respective statistics + */ + @PreAuthorize("hasAuthority('ADMIN')") + @GetMapping("/filteredcollections") + public ResponseEntity> getFilteredCollections( + @RequestParam(name = "filters", required = false) List filters, + HttpServletRequest request, HttpServletResponse response) throws IOException { + if (contentReportService.getEnabled()) { + Context context = ContextUtil.obtainContext(request); + Set filtersSet = listToStream(filters) + .map(Filter::get) + .filter(f -> f != null) + .collect(Collectors.toSet()); + FilteredCollectionsQuery query = FilteredCollectionsQuery.of(filtersSet); + return filteredCollectionsReport(context, query); + } + + error404(response); + return null; + } + + + private ResponseEntity> filteredCollectionsReport(Context context, + FilteredCollectionsQuery query) { + FilteredCollectionsRest report = contentReportRestRepository + .findFilteredCollections(context, query); + FilteredCollectionsResource result = converter.toResource(report); + return ControllerUtils.toResponseEntity(HttpStatus.OK, new HttpHeaders(), result); + } + + /** + * Endpoint for the Filtered Items contents report. + * All parameters received as comma-separated lists can also be repeated + * instead (e.g., filters=a&filters=b&...). + * @param collections comma-separated list UUIDs of collections to include in the report + * @param predicates predicates to filter the requested items. + * A given predicate has the form + * field:operator:value (if value is required by the operator), or + * field:operator (if no value is required by the operator). + * The colon is used here as a separator to avoid conflicts with the + * comma, which is already used by Spring as a multi-value separator. + * Predicates are actually retrieved directly through the request to prevent comma-containing + * predicate values from being split by the Spring infrastructure. + * @param pageNumber page number (starting at 0) + * @param pageLimit maximum number of items per page + * @param filters querying filters received as a comma-separated string + * @param additionalFields comma-separated list of extra fields to add to the report + * @param request HTTP request + * @param response HTTP response + * @param pageable paging parameters + * @return the list of items with their respective statistics + */ + @PreAuthorize("hasAuthority('ADMIN')") + @GetMapping("/filtereditems") + public ResponseEntity> getFilteredItems( + @RequestParam(name = "collections", required = false) List collections, + @RequestParam(name = "queryPredicates", required = false) List predicates, + @RequestParam(name = "pageNumber", defaultValue = "0") String pageNumber, + @RequestParam(name = "pageLimit", defaultValue = "10") String pageLimit, + @RequestParam(name = "filters", required = false) List filters, + @RequestParam(name = "additionalFields", required = false) List additionalFields, + HttpServletRequest request, HttpServletResponse response, Pageable pageable) throws IOException { + if (contentReportService.getEnabled()) { + Context context = ContextUtil.obtainContext(request); + String[] realPredicates = request.getParameterValues("queryPredicates"); + List collUuids = Optional.ofNullable(collections).orElseGet(() -> List.of()); + List preds = arrayToStream(realPredicates) + .map(FilteredItemsQueryPredicate::of) + .collect(Collectors.toList()); + int pgLimit = parseInt(pageLimit, 10); + int pgNumber = parseInt(pageNumber, 0); + Pageable myPageable = pageable; + if (pageable == null || pageable.getPageNumber() != pgNumber || pageable.getPageSize() != pgLimit) { + Sort sort = Optional.ofNullable(pageable).map(Pageable::getSort).orElse(Sort.unsorted()); + myPageable = PageRequest.of(pgNumber, pgLimit, sort); + } + Set filtersMap = listToStream(filters) + .map(Filter::get) + .filter(f -> f != null) + .collect(Collectors.toSet()); + List addFields = Optional.ofNullable(additionalFields).orElseGet(() -> List.of()); + FilteredItemsQueryRest query = FilteredItemsQueryRest.of(collUuids, preds, pgLimit, filtersMap, addFields); + + return filteredItemsReport(context, query, myPageable); + } + + error404(response); + return null; + } + + private static Stream listToStream(Collection array) { + return Optional.ofNullable(array) + .stream() + .flatMap(Collection::stream) + .filter(StringUtils::isNotBlank); + } + + private static Stream arrayToStream(String... array) { + return Optional.ofNullable(array) + .stream() + .flatMap(Arrays::stream) + .filter(StringUtils::isNotBlank); + } + + private static int parseInt(String value, int defaultValue) { + return Optional.ofNullable(value) + .stream() + .mapToInt(Integer::parseInt) + .findFirst() + .orElse(defaultValue); + } + + private ResponseEntity> filteredItemsReport(Context context, + FilteredItemsQueryRest query, Pageable pageable) { + FilteredItemsRest report = contentReportRestRepository + .findFilteredItems(context, query, pageable); + FilteredItemsResource result = converter.toResource(report); + return ControllerUtils.toResponseEntity(HttpStatus.OK, new HttpHeaders(), result); + } + + private void error404(HttpServletResponse response) throws IOException { + log.debug("Content Reports are disabled"); + String err = "Content Reports are disabled"; + response.setStatus(404); + response.setContentType("text/html"); + response.setContentLength(err.length()); + response.getWriter().write(err); + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/CsrfRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/CsrfRestController.java new file mode 100644 index 000000000000..7f94b22cfea5 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/CsrfRestController.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.rest; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +import org.springframework.data.rest.webmvc.ControllerUtils; +import org.springframework.hateoas.RepresentationModel; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.web.csrf.CsrfToken; +import org.springframework.security.web.csrf.CsrfTokenRepository; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * Define GET /api/security/csrf endpoint which may be used to obtain a CSRF token from Spring Security. + * This is useful to force a CSRF token to be generated prior to a POST/PUT/PATCH request that requires it. + *

    + * NOTE: This endpoint should be used sparingly to ensure clients are NOT performing two requests for every modifying + * request (e.g. a GET /csrf followed by a POST/PUT/PATCH to another endpoint). Ideally, calling this endpoint is only + * necessary BEFORE the first POST/PUT/PATCH (if a CSRF token has not yet been obtained), or in scenarios where the + * client must *force* the CSRF token to be reloaded. + */ +@RequestMapping(value = "/api/security") +@RestController +public class CsrfRestController { + + @Lazy + @Autowired + CsrfTokenRepository csrfTokenRepository; + + /** + * Return the current CSRF token as defined by Spring Security. + * Inspired by + * https://docs.spring.io/spring-security/reference/5.8/migration/servlet/exploits.html#_i_am_using_a_single_page_application_with_httpsessioncsrftokenrepository + * @param request HTTP Request + * @param response HTTP response + * @param csrfToken injected CsrfToken by Spring Security + * @return An empty response with CSRF in header & cookie + */ + @GetMapping("/csrf") + @PreAuthorize("permitAll()") + public ResponseEntity> getCsrf(HttpServletRequest request, + HttpServletResponse response, + CsrfToken csrfToken) { + // Save the CSRF token to our response using the currently enabled CsrfTokenRepository + csrfTokenRepository.saveToken(csrfToken, request, response); + + // Return a 204 No Content status + return ControllerUtils.toEmptyResponse(HttpStatus.NO_CONTENT); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/DiscoverableEndpointsService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/DiscoverableEndpointsService.java index 1853285d717f..aa22603387cc 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/DiscoverableEndpointsService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/DiscoverableEndpointsService.java @@ -84,7 +84,7 @@ public List getDiscoverableEndpoints() { private boolean isLinkValid(Object controller, String href) { // FIXME we need to implement a check to be sure that there are no other - // controller with an highter precedence mapped on the same URL (this + // controller with an higher precedence mapped on the same URL (this // could be used to override default implementation) return true; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/EntityTypeLabelRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/EntityTypeLabelRestController.java index 072975417217..93658848b08e 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/EntityTypeLabelRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/EntityTypeLabelRestController.java @@ -8,9 +8,9 @@ package org.dspace.app.rest; import java.sql.SQLException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.dspace.app.rest.converter.ConverterService; import org.dspace.app.rest.model.EntityTypeRest; import org.dspace.app.rest.model.hateoas.EntityTypeResource; @@ -40,7 +40,7 @@ * @author Maria Verdonck (Atmire) on 2019-12-13 */ @RestController -@RequestMapping("/api/" + EntityTypeRest.CATEGORY + "/" + EntityTypeRest.NAME_PLURAL) +@RequestMapping("/api/" + EntityTypeRest.CATEGORY + "/" + EntityTypeRest.PLURAL_NAME) public class EntityTypeLabelRestController { protected final EntityTypeService entityTypeService = ContentServiceFactory.getInstance().getEntityTypeService(); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/GroupRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/GroupRestController.java index 522d69fbe84b..4ad2834cbcf5 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/GroupRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/GroupRestController.java @@ -23,9 +23,9 @@ import java.util.UUID; import java.util.regex.Matcher; import java.util.regex.Pattern; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.GroupRest; import org.dspace.app.rest.utils.Utils; @@ -47,7 +47,7 @@ * This will be the entry point for the api/eperson/groups endpoint with additional paths to it */ @RestController -@RequestMapping("/api/" + GroupRest.CATEGORY + "/" + GroupRest.GROUPS) +@RequestMapping("/api/" + GroupRest.CATEGORY + "/" + GroupRest.PLURAL_NAME) public class GroupRestController { @Autowired diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/HarvesterMetadataController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/HarvesterMetadataController.java index dad1a2eb7511..a40629cae5a4 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/HarvesterMetadataController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/HarvesterMetadataController.java @@ -9,9 +9,9 @@ import java.util.List; import java.util.Map; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.dspace.app.rest.converter.ConverterService; import org.dspace.app.rest.link.HalLinkService; import org.dspace.app.rest.model.HarvesterMetadataRest; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ItemAddBundleController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ItemAddBundleController.java index 5175dec5e2e3..fd40b5cafd63 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ItemAddBundleController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ItemAddBundleController.java @@ -12,10 +12,10 @@ import java.io.IOException; import java.sql.SQLException; import java.util.UUID; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.dspace.app.rest.converter.ConverterService; import org.dspace.app.rest.converter.MetadataConverter; import org.dspace.app.rest.exception.UnprocessableEntityException; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ItemOwningCollectionUpdateRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ItemOwningCollectionUpdateRestController.java index b5a0c957f265..db238e1a5c83 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ItemOwningCollectionUpdateRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ItemOwningCollectionUpdateRestController.java @@ -14,9 +14,9 @@ import java.sql.SQLException; import java.util.List; 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.dspace.app.rest.converter.ConverterService; import org.dspace.app.rest.exception.DSpaceBadRequestException; import org.dspace.app.rest.exception.UnprocessableEntityException; @@ -43,7 +43,7 @@ import org.springframework.web.bind.annotation.RestController; /** - * This controller will handle all the incoming calls on the api/code/items/{uuid}/owningCollection endpoint + * This controller will handle all the incoming calls on the api/core/items/{uuid}/owningCollection endpoint * where the uuid corresponds to the item of which you want to edit the owning collection. */ @RestController diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ItemTemplateRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ItemTemplateRestController.java index a6dbf3496e49..5ebc567e600c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ItemTemplateRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ItemTemplateRestController.java @@ -12,9 +12,9 @@ import java.io.IOException; import java.sql.SQLException; import java.util.UUID; -import javax.servlet.http.HttpServletRequest; import com.fasterxml.jackson.databind.JsonNode; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.converter.ConverterService; import org.dspace.app.rest.exception.DSpaceBadRequestException; import org.dspace.app.rest.exception.UnprocessableEntityException; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/LDNInboxController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/LDNInboxController.java new file mode 100644 index 000000000000..f0ccbcf873c4 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/LDNInboxController.java @@ -0,0 +1,145 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.rest; + +import java.sql.SQLException; +import java.util.regex.Pattern; + +import jakarta.servlet.http.HttpServletRequest; +import org.apache.commons.validator.routines.UrlValidator; +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.model.Notification; +import org.dspace.app.ldn.service.LDNMessageService; +import org.dspace.app.rest.exception.DSpaceBadRequestException; +import org.dspace.app.rest.exception.InvalidLDNMessageException; +import org.dspace.core.Context; +import org.dspace.services.ConfigurationService; +import org.dspace.web.ContextUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.server.ResponseStatusException; + +@Controller +@RequestMapping("/ldn") +@ConditionalOnProperty("ldn.enabled") +public class LDNInboxController { + + private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(); + + @Autowired + private LDNRouter router; + + @Autowired + private LDNMessageService ldnMessageService; + + @Autowired + private ConfigurationService configurationService; + + /** + * LDN DSpace inbox. + * + * @param notification received notification + * @return ResponseEntity 400 not stored, 202 stored + * @throws Exception + */ + @PostMapping(value = "/inbox", consumes = "application/ld+json") + public ResponseEntity inbox(HttpServletRequest request, @RequestBody Notification notification) + throws Exception { + + Context context = ContextUtil.obtainCurrentRequestContext(); + validate(context, notification, request.getRemoteAddr()); + + LDNMessageEntity ldnMsgEntity = ldnMessageService.create(context, notification, request.getRemoteAddr()); + log.info("stored ldn message {}", ldnMsgEntity); + context.commit(); + + return ResponseEntity.accepted() + .body(String.format("Successfully stored notification %s %s", + notification.getId(), notification.getType())); + } + + /** + * LDN DSpace inbox options. + * + * @return ResponseEntity 200 with allow and accept-post headers + */ + @RequestMapping(value = "/inbox", method = RequestMethod.OPTIONS) + public ResponseEntity options() { + return ResponseEntity.ok() + .allow(HttpMethod.OPTIONS, HttpMethod.POST) + .header("Accept-Post", "application/ld+json") + .build(); + } + + /** + * @param e + * @return ResponseEntity + */ + @ExceptionHandler(ResponseStatusException.class) + public ResponseEntity handleResponseStatusException(ResponseStatusException e) { + return ResponseEntity.status(e.getStatusCode().value()) + .body(e.getMessage()); + } + + private void validate(Context context, Notification notification, String sourceIp) { + String id = notification.getId(); + Pattern URNRegex = + Pattern.compile("^urn:uuid:[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"); + + if (!URNRegex.matcher(id).matches() && !new UrlValidator().isValid(id)) { + throw new InvalidLDNMessageException("Invalid URI format for 'id' field."); + } + + if (notification.getOrigin() == null || notification.getTarget() == null || notification.getObject() == null) { + throw new InvalidLDNMessageException("Origin or Target or Object is missing"); + } + + if (configurationService.getBooleanProperty("ldn.notify.inbox.block-untrusted", true)) { + try { + NotifyServiceEntity originNotifyService = + ldnMessageService.findNotifyService(context, notification.getOrigin()); + if (originNotifyService == null) { + throw new DSpaceBadRequestException("Notify Service [" + notification.getOrigin() + + "] unknown. LDN message can not be received."); + } + } catch (SQLException sqle) { + throw new DSpaceBadRequestException("Notify Service [" + notification.getOrigin() + + "] unknown. LDN message can not be received."); + } + } + if (configurationService.getBooleanProperty("ldn.notify.inbox.block-untrusted-ip", true)) { + try { + NotifyServiceEntity originNotifyService = + ldnMessageService.findNotifyService(context, notification.getOrigin()); + if (originNotifyService == null) { + throw new DSpaceBadRequestException("Notify Service [" + notification.getOrigin() + + "] unknown. LDN message can not be received."); + } + boolean isValidIp = ldnMessageService.isValidIp(originNotifyService, sourceIp); + if (!isValidIp) { + throw new DSpaceBadRequestException("Source IP for Incoming LDN Message [" + notification.getId() + + "] out of its Notify Service IP Range. LDN message can not be received."); + } + } catch (SQLException sqle) { + throw new DSpaceBadRequestException("Notify Service [" + notification.getOrigin() + + "] unknown. LDN message can not be received."); + } + } + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/LDNMessageRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/LDNMessageRestController.java new file mode 100644 index 000000000000..92335cb5cc1e --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/LDNMessageRestController.java @@ -0,0 +1,94 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.rest; + +import static org.dspace.app.rest.utils.RegexUtils.REGEX_REQUESTMAPPING_IDENTIFIER_AS_URN_UUID; + +import java.sql.SQLException; +import java.util.List; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.dspace.app.ldn.LDNMessageEntity; +import org.dspace.app.ldn.service.LDNMessageService; +import org.dspace.app.rest.converter.ConverterService; +import org.dspace.app.rest.model.LDNMessageEntityRest; +import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.app.rest.utils.Utils; +import org.dspace.authorize.AuthorizeException; +import org.dspace.core.Context; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.hateoas.Link; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * Rest Controller for requesting the reprocessing of LDNMessageEntity + * + * @author Stefano Maffei (stefano.maffei at 4science.com) + */ +@RestController +@RequestMapping("/api/" + LDNMessageEntityRest.CATEGORY + "/" + + LDNMessageEntityRest.NAME_PLURALS + REGEX_REQUESTMAPPING_IDENTIFIER_AS_URN_UUID + "/enqueueretry") +public class LDNMessageRestController implements InitializingBean { + + private static final Logger log = LogManager.getLogger(LDNMessageRestController.class); + + @Autowired + private ConverterService converterService; + + @Autowired + private LDNMessageService ldnMessageService; + + @Autowired + private Utils utils; + + @Autowired + private DiscoverableEndpointsService discoverableEndpointsService; + + @Override + public void afterPropertiesSet() { + discoverableEndpointsService.register(this, + List.of(Link.of("/api/" + LDNMessageEntityRest.CATEGORY + "/" + + LDNMessageEntityRest.NAME_PLURALS + "/{id}/enqueueretry", + "enqueueretry"))); + } + + @PostMapping(produces = "application/json") + @PreAuthorize("hasAuthority('ADMIN')") + public ResponseEntity findByItem(@PathVariable("id") String id) + throws SQLException, AuthorizeException, JsonProcessingException { + + Context context = ContextUtil.obtainCurrentRequestContext(); + LDNMessageEntity ldnMessageEntity = ldnMessageService.find(context, id); + if (ldnMessageEntity == null) { + throw new ResourceNotFoundException("No such item: " + id); + } + ldnMessageEntity.setQueueStatus(LDNMessageEntity.QUEUE_STATUS_QUEUED_FOR_RETRY); + ldnMessageService.update(context, ldnMessageEntity); + + LDNMessageEntityRest resultRequestStatusRests = converterService.toRest( + ldnMessageEntity, utils.obtainProjection()); + + context.complete(); + String result = new ObjectMapper() + .writerWithDefaultPrettyPrinter().writeValueAsString(resultRequestStatusRests); + + return new ResponseEntity<>(result, HttpStatus.OK); + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/MappedCollectionRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/MappedCollectionRestController.java index 14dae21ebec0..6f886096bd30 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/MappedCollectionRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/MappedCollectionRestController.java @@ -14,9 +14,9 @@ import java.sql.SQLException; import java.util.List; 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.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.app.rest.exception.MethodNotAllowedException; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/NotifyRequestStatusRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/NotifyRequestStatusRestController.java new file mode 100644 index 000000000000..0c599b03e852 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/NotifyRequestStatusRestController.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.app.rest; + +import static org.dspace.app.rest.utils.RegexUtils.REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID; + +import java.sql.SQLException; +import java.util.List; +import java.util.UUID; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.dspace.app.ldn.model.NotifyRequestStatus; +import org.dspace.app.ldn.service.LDNMessageService; +import org.dspace.app.rest.converter.ConverterService; +import org.dspace.app.rest.model.NotifyRequestStatusRest; +import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.app.rest.utils.Utils; +import org.dspace.authorize.AuthorizeException; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.content.Item; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.hateoas.Link; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + + +/** + * Rest Controller for NotifyRequestStatus targeting items + * + * @author Francesco Bacchelli (francesco.bacchelli at 4science dot it) + */ +@RestController +@RequestMapping("/api/" + NotifyRequestStatusRest.CATEGORY + "/" + NotifyRequestStatusRest.PLURAL_NAME + + REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID) +public class NotifyRequestStatusRestController implements InitializingBean { + + private static final Logger log = LogManager.getLogger(NotifyRequestStatusRestController.class); + + @Autowired + private ConverterService converterService; + + @Autowired + private Utils utils; + + @Autowired + private LDNMessageService ldnMessageService; + + @Autowired + private ItemService itemService; + + @Autowired + private AuthorizeService authorizeService; + + @Autowired + private DiscoverableEndpointsService discoverableEndpointsService; + + @Override + public void afterPropertiesSet() { + discoverableEndpointsService.register(this, + List.of(Link.of("/api/" + NotifyRequestStatusRest.CATEGORY + "/" + NotifyRequestStatusRest.NAME, + NotifyRequestStatusRest.NAME))); + } + + @GetMapping(produces = "application/json") + @PreAuthorize("hasAuthority('AUTHENTICATED')") + public ResponseEntity findByItem(@PathVariable UUID uuid) + throws SQLException, AuthorizeException, JsonProcessingException { + + Context context = ContextUtil.obtainCurrentRequestContext(); + Item item = itemService.find(context, uuid); + if (item == null) { + throw new ResourceNotFoundException("No such item: " + uuid); + } + EPerson currentUser = context.getCurrentUser(); + if (!currentUser.equals(item.getSubmitter()) && !authorizeService.isAdmin(context)) { + throw new AuthorizeException("User unauthorized"); + } + NotifyRequestStatus resultRequests = ldnMessageService.findRequestsByItem(context, item); + NotifyRequestStatusRest resultRequestStatusRests = converterService.toRest( + resultRequests, utils.obtainProjection()); + + context.complete(); + String result = new ObjectMapper() + .writerWithDefaultPrettyPrinter().writeValueAsString(resultRequestStatusRests); + + return new ResponseEntity<>(result, HttpStatus.OK); + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/OidcRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/OidcRestController.java index db04b3b7cd95..eaba79d1a8b2 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/OidcRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/OidcRestController.java @@ -10,15 +10,15 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; -import javax.annotation.PostConstruct; -import javax.servlet.http.HttpServletResponse; +import jakarta.annotation.PostConstruct; +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.app.rest.model.AuthnRest; import org.dspace.core.Utils; import org.dspace.services.ConfigurationService; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.hateoas.Link; import org.springframework.web.bind.annotation.RequestMapping; @@ -27,7 +27,7 @@ import org.springframework.web.bind.annotation.RestController; /** - * Rest controller that handles redirect after OIDC authentication succeded. + * Rest controller that handles redirect after OIDC authentication succeeded. * * @author Luca Giamminonni (luca.giamminonni at 4science.it) */ @@ -35,7 +35,7 @@ @RequestMapping(value = "/api/" + AuthnRest.CATEGORY + "/oidc") public class OidcRestController { - private static final Logger log = LoggerFactory.getLogger(OidcRestController.class); + private static final Logger log = LogManager.getLogger(); @Autowired private ConfigurationService configurationService; @@ -66,11 +66,12 @@ public void oidc(HttpServletResponse response, } if (StringUtils.equalsAnyIgnoreCase(redirectHostName, allowedHostNames.toArray(new String[0]))) { - log.debug("OIDC redirecting to " + redirectUrl); + log.debug("OIDC redirecting to {}", redirectUrl); response.sendRedirect(redirectUrl); // lgtm [java/unvalidated-url-redirection] } else { - log.error("Invalid OIDC redirectURL=" + redirectUrl + - ". URL doesn't match hostname of server or UI!"); + log.error("Invalid OIDC redirectURL={}." + + " URL doesn't match hostname of server or UI!", + redirectUrl); response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid redirectURL! Must match server or ui hostname."); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/OpenSearchController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/OpenSearchController.java index 665504139cb3..6bc7034b5b09 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/OpenSearchController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/OpenSearchController.java @@ -9,22 +9,19 @@ import java.io.IOException; import java.util.ArrayList; -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.transform.Transformer; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.apache.logging.log4j.Logger; import org.dspace.app.rest.utils.ContextUtil; import org.dspace.app.rest.utils.ScopeResolver; -import org.dspace.app.util.SyndicationFeed; import org.dspace.app.util.factory.UtilServiceFactory; import org.dspace.app.util.service.OpenSearchService; import org.dspace.authorize.factory.AuthorizeServiceFactory; @@ -200,12 +197,10 @@ public void search(HttpServletRequest request, log.info("opensearch done, query=\"" + query + "\",results=" + qResults.getTotalSearchResults()); - // format and return results - Map labelMap = getLabels(request); List dsoResults = qResults.getIndexableObjects(); Document resultsDoc = openSearchService.getResultsDoc(context, format, query, (int) qResults.getTotalSearchResults(), qResults.getStart(), - qResults.getMaxResults(), container, dsoResults, labelMap); + qResults.getMaxResults(), container, dsoResults); try { Transformer xf = TransformerFactory.newInstance().newTransformer(); response.setContentType(openSearchService.getContentType(format)); @@ -274,21 +269,4 @@ private void init() { public void setOpenSearchService(OpenSearchService oSS) { openSearchService = oSS; } - - - /** - * Internal method to get labels for the returned document - */ - private Map getLabels(HttpServletRequest request) { - // TODO: get strings from translation file or configuration - Map labelMap = new HashMap(); - labelMap.put(SyndicationFeed.MSG_UNTITLED, "notitle"); - labelMap.put(SyndicationFeed.MSG_LOGO_TITLE, "logo.title"); - labelMap.put(SyndicationFeed.MSG_FEED_DESCRIPTION, "general-feed.description"); - labelMap.put(SyndicationFeed.MSG_UITYPE, SyndicationFeed.UITYPE_JSPUI); - for (String selector : SyndicationFeed.getDescriptionSelectors()) { - labelMap.put("metadata." + selector, selector); - } - return labelMap; - } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/PrimaryBitstreamController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/PrimaryBitstreamController.java index c236954dab48..d978752b7d4c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/PrimaryBitstreamController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/PrimaryBitstreamController.java @@ -12,8 +12,8 @@ import java.util.List; import java.util.UUID; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.converter.ConverterService; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.BundleRest; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/QAEventRelatedRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/QAEventRelatedRestController.java index 538d99b3ceb5..b3afdf7945ca 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/QAEventRelatedRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/QAEventRelatedRestController.java @@ -47,8 +47,8 @@ * "/api/integration/qualityassuranceevents/{qaeventid}/related" */ @RestController -@RequestMapping("/api/" + QAEventRest.CATEGORY + "/qualityassuranceevents" - + REGEX_REQUESTMAPPING_IDENTIFIER_AS_STRING_VERSION_STRONG + "/related") +@RequestMapping("/api/" + QAEventRest.CATEGORY + "/" + QAEventRest.PLURAL_NAME + + REGEX_REQUESTMAPPING_IDENTIFIER_AS_STRING_VERSION_STRONG + "/" + QAEventRest.RELATED) public class QAEventRelatedRestController { @Autowired @@ -78,7 +78,6 @@ public ResponseEntity> addRelatedItem(@PathVariable(name @RequestParam(name = "item") UUID relatedItemUUID) throws SQLException, AuthorizeException { Context context = ContextUtil.obtainCurrentRequestContext(); - QAEvent qaevent = qaEventService.findEventByEventId(qaeventId); if (qaevent == null) { throw new ResourceNotFoundException("No such qa event: " + qaeventId); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/RelationshipRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/RelationshipRestController.java index 4959d5ac75ee..b9c940f1c107 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/RelationshipRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/RelationshipRestController.java @@ -10,9 +10,9 @@ import static org.dspace.app.rest.utils.RegexUtils.REGEX_REQUESTMAPPING_IDENTIFIER_AS_DIGIT; import java.sql.SQLException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.dspace.app.rest.model.RelationshipRest; import org.dspace.app.rest.repository.RelationshipRestRepository; import org.dspace.app.rest.utils.ContextUtil; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ResourcePolicyEPersonReplaceRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ResourcePolicyEPersonReplaceRestController.java index e772aa0abe18..3311f303ade6 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ResourcePolicyEPersonReplaceRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ResourcePolicyEPersonReplaceRestController.java @@ -15,9 +15,9 @@ import java.sql.SQLException; import java.util.List; import java.util.Objects; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.utils.Utils; import org.dspace.authorize.AuthorizeException; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ResourcePolicyGroupReplaceRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ResourcePolicyGroupReplaceRestController.java index e9ba0dff4429..2a041aba3a0a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ResourcePolicyGroupReplaceRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ResourcePolicyGroupReplaceRestController.java @@ -15,9 +15,9 @@ import java.sql.SQLException; import java.util.List; import java.util.Objects; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.utils.Utils; import org.dspace.authorize.AuthorizeException; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/RestResourceController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/RestResourceController.java index 03bea35597ba..772096e705a5 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/RestResourceController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/RestResourceController.java @@ -23,15 +23,14 @@ import java.util.List; import java.util.Optional; import java.util.UUID; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +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.Logger; -import org.atteo.evo.inflector.English; import org.dspace.app.rest.converter.ConverterService; import org.dspace.app.rest.converter.JsonPatchConverter; import org.dspace.app.rest.exception.DSpaceBadRequestException; @@ -56,6 +55,7 @@ import org.dspace.app.rest.utils.Utils; import org.dspace.authorize.AuthorizeException; import org.dspace.util.UUIDUtils; +import org.springframework.aop.AopInvocationException; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; @@ -126,7 +126,7 @@ public void afterPropertiesSet() { // see https://github.com/spring-projects/spring-hateoas/issues/408 // Link l = linkTo(this.getClass(), r).withRel(r); String[] split = r.split("\\.", 2); - String plural = English.plural(split[1]); + String plural = split[1]; Link l = Link.of("/api/" + split[0] + "/" + plural, plural); links.add(l); log.debug(l.getRel().value() + " " + l.getHref()); @@ -220,8 +220,10 @@ private HALResource findOneInter Optional modelObject = Optional.empty(); try { modelObject = repository.findById(id); - } catch (ClassCastException e) { - // ignore, as handled below + } catch (ClassCastException | IllegalArgumentException | AopInvocationException e) { + // These exceptions may be thrown if the "id" param above is not valid for DSpaceRestRepository.findById() + // (e.g. passing an Integer param when a UUID is expected, or similar). + // We can safely ignore these exceptions as they simply mean the object was not found (see below). } if (!modelObject.isPresent()) { throw new ResourceNotFoundException(apiCategory + "." + model + " with id: " + id + " not found"); @@ -371,7 +373,8 @@ public RepresentationModel findRel(HttpServletRequest request, HttpServletRespon * @return The relevant ResponseEntity for this request * @throws HttpRequestMethodNotSupportedException If something goes wrong */ - @RequestMapping(method = RequestMethod.POST, consumes = {"application/json", "application/hal+json"}) + @RequestMapping(method = RequestMethod.POST, value = {"", "/"}, + consumes = {"application/json", "application/hal+json"}) public ResponseEntity> post(HttpServletRequest request, @PathVariable String apiCategory, @PathVariable String model, @@ -398,7 +401,7 @@ public ResponseEntity> post(HttpServletRequest request, * @return The relevant ResponseEntity for this request * @throws HttpRequestMethodNotSupportedException If something goes wrong */ - @RequestMapping(method = RequestMethod.POST, consumes = {"text/uri-list"}) + @RequestMapping(method = RequestMethod.POST, value = {"", "/"}, consumes = {"text/uri-list"}) public ResponseEntity> postWithUriListContentType(HttpServletRequest request, @PathVariable String apiCategory, @PathVariable String model) @@ -651,7 +654,7 @@ private ResponseEntity> uploadI * @throws IOException * @throws AuthorizeException */ - @RequestMapping(method = { RequestMethod.POST }, headers = "content-type=multipart/form-data") + @RequestMapping(method = { RequestMethod.POST }, value = {"", "/"}, headers = "content-type=multipart/form-data") public ResponseEntity> upload( HttpServletRequest request, @PathVariable String apiCategory, @@ -948,7 +951,7 @@ private RepresentationModel findRelInternal(HttpServle int start = Math.toIntExact(page.getOffset()); int end = (start + page.getPageSize()) > fullList.size() ? fullList.size() : (start + page.getPageSize()); DSpaceRestRepository resourceRepository = utils - .getResourceRepository(fullList.get(0).getCategory(), fullList.get(0).getType()); + .getResourceRepository(fullList.get(0).getCategory(), fullList.get(0).getTypePlural()); PageImpl pageResult = new PageImpl(fullList.subList(start, end), page, fullList.size()); return assembler.toModel(pageResult.map(converter::toResource)); @@ -962,7 +965,8 @@ private RepresentationModel findRelInternal(HttpServle } /** - * Find all + * Find all via a GET request to the root endpoint. This method will trigger in cases where the called endpoint + * either includes a trailing slash or not. * * @param apiCategory * @param model @@ -970,7 +974,7 @@ private RepresentationModel findRelInternal(HttpServle * @param assembler * @return */ - @RequestMapping(method = RequestMethod.GET) + @RequestMapping(method = RequestMethod.GET, value = {"", "/"}) @SuppressWarnings("unchecked") public PagedModel> findAll(@PathVariable String apiCategory, @PathVariable String model, Pageable page, PagedResourcesAssembler assembler, HttpServletResponse response, diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/RootRestResourceController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/RootRestResourceController.java index 7cb1854af88a..d6ed84c3d656 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/RootRestResourceController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/RootRestResourceController.java @@ -7,8 +7,7 @@ */ package org.dspace.app.rest; -import javax.servlet.http.HttpServletRequest; - +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.converter.ConverterService; import org.dspace.app.rest.link.HalLinkService; import org.dspace.app.rest.model.hateoas.RootResource; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/SitemapRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/SitemapRestController.java index efc79f2a395d..95d0ba772959 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/SitemapRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/SitemapRestController.java @@ -13,9 +13,9 @@ import java.io.InputStream; import java.nio.file.Files; import java.sql.SQLException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.apache.catalina.connector.ClientAbortException; import org.apache.logging.log4j.Logger; import org.dspace.app.rest.utils.ContextUtil; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/SubmissionCCLicenseUrlRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/SubmissionCCLicenseUrlRepository.java index c32d551cbee3..6daac9be8cf0 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/SubmissionCCLicenseUrlRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/SubmissionCCLicenseUrlRepository.java @@ -10,8 +10,8 @@ import java.util.Arrays; import java.util.HashMap; import java.util.Map; -import javax.servlet.ServletRequest; +import jakarta.servlet.ServletRequest; import org.apache.commons.lang3.StringUtils; import org.dspace.app.rest.converter.ConverterService; import org.dspace.app.rest.exception.DSpaceBadRequestException; @@ -38,7 +38,7 @@ * It only supports a search method */ -@Component(SubmissionCCLicenseUrlRest.CATEGORY + "." + SubmissionCCLicenseUrlRest.NAME) +@Component(SubmissionCCLicenseUrlRest.CATEGORY + "." + SubmissionCCLicenseUrlRest.PLURAL_NAME) public class SubmissionCCLicenseUrlRepository extends DSpaceRestRepository implements InitializingBean { @@ -133,8 +133,8 @@ public Class getDomainClass() { public void afterPropertiesSet() { discoverableEndpointsService.register(this, Arrays.asList( Link.of("/api/" + SubmissionCCLicenseUrlRest.CATEGORY + "/" + - SubmissionCCLicenseUrlRest.PLURAL + "/search", - SubmissionCCLicenseUrlRest.PLURAL + "-search"))); + SubmissionCCLicenseUrlRest.PLURAL_NAME + "/search", + SubmissionCCLicenseUrlRest.PLURAL_NAME + "-search"))); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/UUIDLookupRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/UUIDLookupRestController.java index 40c0a79b97be..3da84cc2d372 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/UUIDLookupRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/UUIDLookupRestController.java @@ -14,9 +14,9 @@ import java.sql.SQLException; import java.util.Arrays; 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.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.app.rest.converter.ConverterService; @@ -24,9 +24,11 @@ import org.dspace.app.rest.utils.ContextUtil; import org.dspace.app.rest.utils.DSpaceObjectUtils; import org.dspace.app.rest.utils.Utils; +import org.dspace.authorize.AuthorizeException; +import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.DSpaceObject; +import org.dspace.core.Constants; import org.dspace.core.Context; -import org.dspace.discovery.SearchServiceException; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.hateoas.Link; @@ -65,6 +67,9 @@ public class UUIDLookupRestController implements InitializingBean { @Autowired private DiscoverableEndpointsService discoverableEndpointsService; + @Autowired + private AuthorizeService authorizeService; + @Autowired private ConverterService converter; @@ -85,13 +90,14 @@ public void afterPropertiesSet() throws Exception { public void getDSObyIdentifier(HttpServletRequest request, HttpServletResponse response, @RequestParam(PARAM) UUID uuid) - throws IOException, SQLException, SearchServiceException { + throws IOException, SQLException, AuthorizeException { Context context = null; try { context = ContextUtil.obtainContext(request); DSpaceObject dso = dspaceObjectUtil.findDSpaceObject(context, uuid); if (dso != null) { + authorizeService.authorizeAction(context, dso, Constants.READ); DSpaceObjectRest dsor = converter.toRest(dso, utils.obtainProjection()); URI link = linkTo(dsor.getController(), dsor.getCategory(), dsor.getTypePlural()).slash(dsor.getId()) .toUri(); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/WebApplication.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/WebApplication.java index 401ad626e305..4c835d99183c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/WebApplication.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/WebApplication.java @@ -10,8 +10,10 @@ import java.io.IOException; import java.sql.SQLException; import java.util.List; -import javax.servlet.Filter; +import jakarta.servlet.Filter; +import org.dspace.app.ldn.LDNQueueExtractor; +import org.dspace.app.ldn.LDNQueueTimeoutChecker; import org.dspace.app.rest.filter.DSpaceRequestContextFilter; import org.dspace.app.rest.model.hateoas.DSpaceLinkRelationProvider; import org.dspace.app.rest.parameter.resolver.SearchFilterResolver; @@ -22,8 +24,6 @@ import org.dspace.app.util.DSpaceContextListener; import org.dspace.google.GoogleAsyncEventListener; import org.dspace.utils.servlet.DSpaceWebappServletFilter; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; @@ -53,8 +53,6 @@ @Configuration public class WebApplication { - private static final Logger log = LoggerFactory.getLogger(WebApplication.class); - @Autowired private ApplicationConfig configuration; @@ -66,6 +64,22 @@ public void generateSitemap() throws IOException, SQLException { GenerateSitemaps.generateSitemapsScheduled(); } + @Scheduled(cron = "${ldn.queue.extractor.cron:-}") + public void ldnExtractFromQueue() throws IOException, SQLException { + if (!configuration.getLdnEnabled()) { + return; + } + LDNQueueExtractor.extractMessageFromQueue(); + } + + @Scheduled(cron = "${ldn.queue.timeout.checker.cron:-}") + public void ldnQueueTimeoutCheck() throws IOException, SQLException { + if (!configuration.getLdnEnabled()) { + return; + } + LDNQueueTimeoutChecker.checkQueueMessageTimeout(); + } + @Scheduled(cron = "${solr-database-resync.cron:-}") public void solrDatabaseResync() throws Exception { SolrDatabaseResyncCli.runScheduled(); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/WorkflowDefinitionCollectionsLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/WorkflowDefinitionCollectionsLinkRepository.java index fd1192e0bb51..549a5ec78e93 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/WorkflowDefinitionCollectionsLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/WorkflowDefinitionCollectionsLinkRepository.java @@ -9,9 +9,9 @@ import java.util.ArrayList; import java.util.List; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; +import jakarta.annotation.Nullable; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.model.CollectionRest; import org.dspace.app.rest.model.WorkflowDefinitionRest; import org.dspace.app.rest.projection.Projection; @@ -33,7 +33,7 @@ * * @author Maria Verdonck (Atmire) on 11/12/2019 */ -@Component(WorkflowDefinitionRest.CATEGORY + "." + WorkflowDefinitionRest.NAME + "." +@Component(WorkflowDefinitionRest.CATEGORY + "." + WorkflowDefinitionRest.PLURAL_NAME + "." + WorkflowDefinitionRest.COLLECTIONS_MAPPED_TO) public class WorkflowDefinitionCollectionsLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/WorkflowDefinitionStepsLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/WorkflowDefinitionStepsLinkRepository.java index 24c82ee460e2..91cb0cffc0f4 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/WorkflowDefinitionStepsLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/WorkflowDefinitionStepsLinkRepository.java @@ -8,9 +8,9 @@ package org.dspace.app.rest; import java.util.List; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; +import jakarta.annotation.Nullable; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.model.WorkflowDefinitionRest; import org.dspace.app.rest.model.WorkflowStepRest; import org.dspace.app.rest.projection.Projection; @@ -32,7 +32,7 @@ * * @author Maria Verdonck (Atmire) on 24/02/2020 */ -@Component(WorkflowDefinitionRest.CATEGORY + "." + WorkflowDefinitionRest.NAME + "." +@Component(WorkflowDefinitionRest.CATEGORY + "." + WorkflowDefinitionRest.PLURAL_NAME + "." + WorkflowDefinitionRest.STEPS) public class WorkflowDefinitionStepsLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/WorkflowStepActionsLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/WorkflowStepActionsLinkRepository.java index f2b6a423f88f..0740d318a056 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/WorkflowStepActionsLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/WorkflowStepActionsLinkRepository.java @@ -8,9 +8,9 @@ package org.dspace.app.rest; import java.util.List; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; +import jakarta.annotation.Nullable; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.model.WorkflowActionRest; import org.dspace.app.rest.model.WorkflowStepRest; import org.dspace.app.rest.projection.Projection; @@ -30,7 +30,7 @@ * * @author Maria Verdonck (Atmire) on 24/02/2020 */ -@Component(WorkflowStepRest.CATEGORY + "." + WorkflowStepRest.NAME + "." +@Component(WorkflowStepRest.CATEGORY + "." + WorkflowStepRest.PLURAL_NAME + "." + WorkflowStepRest.ACTIONS) public class WorkflowStepActionsLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/AuthorizationRestUtil.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/AuthorizationRestUtil.java index c8a9d2435bb5..d2cf2fe364da 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/AuthorizationRestUtil.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/AuthorizationRestUtil.java @@ -12,6 +12,7 @@ import java.util.UUID; import org.apache.commons.lang3.StringUtils; +import org.atteo.evo.inflector.English; import org.dspace.app.rest.model.BaseObjectRest; import org.dspace.app.rest.repository.DSpaceRestRepository; import org.dspace.app.rest.repository.ReloadableEntityObjectRepository; @@ -66,7 +67,8 @@ public BaseObjectRest getObject(Context context, String id) throws SQLException String[] objType; try { objType = parts[2].split("\\."); - DSpaceRestRepository repository = utils.getResourceRepositoryByCategoryAndModel(objType[0], objType[1]); + DSpaceRestRepository repository = utils + .getResourceRepositoryByCategoryAndModel(objType[0], English.plural(objType[1])); Serializable pk = utils.castToPKClass((ReloadableEntityObjectRepository) repository, objIdStr); try { // disable the security as we only need to retrieve the object to further process the authorization @@ -141,7 +143,7 @@ private String[] splitIdParts(String id) { objId = idParts[2]; } else { throw new IllegalArgumentException( - "the authoization id is invalid, it must have the form " + + "the authorization id is invalid, it must have the form " + "[eperson-uuid_]feature-id_object-type_object-id"); } return new String[] { eperson, feature, objType, objId }; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanClaimItemFeature.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanClaimItemFeature.java index 9642bb4a2d61..aed82ba866c2 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanClaimItemFeature.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanClaimItemFeature.java @@ -11,6 +11,8 @@ import java.util.UUID; import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.app.rest.authorization.AuthorizationFeature; import org.dspace.app.rest.authorization.AuthorizationFeatureDocumentation; import org.dspace.app.rest.model.BaseObjectRest; @@ -22,8 +24,6 @@ import org.dspace.core.Context; import org.dspace.eperson.EPerson; import org.dspace.profile.service.ResearcherProfileService; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -43,7 +43,7 @@ public class CanClaimItemFeature implements AuthorizationFeature { public static final String NAME = "canClaimItem"; - private static final Logger LOG = LoggerFactory.getLogger(CanClaimItemFeature.class); + private static final Logger LOG = LogManager.getLogger(); @Autowired private ItemService itemService; @@ -72,7 +72,8 @@ private boolean hasNotAlreadyAProfile(Context context) { try { return researcherProfileService.findById(context, context.getCurrentUser().getID()) == null; } catch (SQLException | AuthorizeException e) { - LOG.warn("Error while checking if eperson has a ResearcherProfileAssociated: {}", e.getMessage(), e); + LOG.warn("Error while checking if eperson has a ResearcherProfileAssociated: {}", + e.getMessage(), e); return false; } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanDeleteVersionFeature.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanDeleteVersionFeature.java index c20668fcc551..15438a5e9aa4 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanDeleteVersionFeature.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanDeleteVersionFeature.java @@ -15,7 +15,6 @@ import org.dspace.app.rest.model.ItemRest; import org.dspace.app.rest.model.VersionRest; import org.dspace.app.rest.projection.DefaultProjection; -import org.dspace.content.service.ItemService; import org.dspace.core.Context; import org.dspace.services.ConfigurationService; import org.dspace.versioning.Version; @@ -34,8 +33,6 @@ description = "It can be used to verify if the user can delete a version of an Item") public class CanDeleteVersionFeature extends DeleteFeature { - @Autowired - private ItemService itemService; @Autowired private ItemConverter itemConverter; @Autowired diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CoarNotifyEnabled.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CoarNotifyEnabled.java new file mode 100644 index 000000000000..5ef8410e08ac --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CoarNotifyEnabled.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.app.rest.authorization.impl; + +import java.sql.SQLException; + +import org.dspace.app.rest.authorization.AuthorizationFeature; +import org.dspace.app.rest.authorization.AuthorizationFeatureDocumentation; +import org.dspace.app.rest.model.BaseObjectRest; +import org.dspace.app.rest.model.SiteRest; +import org.dspace.core.Context; +import org.dspace.discovery.SearchServiceException; +import org.dspace.services.ConfigurationService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +@AuthorizationFeatureDocumentation(name = CoarNotifyEnabled.NAME, + description = "It can be used to verify if the user can see the coar notify protocol is enabled") +public class CoarNotifyEnabled implements AuthorizationFeature { + + public final static String NAME = "coarNotifyEnabled"; + + @Autowired + private ConfigurationService configurationService; + + @Override + public boolean isAuthorized(Context context, BaseObjectRest object) throws SQLException, SearchServiceException { + return configurationService.getBooleanProperty("ldn.enabled", true); + } + + @Override + public String[] getSupportedTypes() { + return new String[]{ SiteRest.CATEGORY + "." + SiteRest.NAME }; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/DeleteFeature.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/DeleteFeature.java index 02ca816290d0..0f5378125d8f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/DeleteFeature.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/DeleteFeature.java @@ -65,11 +65,13 @@ public class DeleteFeature implements AuthorizationFeature { @Override public boolean isAuthorized(Context context, BaseObjectRest object) throws SQLException { if (object instanceof BaseObjectRest) { + DSpaceObject dSpaceObject = (DSpaceObject) utils.getDSpaceAPIObjectFromRest(context, object); + if (object.getType().equals(WorkspaceItemRest.NAME)) { - object = ((WorkspaceItemRest)object).getItem(); + WorkspaceItem workspaceItem = (WorkspaceItem) utils.getDSpaceAPIObjectFromRest(context, object); + dSpaceObject = workspaceItem.getItem(); } - DSpaceObject dSpaceObject = (DSpaceObject) utils.getDSpaceAPIObjectFromRest(context, object); DSpaceObject parentObject = getParentObject(context, dSpaceObject); switch (object.getType()) { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/configuration/ActuatorConfiguration.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/configuration/ActuatorConfiguration.java index ad78fe2db40b..08a7e9aec8e9 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/configuration/ActuatorConfiguration.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/configuration/ActuatorConfiguration.java @@ -14,6 +14,7 @@ import org.apache.solr.client.solrj.SolrServerException; import org.dspace.app.rest.DiscoverableEndpointsService; import org.dspace.app.rest.health.GeoIpHealthIndicator; +import org.dspace.app.rest.health.SolrHealthIndicator; import org.dspace.authority.AuthoritySolrServiceImpl; import org.dspace.discovery.SolrSearchCore; import org.dspace.statistics.SolrStatisticsCore; @@ -22,7 +23,6 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.actuate.autoconfigure.health.ConditionalOnEnabledHealthIndicator; import org.springframework.boot.actuate.health.Status; -import org.springframework.boot.actuate.solr.SolrHealthIndicator; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.annotation.Bean; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AInprogressItemConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AInprogressItemConverter.java index a80c8bd948b9..d064c74442ca 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AInprogressItemConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AInprogressItemConverter.java @@ -70,11 +70,6 @@ protected void fillFromModel(T obj, R witem, Projection projection) { submitter = obj.getSubmitter(); witem.setId(obj.getID()); - witem.setCollection(collection != null ? converter.toRest(collection, projection) : null); - witem.setItem(converter.toRest(item, projection)); - if (submitter != null) { - witem.setSubmitter(converter.toRest(submitter, projection)); - } // 1. retrieve the submission definition // 2. iterate over the submission section to allow to plugin additional diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/CollectionConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/CollectionConverter.java index e9b6aa03b85a..8cae60eca39e 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/CollectionConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/CollectionConverter.java @@ -9,8 +9,11 @@ import org.dspace.app.rest.model.CollectionRest; import org.dspace.app.rest.projection.Projection; +import org.dspace.app.rest.utils.ContextUtil; import org.dspace.content.Collection; +import org.dspace.content.service.CollectionService; import org.dspace.discovery.IndexableObject; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; /** @@ -23,10 +26,14 @@ public class CollectionConverter extends DSpaceObjectConverter implements IndexableObjectConverter { + @Autowired + CollectionService collectionService; + @Override public CollectionRest convert(Collection collection, Projection projection) { CollectionRest resource = super.convert(collection, projection); - resource.setArchivedItemsCount(collection.countArchivedItems()); + resource.setArchivedItemsCount( + collectionService.countArchivedItems(ContextUtil.obtainCurrentRequestContext(), collection)); return resource; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/CommunityConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/CommunityConverter.java index a90ad3cfe644..62062e08139c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/CommunityConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/CommunityConverter.java @@ -9,8 +9,11 @@ import org.dspace.app.rest.model.CommunityRest; import org.dspace.app.rest.projection.Projection; +import org.dspace.app.rest.utils.ContextUtil; import org.dspace.content.Community; +import org.dspace.content.service.CommunityService; import org.dspace.discovery.IndexableObject; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; /** @@ -24,9 +27,14 @@ public class CommunityConverter extends DSpaceObjectConverter implements IndexableObjectConverter { + @Autowired + CommunityService communityService; + public CommunityRest convert(Community community, Projection projection) { CommunityRest resource = super.convert(community, projection); - resource.setArchivedItemsCount(community.countArchivedItems()); + + resource.setArchivedItemsCount( + communityService.countArchivedItems(ContextUtil.obtainCurrentRequestContext(), community)); return resource; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ConverterService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ConverterService.java index e83790495146..abb79d57984c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ConverterService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ConverterService.java @@ -16,9 +16,9 @@ import java.util.List; import java.util.Map; import java.util.Set; -import javax.annotation.Nullable; -import javax.annotation.PostConstruct; +import jakarta.annotation.Nullable; +import jakarta.annotation.PostConstruct; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -151,7 +151,7 @@ private String parseAnnotation(Annotation preAuthorize) { private Annotation getAnnotationForRestObject(BaseObjectRest restObject) { BaseObjectRest baseObjectRest = restObject; DSpaceRestRepository repositoryToUse = utils - .getResourceRepositoryByCategoryAndModel(baseObjectRest.getCategory(), baseObjectRest.getType()); + .getResourceRepositoryByCategoryAndModel(baseObjectRest.getCategory(), baseObjectRest.getTypePlural()); Annotation preAuthorize = null; int maxDepth = 0; // DS-4530 exclude the AOP Proxy from determining the annotations diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/CorrectionTypeConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/CorrectionTypeConverter.java new file mode 100644 index 000000000000..58330fdfae90 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/CorrectionTypeConverter.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.rest.converter; + +import org.dspace.app.rest.model.CorrectionTypeRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.correctiontype.CorrectionType; +import org.springframework.stereotype.Component; + +/** + * This class provides the method to convert a CorrectionType to its REST representation, the + * CorrectionTypeRest + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + */ +@Component +public class CorrectionTypeConverter implements DSpaceConverter { + + @Override + public CorrectionTypeRest convert(CorrectionType target, Projection projection) { + CorrectionTypeRest targetRest = new CorrectionTypeRest(); + targetRest.setProjection(projection); + targetRest.setId(target.getId()); + targetRest.setTopic(target.getTopic()); + return targetRest; + } + + @Override + public Class getModelClass() { + return CorrectionType.class; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/FilteredItemConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/FilteredItemConverter.java new file mode 100644 index 000000000000..9d42688eb3dc --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/FilteredItemConverter.java @@ -0,0 +1,125 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.rest.converter; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.Logger; +import org.dspace.app.rest.model.FilteredItemRest; +import org.dspace.app.rest.model.MetadataValueList; +import org.dspace.app.rest.projection.Projection; +import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.app.util.service.MetadataExposureService; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.content.Item; +import org.dspace.content.MetadataField; +import org.dspace.content.MetadataValue; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +/** + * This is the converter from/to the Item in the DSpace API data model and the + * REST data model + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + */ +@Component +public class FilteredItemConverter { + + // Must be loaded @Lazy, as ConverterService autowires all DSpaceConverter components + @Lazy + @Autowired + ConverterService converter; + @Autowired + private ItemService itemService; + @Autowired + private CollectionConverter collectionConverter; + @Autowired + AuthorizeService authorizeService; + @Autowired + MetadataExposureService metadataExposureService; + + private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(FilteredItemConverter.class); + + public FilteredItemRest convert(Item obj, Projection projection) { + FilteredItemRest item = new FilteredItemRest(); + + item.setHandle(obj.getHandle()); + if (obj.getID() != null) { + item.setUuid(obj.getID().toString()); + } + item.setName(obj.getName()); + + MetadataValueList metadataValues = getPermissionFilteredMetadata( + ContextUtil.obtainCurrentRequestContext(), obj); + item.setMetadata(converter.toRest(metadataValues, projection)); + + item.setInArchive(obj.isArchived()); + item.setDiscoverable(obj.isDiscoverable()); + item.setWithdrawn(obj.isWithdrawn()); + item.setLastModified(obj.getLastModified()); + + List entityTypes = + itemService.getMetadata(obj, "dspace", "entity", "type", Item.ANY, false); + if (CollectionUtils.isNotEmpty(entityTypes) && StringUtils.isNotBlank(entityTypes.get(0).getValue())) { + item.setEntityType(entityTypes.get(0).getValue()); + } + + Optional.ofNullable(obj.getOwningCollection()) + .map(coll -> collectionConverter.convert(coll, Projection.DEFAULT)) + .ifPresent(item::setOwningCollection); + + return item; + } + + /** + * Retrieves the metadata list filtered according to the hidden metadata configuration + * When the context is null, it will return the metadatalist as for an anonymous user + * Overrides the parent method to include virtual metadata + * @param context The context + * @param obj The object of which the filtered metadata will be retrieved + * @return A list of object metadata (including virtual metadata) filtered based on the hidden metadata + * configuration + */ + private MetadataValueList getPermissionFilteredMetadata(Context context, Item obj) { + List fullList = itemService.getMetadata(obj, Item.ANY, Item.ANY, Item.ANY, Item.ANY, true); + List returnList = new LinkedList<>(); + try { + if (obj.isWithdrawn() && (Objects.isNull(context) || + Objects.isNull(context.getCurrentUser()) || !authorizeService.isAdmin(context))) { + return new MetadataValueList(new ArrayList<>()); + } + if (context != null && (authorizeService.isAdmin(context) || itemService.canEdit(context, obj))) { + return new MetadataValueList(fullList); + } + for (MetadataValue mv : fullList) { + MetadataField metadataField = mv.getMetadataField(); + if (!metadataExposureService + .isHidden(context, metadataField.getMetadataSchema().getName(), + metadataField.getElement(), + metadataField.getQualifier())) { + returnList.add(mv); + } + } + } catch (SQLException e) { + log.error("Error filtering item metadata based on permissions", e); + } + return new MetadataValueList(returnList); + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ItemFilterConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ItemFilterConverter.java new file mode 100644 index 000000000000..b058ef057c85 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ItemFilterConverter.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.app.rest.converter; + +import org.dspace.app.ldn.ItemFilter; +import org.dspace.app.rest.model.ItemFilterRest; +import org.dspace.app.rest.projection.Projection; +import org.springframework.stereotype.Component; + +/** + * This is the converter from the ItemFilter to the REST data model + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + */ +@Component +public class ItemFilterConverter implements DSpaceConverter { + + @Override + public ItemFilterRest convert(ItemFilter obj, Projection projection) { + ItemFilterRest itemFilterRest = new ItemFilterRest(); + itemFilterRest.setProjection(projection); + itemFilterRest.setId(obj.getId()); + return itemFilterRest; + } + + @Override + public Class getModelClass() { + return ItemFilter.class; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/JsonPatchConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/JsonPatchConverter.java index aba4d6a99536..89cbc1c2d7d1 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/JsonPatchConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/JsonPatchConverter.java @@ -10,13 +10,13 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; -import javax.annotation.Nonnull; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.ObjectNode; +import jakarta.annotation.Nonnull; import org.dspace.app.rest.model.patch.AddOperation; import org.dspace.app.rest.model.patch.CopyOperation; import org.dspace.app.rest.model.patch.FromOperation; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/LDNMessageEntityConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/LDNMessageEntityConverter.java new file mode 100644 index 000000000000..1c0783aec4d9 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/LDNMessageEntityConverter.java @@ -0,0 +1,71 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.rest.converter; + +import java.util.UUID; + +import org.dspace.app.ldn.LDNMessageEntity; +import org.dspace.app.ldn.NotifyServiceEntity; +import org.dspace.app.rest.model.LDNMessageEntityRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.content.DSpaceObject; +import org.dspace.discovery.IndexableObject; +import org.springframework.stereotype.Component; + +/** + * Converter to translate between {@link LDNMessageEntity} and {@link LDNMessageEntityRest} representations. + * @author Stefano Maffei (stefano.maffei at 4science.com) + */ +@Component +public class LDNMessageEntityConverter implements IndexableObjectConverter { + + @Override + public LDNMessageEntityRest convert(LDNMessageEntity obj, Projection projection) { + LDNMessageEntityRest ldnRest = new LDNMessageEntityRest(); + ldnRest.setNotificationId(obj.getID()); + ldnRest.setId(obj.getID()); + ldnRest.setQueueStatus(obj.getQueueStatus()); + ldnRest.setQueueStatusLabel(LDNMessageEntity.getQueueStatus(obj)); + ldnRest.setContext(getObjectIdentifier(obj.getContext())); + ldnRest.setObject(getObjectIdentifier(obj.getObject())); + ldnRest.setActivityStreamType(obj.getActivityStreamType()); + ldnRest.setCoarNotifyType(obj.getCoarNotifyType()); + ldnRest.setTarget(getObjectIdentifier(obj.getTarget())); + ldnRest.setOrigin(getObjectIdentifier(obj.getOrigin())); + ldnRest.setInReplyTo(getObjectIdentifier(obj.getInReplyTo())); + ldnRest.setQueueAttempts(obj.getQueueAttempts()); + ldnRest.setQueueLastStartTime(obj.getQueueLastStartTime()); + ldnRest.setQueueTimeout(obj.getQueueTimeout()); + ldnRest.setMessage(obj.getMessage()); + ldnRest.setNotificationType(LDNMessageEntity.getNotificationType(obj)); + return ldnRest; + } + + private UUID getObjectIdentifier(DSpaceObject dso) { + return dso == null ? null : dso.getID(); + } + + private Integer getObjectIdentifier(NotifyServiceEntity nse) { + return nse == null ? null : nse.getID(); + } + + private String getObjectIdentifier(LDNMessageEntity msg) { + return msg == null ? null : msg.getID(); + } + + @Override + public Class getModelClass() { + return LDNMessageEntity.class; + } + + @Override + public boolean supportsModel(IndexableObject idxo) { + return idxo.getIndexedObject() instanceof LDNMessageEntity; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/NotifyRequestStatusConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/NotifyRequestStatusConverter.java new file mode 100644 index 000000000000..d4a6efceee87 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/NotifyRequestStatusConverter.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.rest.converter; + +import org.dspace.app.ldn.model.NotifyRequestStatus; +import org.dspace.app.rest.model.NotifyRequestStatusRest; +import org.dspace.app.rest.projection.Projection; +import org.springframework.stereotype.Component; + +/** + * This is the converter from/to the NotifyRequestStatus in the DSpace API data model and + * the REST data model + * + * @author Francesco Bacchelli (francesco.bacchelli at 4science.it) + */ +@Component +public class NotifyRequestStatusConverter implements DSpaceConverter { + + @Override + public NotifyRequestStatusRest convert(NotifyRequestStatus modelObject, Projection projection) { + NotifyRequestStatusRest result = new NotifyRequestStatusRest(); + result.setItemuuid(modelObject.getItemUuid()); + result.setNotifyStatus(modelObject.getNotifyStatus()); + return result; + } + + @Override + public Class getModelClass() { + return NotifyRequestStatus.class; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/NotifyServiceConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/NotifyServiceConverter.java new file mode 100644 index 000000000000..8e0225f43fca --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/NotifyServiceConverter.java @@ -0,0 +1,68 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.rest.converter; + +import java.util.ArrayList; +import java.util.List; + +import org.dspace.app.ldn.NotifyServiceEntity; +import org.dspace.app.ldn.NotifyServiceInboundPattern; +import org.dspace.app.rest.model.NotifyServiceInboundPatternRest; +import org.dspace.app.rest.model.NotifyServiceRest; +import org.dspace.app.rest.projection.Projection; +import org.springframework.stereotype.Component; + +/** + * This is the converter from the NotifyServiceEntity to the REST data model + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + */ +@Component +public class NotifyServiceConverter implements DSpaceConverter { + + @Override + public NotifyServiceRest convert(NotifyServiceEntity obj, Projection projection) { + NotifyServiceRest notifyServiceRest = new NotifyServiceRest(); + + notifyServiceRest.setProjection(projection); + notifyServiceRest.setId(obj.getID()); + notifyServiceRest.setName(obj.getName()); + notifyServiceRest.setDescription(obj.getDescription()); + notifyServiceRest.setUrl(obj.getUrl()); + notifyServiceRest.setLdnUrl(obj.getLdnUrl()); + notifyServiceRest.setEnabled(obj.isEnabled()); + notifyServiceRest.setScore(obj.getScore()); + notifyServiceRest.setLowerIp(obj.getLowerIp()); + notifyServiceRest.setUpperIp(obj.getUpperIp()); + + if (obj.getInboundPatterns() != null) { + notifyServiceRest.setNotifyServiceInboundPatterns( + convertInboundPatternToRest(obj.getInboundPatterns())); + } + + return notifyServiceRest; + } + + private List convertInboundPatternToRest( + List inboundPatterns) { + List inboundPatternRests = new ArrayList<>(); + for (NotifyServiceInboundPattern inboundPattern : inboundPatterns) { + NotifyServiceInboundPatternRest inboundPatternRest = new NotifyServiceInboundPatternRest(); + inboundPatternRest.setPattern(inboundPattern.getPattern()); + inboundPatternRest.setConstraint(inboundPattern.getConstraint()); + inboundPatternRest.setAutomatic(inboundPattern.isAutomatic()); + inboundPatternRests.add(inboundPatternRest); + } + return inboundPatternRests; + } + + @Override + public Class getModelClass() { + return NotifyServiceEntity.class; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/PotentialDuplicateConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/PotentialDuplicateConverter.java new file mode 100644 index 000000000000..8d2814b611e5 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/PotentialDuplicateConverter.java @@ -0,0 +1,68 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.rest.converter; + +import org.dspace.app.rest.model.MetadataValueList; +import org.dspace.app.rest.model.PotentialDuplicateRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.content.virtual.PotentialDuplicate; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +/** + * Convert DSpace PotentialDuplicate object to a PotentialDuplicateRest REST resource + * for use in REST results. + * + * @author Kim Shepherd + */ +@Component +public class PotentialDuplicateConverter implements DSpaceConverter { + @Lazy + @Autowired + private ConverterService converter; + + /** + * Convert a PotentialDuplicate model object into its equivalent REST resource, applying + * a given projection. + * @see PotentialDuplicate + * @see PotentialDuplicateRest + * + * @param modelObject a PotentialDuplicate object + * @param projection current projection + * @return a converted PotentialDuplicateRest REST object + */ + @Override + public PotentialDuplicateRest convert(PotentialDuplicate modelObject, Projection projection) { + if (modelObject == null) { + return null; + } + // Instantiate new REST model object + PotentialDuplicateRest rest = new PotentialDuplicateRest(); + // Set or otherwise transform things here, then return + rest.setUuid(modelObject.getUuid()); + rest.setTitle(modelObject.getTitle()); + rest.setOwningCollectionName(modelObject.getOwningCollectionName()); + rest.setWorkflowItemId(modelObject.getWorkflowItemId()); + rest.setWorkspaceItemId(modelObject.getWorkspaceItemId()); + rest.setMetadata(converter.toRest(new MetadataValueList(modelObject.getMetadataValueList()), projection)); + + // Return converted object + return rest; + } + + /** + * For what DSpace API model class does this converter convert? + * @return Class of model objects represented. + */ + @Override + public Class getModelClass() { + return PotentialDuplicate.class; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ProcessConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ProcessConverter.java index 3a243f3946b0..15e99f9503d6 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ProcessConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ProcessConverter.java @@ -39,7 +39,9 @@ public ProcessRest convert(Process process, Projection projection) { processRest.setId(process.getID()); processRest.setScriptName(process.getName()); processRest.setProcessId(process.getID()); - processRest.setUserId(process.getEPerson().getID()); + if (process.getEPerson() != null) { + processRest.setUserId(process.getEPerson().getID()); + } processRest.setProcessStatus(process.getProcessStatus()); processRest.setStartTime(process.getStartTime()); processRest.setEndTime(process.getFinishedTime()); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/QAEventConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/QAEventConverter.java index a32c0ddc99a9..098f4dafb75b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/QAEventConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/QAEventConverter.java @@ -8,17 +8,23 @@ package org.dspace.app.rest.converter; import java.text.DecimalFormat; -import javax.annotation.PostConstruct; +import java.text.DecimalFormatSymbols; +import java.util.Locale; 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 jakarta.annotation.PostConstruct; +import org.dspace.app.rest.model.CorrectionTypeQAEventMessageRest; +import org.dspace.app.rest.model.NotifyQAEventMessageRest; import org.dspace.app.rest.model.OpenaireQAEventMessageRest; import org.dspace.app.rest.model.QAEventMessageRest; import org.dspace.app.rest.model.QAEventRest; import org.dspace.app.rest.projection.Projection; import org.dspace.content.QAEvent; +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.services.ConfigurationService; @@ -54,16 +60,18 @@ public QAEventRest convert(QAEvent modelObject, Projection projection) { rest.setId(modelObject.getEventId()); try { rest.setMessage(convertMessage(jsonMapper.readValue(modelObject.getMessage(), - modelObject.getMessageDtoClass()))); + modelObject.getMessageDtoClass()))); } catch (JsonProcessingException e) { - throw new RuntimeException(e); + throw new RuntimeException(e.getMessage(), e); } + rest.setSource(modelObject.getSource()); rest.setOriginalId(modelObject.getOriginalId()); rest.setProjection(projection); rest.setTitle(modelObject.getTitle()); rest.setTopic(modelObject.getTopic()); rest.setEventDate(modelObject.getLastUpdate()); - rest.setTrust(new DecimalFormat("0.000").format(modelObject.getTrust())); + DecimalFormat decimalFormat = new DecimalFormat("0.000", new DecimalFormatSymbols(Locale.ENGLISH)); + rest.setTrust(decimalFormat.format(modelObject.getTrust())); // right now only the pending status can be found in persisted qa events rest.setStatus(modelObject.getStatus()); return rest; @@ -72,10 +80,32 @@ public QAEventRest convert(QAEvent modelObject, Projection projection) { private QAEventMessageRest convertMessage(QAMessageDTO dto) { if (dto instanceof OpenaireMessageDTO) { return convertOpenaireMessage(dto); + } else if (dto instanceof NotifyMessageDTO) { + return convertNotifyMessage(dto); + } + if (dto instanceof CorrectionTypeMessageDTO) { + return convertCorrectionTypeMessage(dto); } throw new IllegalArgumentException("Unknown message type: " + dto.getClass()); } + private QAEventMessageRest convertNotifyMessage(QAMessageDTO dto) { + NotifyMessageDTO notifyDto = (NotifyMessageDTO) dto; + NotifyQAEventMessageRest message = new NotifyQAEventMessageRest(); + message.setServiceName(notifyDto.getServiceName()); + message.setServiceId(notifyDto.getServiceId()); + message.setHref(notifyDto.getHref()); + message.setRelationship(notifyDto.getRelationship()); + return message; + } + + private QAEventMessageRest convertCorrectionTypeMessage(QAMessageDTO dto) { + CorrectionTypeMessageDTO correctionTypeDto = (CorrectionTypeMessageDTO) dto; + CorrectionTypeQAEventMessageRest message = new CorrectionTypeQAEventMessageRest(); + message.setReason(correctionTypeDto.getReason()); + return message; + } + private QAEventMessageRest convertOpenaireMessage(QAMessageDTO dto) { OpenaireMessageDTO openaireDto = (OpenaireMessageDTO) dto; OpenaireQAEventMessageRest message = new OpenaireQAEventMessageRest(); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/QASourceConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/QASourceConverter.java index 6c1bc0d66cb7..c358c7323e95 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/QASourceConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/QASourceConverter.java @@ -31,7 +31,8 @@ public Class getModelClass() { public QASourceRest convert(QASource modelObject, Projection projection) { QASourceRest rest = new QASourceRest(); rest.setProjection(projection); - rest.setId(modelObject.getName()); + rest.setId(modelObject.getName() + + (modelObject.getFocus() != null ? ":" + modelObject.getFocus().toString() : "")); rest.setLastEvent(modelObject.getLastEvent()); rest.setTotalEvents(modelObject.getTotalEvents()); return rest; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/QATopicConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/QATopicConverter.java index efa32baba224..e6334924c190 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/QATopicConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/QATopicConverter.java @@ -31,7 +31,9 @@ public Class getModelClass() { public QATopicRest convert(QATopic modelObject, Projection projection) { QATopicRest rest = new QATopicRest(); rest.setProjection(projection); - rest.setId(modelObject.getKey().replace("/", "!")); + rest.setId(modelObject.getSource() + ":" + + modelObject.getKey().replace("/", "!") + + (modelObject.getFocus() != null ? ":" + modelObject.getFocus().toString() : "")); rest.setName(modelObject.getKey()); rest.setLastEvent(modelObject.getLastEvent()); rest.setTotalEvents(modelObject.getTotalEvents()); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RequestItemConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RequestItemConverter.java index 27ea4f66305b..e6d6f4da2aee 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RequestItemConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RequestItemConverter.java @@ -8,8 +8,7 @@ package org.dspace.app.rest.converter; -import javax.inject.Named; - +import jakarta.inject.Named; import org.dspace.app.requestitem.RequestItem; import org.dspace.app.rest.model.RequestItemRest; import org.dspace.app.rest.projection.Projection; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SearchEventConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SearchEventConverter.java index 126d37ba1ace..d781d255df11 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SearchEventConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SearchEventConverter.java @@ -11,9 +11,10 @@ import java.util.Arrays; import java.util.LinkedList; import java.util.List; -import javax.servlet.http.HttpServletRequest; -import org.apache.log4j.Logger; +import jakarta.servlet.http.HttpServletRequest; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.app.rest.model.PageRest; import org.dspace.app.rest.model.SearchEventRest; import org.dspace.app.rest.model.SearchResultsRest; @@ -31,7 +32,7 @@ @Component public class SearchEventConverter { /* Log4j logger */ - private static final Logger log = Logger.getLogger(SearchEventConverter.class); + private static final Logger log = LogManager.getLogger(SearchEventConverter.class); @Autowired private ScopeResolver scopeResolver; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionCOARNotifyConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionCOARNotifyConverter.java new file mode 100644 index 000000000000..8cfcb6c0639c --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionCOARNotifyConverter.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.rest.converter; + +import org.dspace.app.rest.model.SubmissionCOARNotifyRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.coarnotify.NotifySubmissionConfiguration; +import org.springframework.stereotype.Component; + +/** + * This converter is responsible for transforming the model representation of an COARNotify to the REST + * representation of an COARNotifySubmissionConfiguration and vice versa + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + **/ +@Component +public class SubmissionCOARNotifyConverter + implements DSpaceConverter { + + /** + * Convert a COARNotify to its REST representation + * @param modelObject - the COARNotify to convert + * @param projection - the projection + * @return the corresponding SubmissionCOARNotifyRest object + */ + @Override + public SubmissionCOARNotifyRest convert(final NotifySubmissionConfiguration modelObject, + final Projection projection) { + + SubmissionCOARNotifyRest submissionCOARNotifyRest = new SubmissionCOARNotifyRest(); + submissionCOARNotifyRest.setProjection(projection); + submissionCOARNotifyRest.setId(modelObject.getId()); + submissionCOARNotifyRest.setPatterns(modelObject.getPatterns()); + return submissionCOARNotifyRest; + } + + @Override + public Class getModelClass() { + return NotifySubmissionConfiguration.class; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionDefinitionConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionDefinitionConverter.java index a84545520463..0717f9b30975 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionDefinitionConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionDefinitionConverter.java @@ -11,8 +11,8 @@ 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.apache.logging.log4j.Logger; import org.dspace.app.rest.model.CollectionRest; import org.dspace.app.rest.model.SubmissionDefinitionRest; @@ -70,7 +70,7 @@ public SubmissionDefinitionRest convert(SubmissionConfig obj, Projection project } } catch (ClassNotFoundException e) { throw new IllegalStateException( - "The submission configration is invalid the processing class for the step " + step.getId() + "The submission configuration is invalid the processing class for the step " + step.getId() + " is not found", e); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SuggestionTargetConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SuggestionTargetConverter.java index 4bf4be72263a..df355dac1f15 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SuggestionTargetConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SuggestionTargetConverter.java @@ -27,7 +27,9 @@ public SuggestionTargetRest convert(SuggestionTarget target, Projection projecti SuggestionTargetRest targetRest = new SuggestionTargetRest(); targetRest.setProjection(projection); targetRest.setId(target.getID()); - targetRest.setDisplay(target.getTarget().getName()); + if (target != null && target.getTarget() != null) { + targetRest.setDisplay(target.getTarget().getName()); + } targetRest.setTotal(target.getTotal()); targetRest.setSource(target.getSource()); return targetRest; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/query/SearchQueryConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/query/SearchQueryConverter.java index 4ea5e6f9df7d..087d422cdcd1 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/query/SearchQueryConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/query/SearchQueryConverter.java @@ -7,6 +7,8 @@ */ package org.dspace.app.rest.converter.query; +import static org.dspace.app.rest.model.SearchConfigurationRest.Filter.OPERATOR_QUERY; + import java.util.LinkedList; import java.util.List; @@ -16,23 +18,23 @@ import org.dspace.app.rest.parameter.SearchFilter; /** - * This method will traverse a list of SearchFilters and transform any SearchFilters with an operator - * this is equal to 'Query' into a SearchFilter that has a standard DSpace operator like 'contains' + * Utility class for transforming a list of SearchFilters. Each SearchFilter with an operator set to 'query' + * is converted into a SearchFilter with a standard DSpace operator like 'contains'. */ public class SearchQueryConverter { /** - * This method traverses the list of SearchFilters and transforms all of those that contain 'Query' + * This method traverses the list of SearchFilters and transforms all of those that with 'query' * as the operator into a standard DSpace SearchFilter * - * @param searchFilters The list of SearchFilters to be used - * @return A list of transformed SearchFilters + * @param searchFilters list of SearchFilters to be transformed + * @return list of transformed SearchFilters */ public List convert(List searchFilters) { List transformedSearchFilters = new LinkedList<>(); for (SearchFilter searchFilter : CollectionUtils.emptyIfNull(searchFilters)) { - if (StringUtils.equals(searchFilter.getOperator(), "query")) { + if (StringUtils.equals(searchFilter.getOperator(), OPERATOR_QUERY)) { SearchFilter transformedSearchFilter = convertQuerySearchFilterIntoStandardSearchFilter(searchFilter); transformedSearchFilters.add(transformedSearchFilter); } else { @@ -46,10 +48,10 @@ public List convert(List searchFilters) { /** * This method takes care of the converter of a specific SearchFilter given to it * - * @param searchFilter The SearchFilter to be transformed - * @return The transformed SearchFilter + * @param searchFilter searchFilter to be transformed + * @return transformed SearchFilter */ - public SearchFilter convertQuerySearchFilterIntoStandardSearchFilter(SearchFilter searchFilter) { + private SearchFilter convertQuerySearchFilterIntoStandardSearchFilter(SearchFilter searchFilter) { RestSearchOperator restSearchOperator = RestSearchOperator.forQuery(searchFilter.getValue()); SearchFilter transformedSearchFilter = new SearchFilter(searchFilter.getName(), restSearchOperator.getDspaceOperator(), restSearchOperator.extractValue(searchFilter.getValue())); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/DSpaceAccessDeniedHandler.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/DSpaceAccessDeniedHandler.java index c2842d33d2bc..8df77f424a7d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/DSpaceAccessDeniedHandler.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/DSpaceAccessDeniedHandler.java @@ -8,10 +8,10 @@ package org.dspace.app.rest.exception; import java.io.IOException; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Lazy; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/DSpaceApiExceptionControllerAdvice.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/DSpaceApiExceptionControllerAdvice.java index a65ea13bc2c0..6bfb43297b54 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/DSpaceApiExceptionControllerAdvice.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/DSpaceApiExceptionControllerAdvice.java @@ -7,7 +7,7 @@ */ package org.dspace.app.rest.exception; -import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN; +import static jakarta.servlet.http.HttpServletResponse.SC_FORBIDDEN; import static org.springframework.web.servlet.DispatcherServlet.EXCEPTION_ATTRIBUTE; import java.io.IOException; @@ -15,10 +15,10 @@ import java.util.HashSet; import java.util.Objects; import java.util.Set; -import javax.inject.Inject; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.inject.Inject; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.app.exception.ResourceAlreadyExistsException; @@ -33,6 +33,7 @@ import org.springframework.data.repository.support.QueryMethodParameterConversionException; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatusCode; import org.springframework.http.ResponseEntity; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.web.csrf.InvalidCsrfTokenException; @@ -42,7 +43,6 @@ import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.context.request.WebRequest; -import org.springframework.web.multipart.MaxUploadSizeExceededException; import org.springframework.web.multipart.MultipartException; import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; @@ -92,19 +92,12 @@ protected void csrfTokenException(HttpServletRequest request, HttpServletRespons HttpServletResponse.SC_FORBIDDEN); } - @ExceptionHandler({IllegalArgumentException.class, MultipartException.class}) + @ExceptionHandler({IllegalArgumentException.class, MultipartException.class, InvalidLDNMessageException.class}) protected void handleWrongRequestException(HttpServletRequest request, HttpServletResponse response, Exception ex) throws IOException { sendErrorResponse(request, response, ex, "Request is invalid or incorrect", HttpServletResponse.SC_BAD_REQUEST); } - @ExceptionHandler(MaxUploadSizeExceededException.class) - protected void handleMaxUploadSizeExceededException(HttpServletRequest request, HttpServletResponse response, - Exception ex) throws IOException { - sendErrorResponse(request, response, ex, "Request entity is too large", - HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE); - } - @ExceptionHandler(SQLException.class) protected void handleSQLException(HttpServletRequest request, HttpServletResponse response, Exception ex) throws IOException { @@ -187,7 +180,7 @@ protected void handleCustomUnprocessableEntityException(HttpServletRequest reque @ExceptionHandler(QueryMethodParameterConversionException.class) protected void ParameterConversionException(HttpServletRequest request, HttpServletResponse response, Exception ex) throws IOException { - // we want the 400 status for missing parameters, see https://jira.lyrasis.org/browse/DS-4428 + // we want the 400 status for missing parameters, see https://github.com/DSpace/DSpace/issues/7765 sendErrorResponse(request, response, ex, "A required parameter is invalid", HttpStatus.BAD_REQUEST.value()); @@ -196,7 +189,7 @@ protected void ParameterConversionException(HttpServletRequest request, HttpServ @ExceptionHandler(MissingParameterException.class) protected void MissingParameterException(HttpServletRequest request, HttpServletResponse response, Exception ex) throws IOException { - // we want the 400 status for missing parameters, see https://jira.lyrasis.org/browse/DS-4428 + // we want the 400 status for missing parameters, see https://github.com/DSpace/DSpace/issues/7765 sendErrorResponse(request, response, ex, "A required parameter is missing", HttpStatus.BAD_REQUEST.value()); @@ -216,16 +209,16 @@ protected void handleInvalidCaptchaTokenRequestException(HttpServletRequest requ @Override protected ResponseEntity handleMissingServletRequestParameter(MissingServletRequestParameterException ex, - HttpHeaders headers, HttpStatus status, + HttpHeaders headers, HttpStatusCode status, WebRequest request) { - // we want the 400 status for missing parameters, see https://jira.lyrasis.org/browse/DS-4428 + // we want the 400 status for missing parameters, see https://github.com/DSpace/DSpace/issues/7765 return super.handleMissingServletRequestParameter(ex, headers, HttpStatus.BAD_REQUEST, request); } @Override protected ResponseEntity handleTypeMismatch(TypeMismatchException ex, HttpHeaders headers, - HttpStatus status, WebRequest request) { - // we want the 400 status for missing parameters, see https://jira.lyrasis.org/browse/DS-4428 + HttpStatusCode status, WebRequest request) { + // we want the 400 status for missing parameters, see https://github.com/DSpace/DSpace/issues/7765 return super.handleTypeMismatch(ex, headers, HttpStatus.BAD_REQUEST, request); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/InvalidLDNMessageException.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/InvalidLDNMessageException.java new file mode 100644 index 000000000000..0542ef02cdf7 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/InvalidLDNMessageException.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.app.rest.exception; + + +/** + * This exception is thrown when the given LDN Message json is invalid + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + */ +public class InvalidLDNMessageException extends RuntimeException { + + public InvalidLDNMessageException(String message, Throwable cause) { + super(message, cause); + } + + public InvalidLDNMessageException(String message) { + super(message); + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/filter/ContentLanguageHeaderResponseFilter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/filter/ContentLanguageHeaderResponseFilter.java index 74ffd73ad463..5f45f7edc554 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/filter/ContentLanguageHeaderResponseFilter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/filter/ContentLanguageHeaderResponseFilter.java @@ -9,14 +9,14 @@ import java.io.IOException; import java.util.Locale; -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.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.HttpServletResponse; import org.dspace.core.I18nUtil; import org.springframework.stereotype.Component; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/filter/DSpaceRequestContextFilter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/filter/DSpaceRequestContextFilter.java index a9170652a251..59cf70202154 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/filter/DSpaceRequestContextFilter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/filter/DSpaceRequestContextFilter.java @@ -8,17 +8,15 @@ package org.dspace.app.rest.filter; import java.io.IOException; -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 jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.FilterConfig; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; import org.dspace.app.rest.utils.ContextUtil; import org.dspace.core.Context; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * A Servlet Filter whose only role is to clean up open Context objects in @@ -29,8 +27,6 @@ * @see ContextUtil */ public class DSpaceRequestContextFilter implements Filter { - private static final Logger log = LoggerFactory.getLogger(DSpaceRequestContextFilter.class); - @Override public void init(FilterConfig filterConfig) throws ServletException { //noop diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/hdlresolver/HdlResolverRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/hdlresolver/HdlResolverRestController.java index 540c3fd17243..5d87dc520579 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/hdlresolver/HdlResolverRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/hdlresolver/HdlResolverRestController.java @@ -10,10 +10,10 @@ import java.text.MessageFormat; import java.util.List; import java.util.Optional; -import javax.servlet.http.HttpServletRequest; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.lang.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -63,7 +63,7 @@ public ResponseEntity handleController(HttpServletRequest request, @Path request, Optional.ofNullable(request.getRequestURI().split(LISTHANDLES)) .filter(split -> split.length > 1) - .map(splitted -> splitted[1]) + .map(split -> split[1]) .orElse(null) ); } else if (LISTPREFIXES.contains(hdlService)) { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/health/SolrHealthIndicator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/health/SolrHealthIndicator.java new file mode 100644 index 000000000000..1d9f4fee24de --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/health/SolrHealthIndicator.java @@ -0,0 +1,123 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.rest.health; + +import org.apache.solr.client.solrj.SolrClient; +import org.apache.solr.client.solrj.impl.BaseHttpSolrClient.RemoteSolrException; +import org.apache.solr.client.solrj.request.CoreAdminRequest; +import org.apache.solr.common.params.CoreAdminParams; +import org.springframework.boot.actuate.health.AbstractHealthIndicator; +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.HealthIndicator; +import org.springframework.boot.actuate.health.Status; + +/** + * {@link HealthIndicator} for Apache Solr. + * + * This is copied from the 'org.springframework.boot.actuate.solr.SolrHealthIndicator' in Spring Boot v2, + * as that class was removed in Spring Boot v3. See https://github.com/spring-projects/spring-boot/issues/31054 + * + * This HealthIndicator has updated by DSpace to support later versions of Spring Boot and Solr. + */ +public class SolrHealthIndicator extends AbstractHealthIndicator { + + private static final int HTTP_NOT_FOUND_STATUS = 404; + + private final SolrClient solrClient; + + private volatile StatusCheck statusCheck; + + public SolrHealthIndicator(SolrClient solrClient) { + super("Solr health check failed"); + this.solrClient = solrClient; + } + + @Override + protected void doHealthCheck(Health.Builder builder) throws Exception { + int statusCode = initializeStatusCheck(); + Status status = (statusCode != 0) ? Status.DOWN : Status.UP; + builder.status(status).withDetail("status", statusCode).withDetail("detectedPathType", + this.statusCheck.getPathType()); + } + + private int initializeStatusCheck() throws Exception { + StatusCheck statusCheck = this.statusCheck; + if (statusCheck != null) { + // Already initialized + return statusCheck.getStatus(this.solrClient); + } + try { + return initializeStatusCheck(new RootStatusCheck()); + } catch (RemoteSolrException ex) { + // 404 is thrown when SolrClient has a baseUrl pointing to a particular core. + if (ex.code() == HTTP_NOT_FOUND_STATUS) { + return initializeStatusCheck(new ParticularCoreStatusCheck()); + } + throw ex; + } + } + + private int initializeStatusCheck(StatusCheck statusCheck) throws Exception { + int result = statusCheck.getStatus(this.solrClient); + this.statusCheck = statusCheck; + return result; + } + + /** + * Strategy used to perform the status check. + */ + private abstract static class StatusCheck { + + private final String pathType; + + StatusCheck(String pathType) { + this.pathType = pathType; + } + + abstract int getStatus(SolrClient client) throws Exception; + + String getPathType() { + return this.pathType; + } + + } + + /** + * {@link StatusCheck} used when {@code baseUrl} points to the root context. + */ + private static class RootStatusCheck extends StatusCheck { + + RootStatusCheck() { + super("root"); + } + + @Override + public int getStatus(SolrClient client) throws Exception { + CoreAdminRequest request = new CoreAdminRequest(); + request.setAction(CoreAdminParams.CoreAdminAction.STATUS); + return request.process(client).getStatus(); + } + + } + + /** + * {@link StatusCheck} used when {@code baseUrl} points to the particular core. + */ + private static class ParticularCoreStatusCheck extends StatusCheck { + + ParticularCoreStatusCheck() { + super("particular core"); + } + + @Override + public int getStatus(SolrClient client) throws Exception { + return client.ping().getStatus(); + } + + } +} \ No newline at end of file diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/BrowseEntryHalLinkFactory.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/BrowseEntryHalLinkFactory.java index 9e515984fe03..9928efb73519 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/BrowseEntryHalLinkFactory.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/BrowseEntryHalLinkFactory.java @@ -9,7 +9,6 @@ import java.util.LinkedList; -import org.atteo.evo.inflector.English; import org.dspace.app.rest.RestResourceController; import org.dspace.app.rest.model.BrowseEntryRest; import org.dspace.app.rest.model.BrowseIndexRest; @@ -36,7 +35,7 @@ protected void addLinks(final BrowseEntryResource halResource, final Pageable pa UriComponentsBuilder baseLink = uriBuilder( getMethodOn(bix.getCategory(), bix.getType()).findRel(null, null, bix.getCategory(), - English.plural(bix.getType()), bix.getId(), + bix.getTypePlural(), bix.getId(), BrowseIndexRest.LINK_ITEMS, null, null)); addFilterParams(baseLink, data); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/OrcidQueueHalLinkFactory.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/OrcidQueueHalLinkFactory.java index 813ddf58db9d..9e679bb0a2be 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/OrcidQueueHalLinkFactory.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/OrcidQueueHalLinkFactory.java @@ -11,7 +11,6 @@ import java.util.LinkedList; -import org.atteo.evo.inflector.English; import org.dspace.app.rest.RestResourceController; import org.dspace.app.rest.model.ItemRest; import org.dspace.app.rest.model.OrcidQueueRest; @@ -37,7 +36,7 @@ protected void addLinks(OrcidQueueResource halResource, Pageable pageable, Linke OrcidQueueRest orcidQueueRest = halResource.getContent(); if (orcidQueueRest.getProfileItemId() != null) { UriComponentsBuilder uriComponentsBuilder = linkTo(getMethodOn(ItemRest.CATEGORY, ItemRest.NAME) - .findRel(null, null, ItemRest.CATEGORY, English.plural(ItemRest.NAME), + .findRel(null, null, ItemRest.CATEGORY, ItemRest.PLURAL_NAME, orcidQueueRest.getProfileItemId(), "", null, null)).toUriComponentsBuilder(); String uribuilder = uriComponentsBuilder.build().toString(); list.add(buildLink("profileItem", uribuilder.substring(0, uribuilder.lastIndexOf("/")))); @@ -45,7 +44,7 @@ protected void addLinks(OrcidQueueResource halResource, Pageable pageable, Linke if (orcidQueueRest.getEntityId() != null) { UriComponentsBuilder uriComponentsBuilder = linkTo(getMethodOn(ItemRest.CATEGORY, ItemRest.NAME) - .findRel(null, null, ItemRest.CATEGORY, English.plural(ItemRest.NAME), + .findRel(null, null, ItemRest.CATEGORY, ItemRest.PLURAL_NAME, orcidQueueRest.getEntityId(), "", null, null)).toUriComponentsBuilder(); String uribuilder = uriComponentsBuilder.build().toString(); list.add(buildLink("entity", uribuilder.substring(0, uribuilder.lastIndexOf("/")))); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/SubmissionSectionHalLinkFactory.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/SubmissionSectionHalLinkFactory.java index 4cce63ed698b..e66f01d4014b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/SubmissionSectionHalLinkFactory.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/SubmissionSectionHalLinkFactory.java @@ -11,7 +11,6 @@ import java.util.LinkedList; -import org.atteo.evo.inflector.English; import org.dspace.app.rest.RestResourceController; import org.dspace.app.rest.model.SubmissionAccessOptionRest; import org.dspace.app.rest.model.SubmissionFormRest; @@ -37,19 +36,22 @@ protected void addLinks(final SubmissionSectionResource halResource, final Pagea SubmissionSectionRest sd = halResource.getContent(); if (SubmissionStepConfig.INPUT_FORM_STEP_NAME.equals(sd.getSectionType())) { - buildLink(list, sd, SubmissionFormRest.CATEGORY, SubmissionFormRest.NAME); + buildLink(list, sd, SubmissionFormRest.CATEGORY, SubmissionFormRest.NAME, SubmissionFormRest.PLURAL_NAME); } if (SubmissionStepConfig.UPLOAD_STEP_NAME.equals(sd.getSectionType())) { - buildLink(list, sd, SubmissionUploadRest.CATEGORY, SubmissionUploadRest.NAME); + buildLink(list, sd, SubmissionUploadRest.CATEGORY, SubmissionUploadRest.NAME, + SubmissionUploadRest.PLURAL_NAME); } if (SubmissionStepConfig.ACCESS_CONDITION_STEP_NAME.equals(sd.getSectionType())) { - buildLink(list, sd, SubmissionAccessOptionRest.CATEGORY, SubmissionAccessOptionRest.NAME); + buildLink(list, sd, SubmissionAccessOptionRest.CATEGORY, SubmissionAccessOptionRest.NAME, + SubmissionAccessOptionRest.PLURAL_NAME); } } - private void buildLink(final LinkedList list, SubmissionSectionRest sd, String category, String name) { + private void buildLink(final LinkedList list, SubmissionSectionRest sd, String category, String name, + String plural) { UriComponentsBuilder uriComponentsBuilder = linkTo(getMethodOn(category, name) - .findRel(null, null, category, English.plural(name), sd.getId(), "", null, null)) + .findRel(null, null, category, plural, sd.getId(), "", null, null)) .toUriComponentsBuilder(); String uribuilder = uriComponentsBuilder.build().toString(); list.add(buildLink(NAME_LINK_ON_PANEL, uribuilder.substring(0, uribuilder.lastIndexOf("/")))); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/contentreport/ContentReportSupportHalLinkFactory.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/contentreport/ContentReportSupportHalLinkFactory.java new file mode 100644 index 000000000000..b31feb3b0267 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/contentreport/ContentReportSupportHalLinkFactory.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.rest.link.contentreport; + +import java.util.LinkedList; + +import org.dspace.app.rest.ContentReportRestController; +import org.dspace.app.rest.link.HalLinkFactory; +import org.dspace.app.rest.model.hateoas.ContentReportSupportResource; +import org.springframework.data.domain.Pageable; +import org.springframework.hateoas.IanaLinkRelations; +import org.springframework.hateoas.Link; +import org.springframework.stereotype.Component; + +/** + * This class adds the self and report links to the ContentReportSupportResource. + * @author Jean-François Morin (Université Laval) + */ +@Component +public class ContentReportSupportHalLinkFactory + extends HalLinkFactory { + + @Override + protected void addLinks(ContentReportSupportResource halResource, Pageable pageable, LinkedList list) + throws Exception { + + list.add(buildLink(IanaLinkRelations.SELF.value(), getMethodOn().getContentReportSupport())); + list.add(buildLink("filteredcollections", getMethodOn().getFilteredCollections(null, null, null))); + list.add(buildLink("filtereditems", getMethodOn() + .getFilteredItems(null, null, null, null, null, null, null, null, null))); + } + + @Override + protected Class getControllerClass() { + return ContentReportRestController.class; + } + + @Override + protected Class getResourceClass() { + return ContentReportSupportResource.class; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/process/SubmissionCCLicenseUrlResourceHalLinkFactory.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/process/SubmissionCCLicenseUrlResourceHalLinkFactory.java index 07d5e46c61e0..7ecd8fc3ecf2 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/process/SubmissionCCLicenseUrlResourceHalLinkFactory.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/process/SubmissionCCLicenseUrlResourceHalLinkFactory.java @@ -51,8 +51,8 @@ protected void addLinks(SubmissionCCLicenseUrlResource halResource, final Pageab UriComponentsBuilder uriComponentsBuilder = uriBuilder(getMethodOn().executeSearchMethods( - SubmissionCCLicenseUrlRest.CATEGORY, SubmissionCCLicenseUrlRest.PLURAL, "rightsByQuestions", null, null, - null, null, new LinkedMultiValueMap<>())); + SubmissionCCLicenseUrlRest.CATEGORY, SubmissionCCLicenseUrlRest.PLURAL_NAME, "rightsByQuestions", null, + null, null, null, new LinkedMultiValueMap<>())); for (String key : parameterMap.keySet()) { uriComponentsBuilder.queryParam(key, parameterMap.get(key)); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/relation/RelationshipHalLinkFactory.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/relation/RelationshipHalLinkFactory.java index 9d4e4291f5ae..e319428f0e8a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/relation/RelationshipHalLinkFactory.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/relation/RelationshipHalLinkFactory.java @@ -9,7 +9,6 @@ import java.util.LinkedList; -import org.atteo.evo.inflector.English; import org.dspace.app.rest.RestResourceController; import org.dspace.app.rest.link.HalLinkFactory; import org.dspace.app.rest.model.ItemRest; @@ -29,10 +28,10 @@ protected void addLinks(RelationshipResource halResource, Pageable pageable, Lin throws Exception { list.add(buildLink("leftItem", getMethodOn() - .findOne(ItemRest.CATEGORY, English.plural(ItemRest.NAME), halResource.getContent().getLeftId()))); + .findOne(ItemRest.CATEGORY, ItemRest.PLURAL_NAME, halResource.getContent().getLeftId()))); list.add(buildLink("rightItem", getMethodOn() - .findOne(ItemRest.CATEGORY, English.plural(ItemRest.NAME), halResource.getContent().getRightId()))); + .findOne(ItemRest.CATEGORY, ItemRest.PLURAL_NAME, halResource.getContent().getRightId()))); } @Override diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/login/impl/ResearcherProfileAutomaticClaim.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/login/impl/ResearcherProfileAutomaticClaim.java index 1450c12f909d..8521da970b27 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/login/impl/ResearcherProfileAutomaticClaim.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/login/impl/ResearcherProfileAutomaticClaim.java @@ -18,6 +18,8 @@ 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.app.rest.login.PostLoggedInAction; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Item; @@ -27,8 +29,6 @@ import org.dspace.eperson.EPerson; import org.dspace.eperson.service.EPersonService; import org.dspace.profile.service.ResearcherProfileService; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.Assert; @@ -43,7 +43,7 @@ */ public class ResearcherProfileAutomaticClaim implements PostLoggedInAction { - private final static Logger LOGGER = LoggerFactory.getLogger(ResearcherProfileAutomaticClaim.class); + private final static Logger LOGGER = LogManager.getLogger(); @Autowired private ResearcherProfileService researcherProfileService; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AInprogressSubmissionRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AInprogressSubmissionRest.java index 903e2866c855..79cf007ba190 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AInprogressSubmissionRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AInprogressSubmissionRest.java @@ -22,16 +22,11 @@ */ public abstract class AInprogressSubmissionRest extends BaseObjectRest { + private Date lastModified = new Date(); private Map sections; @JsonIgnore - private CollectionRest collection; - @JsonIgnore - private ItemRest item; - @JsonIgnore private SubmissionDefinitionRest submissionDefinition; - @JsonIgnore - private EPersonRest submitter; public Date getLastModified() { return lastModified; @@ -41,14 +36,6 @@ public void setLastModified(Date lastModified) { this.lastModified = lastModified; } - public ItemRest getItem() { - return item; - } - - public void setItem(ItemRest item) { - this.item = item; - } - public SubmissionDefinitionRest getSubmissionDefinition() { return submissionDefinition; } @@ -57,14 +44,6 @@ public void setSubmissionDefinition(SubmissionDefinitionRest submissionDefinitio this.submissionDefinition = submissionDefinition; } - public EPersonRest getSubmitter() { - return submitter; - } - - public void setSubmitter(EPersonRest submitter) { - this.submitter = submitter; - } - public Map getSections() { if (sections == null) { sections = new HashMap(); @@ -76,12 +55,6 @@ public void setSections(Map sections) { this.sections = sections; } - public CollectionRest getCollection() { - return collection; - } - public void setCollection(CollectionRest collection) { - this.collection = collection; - } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AccessStatusRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AccessStatusRest.java index c7dc2d11985d..85993f9a9213 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AccessStatusRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AccessStatusRest.java @@ -7,7 +7,6 @@ */ package org.dspace.app.rest.model; -import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty.Access; @@ -16,6 +15,7 @@ */ public class AccessStatusRest implements RestModel { public static final String NAME = "accessStatus"; + public static final String PLURAL_NAME = NAME; String status; @@ -25,10 +25,12 @@ public String getType() { return NAME; } + /** + * The plural name is the same as the singular name + */ @Override - @JsonIgnore public String getTypePlural() { - return getType(); + return PLURAL_NAME; } public AccessStatusRest() { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthenticationTokenRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthenticationTokenRest.java index 0599e095657a..6fbeeb219ac5 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthenticationTokenRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthenticationTokenRest.java @@ -15,6 +15,7 @@ */ public class AuthenticationTokenRest extends RestAddressableModel { public static final String NAME = "shortlivedtoken"; + public static final String PLURAL_NAME = "shortlivedtokens"; public static final String CATEGORY = RestAddressableModel.AUTHENTICATION; private String token; @@ -34,6 +35,11 @@ public String getType() { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + public String getToken() { return token; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthnRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthnRest.java index fade90fe4da7..8c873ccc1256 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthnRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthnRest.java @@ -18,6 +18,7 @@ public class AuthnRest extends BaseObjectRest { public static final String NAME = "authn"; + public static final String PLURAL_NAME = NAME; public static final String CATEGORY = RestAddressableModel.AUTHENTICATION; public String getCategory() { @@ -28,6 +29,14 @@ public String getType() { return NAME; } + /** + * The plural name is the same as the singular name + */ + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + public Class getController() { return AuthenticationRestController.class; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthorizationFeatureRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthorizationFeatureRest.java index cf602e2fb3ab..775b10d619c6 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthorizationFeatureRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthorizationFeatureRest.java @@ -21,6 +21,7 @@ */ public class AuthorizationFeatureRest extends BaseObjectRest { public static final String NAME = "feature"; + public static final String PLURAL_NAME = "features"; public static final String CATEGORY = RestAddressableModel.AUTHORIZATION; private String description; @@ -34,6 +35,11 @@ public String getType() { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @Override public String getCategory() { return CATEGORY; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthorizationRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthorizationRest.java index 95f288831303..cd3e33b9e2fa 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthorizationRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthorizationRest.java @@ -18,12 +18,13 @@ * @author Andrea Bollini (andrea.bollini at 4science.it) */ @LinksRest(links = { - @LinkRest(method = "getEperson", name = AuthorizationRest.EPERSON), - @LinkRest(method = "getFeature", name = AuthorizationRest.FEATURE), - @LinkRest(method = "getObject", name = AuthorizationRest.OBJECT) + @LinkRest(method = "getEperson", name = AuthorizationRest.EPERSON), + @LinkRest(method = "getFeature", name = AuthorizationRest.FEATURE), + @LinkRest(method = "getObject", name = AuthorizationRest.OBJECT) }) public class AuthorizationRest extends BaseObjectRest { public static final String NAME = "authorization"; + public static final String PLURAL_NAME = "authorizations"; public static final String CATEGORY = RestAddressableModel.AUTHORIZATION; public static final String EPERSON = "eperson"; @@ -36,6 +37,14 @@ public String getType() { return NAME; } + /** + * The plural name is the same as the singular name + */ + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @Override public String getCategory() { return CATEGORY; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BitstreamFormatRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BitstreamFormatRest.java index 1b02a1d89a4d..486eb02cad09 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BitstreamFormatRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BitstreamFormatRest.java @@ -20,6 +20,7 @@ */ public class BitstreamFormatRest extends BaseObjectRest { public static final String NAME = "bitstreamformat"; + public static final String PLURAL_NAME = "bitstreamformats"; public static final String CATEGORY = RestAddressableModel.CORE; @@ -96,6 +97,11 @@ public String getType() { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @Override @JsonIgnore public Class getController() { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BitstreamRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BitstreamRest.java index 8e9efc2680b7..d456f7222308 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BitstreamRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BitstreamRest.java @@ -16,18 +16,9 @@ * @author Andrea Bollini (andrea.bollini at 4science.it) */ @LinksRest(links = { - @LinkRest( - name = BitstreamRest.BUNDLE, - method = "getBundle" - ), - @LinkRest( - name = BitstreamRest.FORMAT, - method = "getFormat" - ), - @LinkRest( - name = BitstreamRest.THUMBNAIL, - method = "getThumbnail" - ) + @LinkRest(name = BitstreamRest.BUNDLE, method = "getBundle"), + @LinkRest(name = BitstreamRest.FORMAT, method = "getFormat"), + @LinkRest(name = BitstreamRest.THUMBNAIL, method = "getThumbnail") }) public class BitstreamRest extends DSpaceObjectRest { public static final String PLURAL_NAME = "bitstreams"; @@ -88,4 +79,9 @@ public String getCategory() { public String getType() { return NAME; } + + @Override + public String getTypePlural() { + return PLURAL_NAME; + } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BrowseEntryRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BrowseEntryRest.java index 6a569bd4c9f9..57f20d7c3105 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BrowseEntryRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BrowseEntryRest.java @@ -17,6 +17,7 @@ public class BrowseEntryRest implements RestModel { private static final long serialVersionUID = -3415049466402327251L; public static final String NAME = "browseEntry"; + public static final String PLURAL_NAME = "browseEntries"; private String authority; private String value; private String valueLang; @@ -69,4 +70,9 @@ public void setBrowseIndex(BrowseIndexRest browseIndex) { public String getType() { return NAME; } + + @Override + public String getTypePlural() { + return PLURAL_NAME; + } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BrowseIndexRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BrowseIndexRest.java index f7978f00fdf5..e5b089479971 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BrowseIndexRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BrowseIndexRest.java @@ -20,19 +20,14 @@ * @author Andrea Bollini (andrea.bollini at 4science.it) */ @LinksRest(links = { - @LinkRest( - name = BrowseIndexRest.LINK_ITEMS, - method = "listBrowseItems" - ), - @LinkRest( - name = BrowseIndexRest.LINK_ENTRIES, - method = "listBrowseEntries" - ) + @LinkRest(name = BrowseIndexRest.LINK_ITEMS, method = "listBrowseItems"), + @LinkRest(name = BrowseIndexRest.LINK_ENTRIES, method = "listBrowseEntries") }) public class BrowseIndexRest extends BaseObjectRest { private static final long serialVersionUID = -4870333170249999559L; public static final String NAME = "browse"; + public static final String PLURAL_NAME = "browses"; public static final String CATEGORY = RestAddressableModel.DISCOVER; @@ -79,6 +74,11 @@ public String getType() { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + public List getMetadataList() { return metadataList; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BulkAccessConditionRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BulkAccessConditionRest.java index 97d35117d1da..18ed4e71aa62 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BulkAccessConditionRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BulkAccessConditionRest.java @@ -23,7 +23,7 @@ public class BulkAccessConditionRest extends BaseObjectRest { private static final long serialVersionUID = -7708437586052984082L; public static final String NAME = "bulkaccessconditionoption"; - public static final String PLURAL = "bulkaccessconditionoptions"; + public static final String PLURAL_NAME = "bulkaccessconditionoptions"; public static final String CATEGORY = RestAddressableModel.CONFIGURATION; private String id; @@ -69,6 +69,11 @@ public String getType() { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @Override public String getCategory() { return CATEGORY; @@ -81,4 +86,4 @@ public Class getController() { return RestResourceController.class; } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BundleRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BundleRest.java index dd4a80d488a8..4a417e6c54c3 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BundleRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BundleRest.java @@ -16,18 +16,9 @@ * @author Jelle Pelgrims (jelle.pelgrims at atmire.com) */ @LinksRest(links = { - @LinkRest( - name = BundleRest.ITEM, - method = "getItem" - ), - @LinkRest( - name = BundleRest.BITSTREAMS, - method = "getBitstreams" - ), - @LinkRest( - name = BundleRest.PRIMARY_BITSTREAM, - method = "getPrimaryBitstream" - ) + @LinkRest(name = BundleRest.ITEM, method = "getItem"), + @LinkRest(name = BundleRest.BITSTREAMS, method = "getBitstreams"), + @LinkRest(name = BundleRest.PRIMARY_BITSTREAM, method = "getPrimaryBitstream") }) public class BundleRest extends DSpaceObjectRest { public static final String NAME = "bundle"; @@ -52,4 +43,9 @@ public String getCategory() { public String getType() { return NAME; } + + @Override + public String getTypePlural() { + return PLURAL_NAME; + } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ClaimedTaskRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ClaimedTaskRest.java index dd97fef44d0b..d29bf7a7ce6b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ClaimedTaskRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ClaimedTaskRest.java @@ -16,10 +16,7 @@ * @author Andrea Bollini (andrea.bollini at 4science.it) */ @LinksRest(links = { - @LinkRest( - name = ClaimedTaskRest.STEP, - method = "getStep" - ) + @LinkRest(name = ClaimedTaskRest.STEP, method = "getStep") }) public class ClaimedTaskRest extends BaseObjectRest { public static final String NAME = "claimedtask"; @@ -47,6 +44,11 @@ public String getType() { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @Override public Class getController() { return RestResourceController.class; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/CollectionRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/CollectionRest.java index 3f5ae3bb34c2..34faba4cb4d9 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/CollectionRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/CollectionRest.java @@ -15,38 +15,14 @@ * @author Andrea Bollini (andrea.bollini at 4science.it) */ @LinksRest(links = { - @LinkRest( - name = CollectionRest.LICENSE, - method = "getLicense" - ), - @LinkRest( - name = CollectionRest.LOGO, - method = "getLogo" - ), - @LinkRest( - name = CollectionRest.MAPPED_ITEMS, - method = "getMappedItems" - ), - @LinkRest( - name = CollectionRest.PARENT_COMMUNITY, - method = "getParentCommunity" - ), - @LinkRest( - name = CollectionRest.ADMIN_GROUP, - method = "getAdminGroup" - ), - @LinkRest( - name = CollectionRest.SUBMITTERS_GROUP, - method = "getSubmittersGroup" - ), - @LinkRest( - name = CollectionRest.ITEM_READ_GROUP, - method = "getItemReadGroup" - ), - @LinkRest( - name = CollectionRest.BITSTREAM_READ_GROUP, - method = "getBitstreamReadGroup" - ), + @LinkRest(name = CollectionRest.LICENSE, method = "getLicense"), + @LinkRest(name = CollectionRest.LOGO, method = "getLogo"), + @LinkRest(name = CollectionRest.MAPPED_ITEMS, method = "getMappedItems"), + @LinkRest(name = CollectionRest.PARENT_COMMUNITY, method = "getParentCommunity"), + @LinkRest(name = CollectionRest.ADMIN_GROUP, method = "getAdminGroup"), + @LinkRest(name = CollectionRest.SUBMITTERS_GROUP, method = "getSubmittersGroup"), + @LinkRest(name = CollectionRest.ITEM_READ_GROUP, method = "getItemReadGroup"), + @LinkRest(name = CollectionRest.BITSTREAM_READ_GROUP, method = "getBitstreamReadGroup"), }) public class CollectionRest extends DSpaceObjectRest { public static final String NAME = "collection"; @@ -75,6 +51,11 @@ public String getType() { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + private int archivedItemsCount; public int getArchivedItemsCount() { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/CommunityRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/CommunityRest.java index 86dc4b2c3900..e70b30803da3 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/CommunityRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/CommunityRest.java @@ -15,26 +15,11 @@ * @author Andrea Bollini (andrea.bollini at 4science.it) */ @LinksRest(links = { - @LinkRest( - name = CommunityRest.COLLECTIONS, - method = "getCollections" - ), - @LinkRest( - name = CommunityRest.LOGO, - method = "getLogo" - ), - @LinkRest( - name = CommunityRest.SUBCOMMUNITIES, - method = "getSubcommunities" - ), - @LinkRest( - name = CommunityRest.PARENT_COMMUNITY, - method = "getParentCommunity" - ), - @LinkRest( - name = CommunityRest.ADMIN_GROUP, - method = "getAdminGroup" - ) + @LinkRest(name = CommunityRest.COLLECTIONS, method = "getCollections"), + @LinkRest(name = CommunityRest.LOGO, method = "getLogo"), + @LinkRest(name = CommunityRest.SUBCOMMUNITIES, method = "getSubcommunities"), + @LinkRest(name = CommunityRest.PARENT_COMMUNITY, method = "getParentCommunity"), + @LinkRest(name = CommunityRest.ADMIN_GROUP, method = "getAdminGroup") }) public class CommunityRest extends DSpaceObjectRest { public static final String NAME = "community"; @@ -59,6 +44,11 @@ public String getType() { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + private int archivedItemsCount; public int getArchivedItemsCount() { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ContentReportSupportRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ContentReportSupportRest.java new file mode 100644 index 000000000000..3062fdf0fade --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ContentReportSupportRest.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.rest.model; + +import org.dspace.app.rest.ContentReportRestController; + +public class ContentReportSupportRest extends BaseObjectRest { + + private static final long serialVersionUID = 9137258312781361906L; + public static final String NAME = "contentreport"; + public static final String CATEGORY = RestModel.CONTENT_REPORT; + + @Override + public String getCategory() { + return CATEGORY; + } + + @Override + public Class getController() { + return ContentReportRestController.class; + } + + @Override + public String getType() { + return NAME; + } + + @Override + public String getTypePlural() { + return NAME; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/CorrectionTypeQAEventMessageRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/CorrectionTypeQAEventMessageRest.java new file mode 100644 index 000000000000..6929f390712c --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/CorrectionTypeQAEventMessageRest.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.rest.model; + +/** + * The CorrectionTypeQAEventMessageRest class implements the QAEventMessageRest + * interface and represents a message structure for Quality Assurance (QA) + * events related to correction types. + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com) + */ +public class CorrectionTypeQAEventMessageRest implements QAEventMessageRest { + + private String reason; + + public String getReason() { + return reason; + } + + public void setReason(String reason) { + this.reason = reason; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/CorrectionTypeRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/CorrectionTypeRest.java new file mode 100644 index 000000000000..6a26313f9220 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/CorrectionTypeRest.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.app.rest.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import org.dspace.app.rest.RestResourceController; + +/** + * The CorrectionType REST Resource + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + */ +public class CorrectionTypeRest extends BaseObjectRest { + + private static final long serialVersionUID = -8297846719538025938L; + + public static final String NAME = "correctiontype"; + public static final String PLURAL_NAME = "correctiontypes"; + public static final String CATEGORY = RestAddressableModel.CONFIGURATION; + + private String topic; + + public String getTopic() { + return topic; + } + + public void setTopic(String topic) { + this.topic = topic; + } + + @Override + public String getCategory() { + return CATEGORY; + } + + @Override + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + public String getType() { + return NAME; + } + + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + + @Override + public Class getController() { + return RestResourceController.class; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/DiscoveryResultsRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/DiscoveryResultsRest.java index bf1d513a8160..e63c94edc3c3 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/DiscoveryResultsRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/DiscoveryResultsRest.java @@ -21,6 +21,7 @@ public abstract class DiscoveryResultsRest extends BaseObjectRest { @JsonIgnore public static final String NAME = "discover"; + public static final String PLURAL_NAME = NAME; public static final String CATEGORY = RestModel.DISCOVER; private String scope; private String query; @@ -40,6 +41,14 @@ public String getType() { return NAME; } + /** + * The plural name is the same as the singular name + */ + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + public Class getController() { return DiscoveryRestController.class; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/EPersonRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/EPersonRest.java index 7b4c683322a9..db243400259d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/EPersonRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/EPersonRest.java @@ -20,13 +20,11 @@ * @author Andrea Bollini (andrea.bollini at 4science.it) */ @LinksRest(links = { - @LinkRest( - name = EPersonRest.GROUPS, - method = "getGroups" - ) + @LinkRest(name = EPersonRest.GROUPS, method = "getGroups") }) public class EPersonRest extends DSpaceObjectRest { public static final String NAME = "eperson"; + public static final String PLURAL_NAME = "epersons"; public static final String CATEGORY = RestAddressableModel.EPERSON; public static final String GROUPS = "groups"; @@ -52,6 +50,11 @@ public String getType() { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + public String getNetid() { return netid; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/EntityTypeRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/EntityTypeRest.java index f777b3a29c18..e73aa709180d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/EntityTypeRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/EntityTypeRest.java @@ -15,18 +15,15 @@ * Refer to {@link org.dspace.content.EntityType} for explanation of the properties */ @LinksRest(links = { - @LinkRest( - name = EntityTypeRest.RELATION_SHIP_TYPES, - method = "getEntityTypeRelationship" - ) + @LinkRest(name = EntityTypeRest.RELATION_SHIP_TYPES, method = "getEntityTypeRelationship") }) public class EntityTypeRest extends BaseObjectRest { private static final long serialVersionUID = 8166078961459192770L; public static final String NAME = "entitytype"; - public static final String NAME_PLURAL = "entitytypes"; - public static final String CATEGORY = "core"; + public static final String PLURAL_NAME = "entitytypes"; + public static final String CATEGORY = RestModel.CORE; public static final String RELATION_SHIP_TYPES = "relationshiptypes"; public String getCategory() { @@ -41,6 +38,11 @@ public String getType() { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + private String label; public String getLabel() { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ExternalSourceEntryRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ExternalSourceEntryRest.java index 06af7e222713..34253d538a56 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ExternalSourceEntryRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ExternalSourceEntryRest.java @@ -33,6 +33,11 @@ public String getType() { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + private String id; private String display; private String value; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ExternalSourceRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ExternalSourceRest.java index 639c2cf72e2e..21f41241b293 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ExternalSourceRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ExternalSourceRest.java @@ -13,10 +13,7 @@ * This class serves as a REST representation for an External Source */ @LinksRest(links = { - @LinkRest( - name = ExternalSourceRest.ENTITY_TYPES, - method = "getSupportedEntityTypes" - ) + @LinkRest(name = ExternalSourceRest.ENTITY_TYPES, method = "getSupportedEntityTypes") }) public class ExternalSourceRest extends BaseObjectRest { @@ -42,6 +39,11 @@ public String getType() { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + private String id; private String name; private boolean hierarchical; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/FacetConfigurationRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/FacetConfigurationRest.java index 4b8244eba2db..06068b34e022 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/FacetConfigurationRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/FacetConfigurationRest.java @@ -21,6 +21,7 @@ public class FacetConfigurationRest extends BaseObjectRest { public static final String NAME = "discover"; + public static final String PLURAL_NAME = NAME; public static final String CATEGORY = RestModel.DISCOVER; private String scope; @@ -38,6 +39,14 @@ public String getType() { return NAME; } + /** + * The plural name is the same as the singular name + */ + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + public Class getController() { return DiscoveryRestController.class; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/FeedbackRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/FeedbackRest.java index 00f1e92c87c2..434f5755e8f4 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/FeedbackRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/FeedbackRest.java @@ -19,6 +19,7 @@ public class FeedbackRest extends BaseObjectRest { private static final long serialVersionUID = 1L; public static final String NAME = "feedback"; + public static final String PLURAL_NAME = "feedbacks"; public static final String CATEGORY = RestAddressableModel.TOOLS; private String page; @@ -61,6 +62,11 @@ public String getType() { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @Override public String getCategory() { return CATEGORY; @@ -72,4 +78,4 @@ public Class getController() { return RestResourceController.class; } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/FilteredCollectionRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/FilteredCollectionRest.java new file mode 100644 index 000000000000..47ff61db647d --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/FilteredCollectionRest.java @@ -0,0 +1,104 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.rest.model; + +import java.util.EnumMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +import com.fasterxml.jackson.annotation.JsonProperty; +import org.dspace.contentreport.Filter; +import org.dspace.contentreport.FilteredCollection; + +/** + * This class serves as a REST representation of a single Collection in a {@link FilteredCollectionsRest} + * from the DSpace statistics. It takes its values from a @link FilteredCollection} instance. + * It must not extend BaseObjectRest. + * + * @author Jean-François Morin (Université Laval) + */ +public class FilteredCollectionRest { + + public static final String NAME = "filtered-collection"; + public static final String CATEGORY = RestModel.CONTENT_REPORT; + + /** 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 */ + @JsonProperty("community_label") + private String communityLabel; + /** Handle of the owning community, used to make it clickable from the generated report */ + @JsonProperty("community_handle") + private String communityHandle; + /** Total number of items in the collection */ + @JsonProperty("nb_total_items") + 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 */ + @JsonProperty("all_filters_value") + private int allFiltersValue; + + /** + * Builds a FilteredCollectionRest instance from a {@link FilteredCollection} instance. + * @param model the FilteredCollection instance that provides values to the + * FilteredCollectionRest instance to be created + * @return a FilteredCollectionRest instance built from the provided model object + */ + public static FilteredCollectionRest of(FilteredCollection model) { + Objects.requireNonNull(model); + + var coll = new FilteredCollectionRest(); + coll.label = model.getLabel(); + coll.handle = model.getHandle(); + coll.communityLabel = model.getCommunityLabel(); + coll.communityHandle = model.getCommunityHandle(); + coll.totalItems = model.getTotalItems(); + coll.allFiltersValue = model.getAllFiltersValue(); + Optional.ofNullable(model.getValues()).ifPresent(coll.values::putAll); + + return coll; + } + + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + public String getType() { + return NAME; + } + + public Map getValues() { + return values; + } + + public String getLabel() { + return label; + } + + public String getHandle() { + return handle; + } + + public String getCommunityLabel() { + return communityLabel; + } + + public String getCommunityHandle() { + return communityHandle; + } + + public int getTotalItems() { + return totalItems; + } + + public int getAllFiltersValue() { + return allFiltersValue; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/FilteredCollectionsQuery.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/FilteredCollectionsQuery.java new file mode 100644 index 000000000000..059516da13e3 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/FilteredCollectionsQuery.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.rest.model; + +import java.util.Collection; +import java.util.EnumSet; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +import org.dspace.contentreport.Filter; + +/** + * Structured query contents for the Filtered Collections report + * @author Jean-François Morin (Université Laval) + */ +public class FilteredCollectionsQuery { + + private Set filters = EnumSet.noneOf(Filter.class); + + /** + * Shortcut method that builds a FilteredCollectionsQuery instance + * from its building blocks. + * @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. + * @return a FilteredCollectionsQuery instance built from the provided parameters + */ + public static FilteredCollectionsQuery of(Collection filters) { + var query = new FilteredCollectionsQuery(); + Optional.ofNullable(filters).ifPresent(query.filters::addAll); + return query; + } + + public Set getFilters() { + return filters; + } + + public void setFilters(Set filters) { + this.filters = filters; + } + + public String toQueryString() { + return filters.stream() + .map(f -> "filters=" + f.getId()) + .collect(Collectors.joining("&")); + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/FilteredCollectionsRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/FilteredCollectionsRest.java new file mode 100644 index 000000000000..2d808e4a3189 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/FilteredCollectionsRest.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.rest.model; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import org.dspace.app.rest.ContentReportRestController; +import org.dspace.contentreport.FilteredCollections; + +/** + * This class serves as a REST representation of a Filtered Collections Report. + * The name must match that of the associated resource class (FilteredCollectionsResource) except for + * the suffix. This is why it is not named something like FilteredCollectionsReportRest. + * + * @author Jean-François Morin (Université Laval) + */ +public class FilteredCollectionsRest extends BaseObjectRest { + + private static final long serialVersionUID = -1109226348211060786L; + /** Type of instances of this class, used by the DSpace REST infrastructure */ + public static final String NAME = "filteredcollectionsreport"; + /** Category of instances of this class, used by the DSpace REST infrastructure */ + public static final String CATEGORY = RestModel.CONTENT_REPORT; + + /** Collections included in the report */ + private List collections = new ArrayList<>(); + /** Report summary */ + private FilteredCollectionRest summary; + + /** + * Builds a FilteredCollectionsRest instance from a {@link FilteredCollections} instance. + * Each underlying FilteredCollection is converted to a FilteredCollectionRest instance. + * @param model the FilteredCollections instance that provides values to the + * FilteredCollectionsRest instance to be created + * @return a FilteredCollectionsRest instance built from the provided model object + */ + public static FilteredCollectionsRest of(FilteredCollections model) { + var colls = new FilteredCollectionsRest(); + Optional.ofNullable(model.getCollections()).ifPresent(cs -> + cs.stream() + .map(FilteredCollectionRest::of) + .forEach(colls.collections::add)); + colls.summary = FilteredCollectionRest.of(model.getSummary()); + return colls; + } + + @Override + public String getCategory() { + return CATEGORY; + } + + @Override + public Class getController() { + return ContentReportRestController.class; + } + + @Override + public String getType() { + return NAME; + } + + @Override + public String getTypePlural() { + return getType(); + } + + public List getCollections() { + return collections; + } + + public FilteredCollectionRest getSummary() { + return summary; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/FilteredItemRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/FilteredItemRest.java new file mode 100644 index 000000000000..b5d512949210 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/FilteredItemRest.java @@ -0,0 +1,128 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.rest.model; + +import java.util.Date; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Specialization of ItemRest dedicated to the Filtered Items report. + * This class adds the owning collection property required to properly + * display search results without compromising the expected behaviour + * of standard ItemRest instances, in all other contexts, especially + * when it comes to embedded contents, a criterion that is widely checked + * against in several integration tests. + * + * @author Jean-François Morin (jean-francois.morin@bibl.ulaval.ca) + */ +public class FilteredItemRest { + + public static final String NAME = "filtered-item"; + public static final String CATEGORY = RestAddressableModel.CONTENT_REPORT; + + public static final String OWNING_COLLECTION = "owningCollection"; + + private String uuid; + private String name; + private String handle; + MetadataRest metadata = new MetadataRest(); + private boolean inArchive = false; + private boolean discoverable = false; + private boolean withdrawn = false; + private Date lastModified = new Date(); + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + private String entityType = null; + private CollectionRest owningCollection; + + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + public String getType() { + return NAME; + } + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + 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; + } + + public MetadataRest getMetadata() { + return metadata; + } + + public void setMetadata(MetadataRest metadata) { + this.metadata = metadata; + } + + public boolean getInArchive() { + return inArchive; + } + + public void setInArchive(boolean inArchive) { + this.inArchive = inArchive; + } + + public boolean getDiscoverable() { + return discoverable; + } + + public void setDiscoverable(boolean discoverable) { + this.discoverable = discoverable; + } + + public boolean getWithdrawn() { + return withdrawn; + } + + public void setWithdrawn(boolean withdrawn) { + this.withdrawn = withdrawn; + } + + public Date getLastModified() { + return lastModified; + } + + public void setLastModified(Date lastModified) { + this.lastModified = lastModified; + } + + public String getEntityType() { + return entityType; + } + + public void setEntityType(String entityType) { + this.entityType = entityType; + } + + public CollectionRest getOwningCollection() { + return owningCollection; + } + + public void setOwningCollection(CollectionRest owningCollection) { + this.owningCollection = owningCollection; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/FilteredItemsQueryPredicate.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/FilteredItemsQueryPredicate.java new file mode 100644 index 000000000000..fe7192e56252 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/FilteredItemsQueryPredicate.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.rest.model; + +import java.util.Optional; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.contentreport.QueryOperator; + +/** + * Data structure representing a query predicate used by the Filtered Items report + * to filter items to retrieve. This version is specific to the REST layer and its + * property types are detached from the persistence layer. + * @see org.dspace.contentreport.QueryPredicate + * @author Jean-François Morin (Université Laval) + */ +public class FilteredItemsQueryPredicate { + + private String field; + private QueryOperator operator; + private String value; + + /** + * Shortcut method that builds a FilteredItemsQueryPredicate from a single field, an operator, and a value. + * @param field Predicate subject + * @param operator Predicate operator + * @param value Predicate object + * @return a FilteredItemsQueryPredicate instance built from the provided parameters + */ + public static FilteredItemsQueryPredicate of(String field, QueryOperator operator, String value) { + var predicate = new FilteredItemsQueryPredicate(); + predicate.field = field; + predicate.operator = operator; + predicate.value = value; + return predicate; + } + + /** + * Shortcut method that builds a FilteredItemsQueryPredicate from a colon-separated string value. + * @param value Colon-separated string value (field:operator:object or field:operator) + * @return a FilteredItemsQueryPredicate instance built from the provided value + */ + public static FilteredItemsQueryPredicate of(String value) { + String[] tokens = value.split("\\:"); + String field = tokens.length > 0 ? tokens[0].trim() : ""; + QueryOperator operator = tokens.length > 1 ? QueryOperator.get(tokens[1].trim()) : null; + String object = tokens.length > 2 ? StringUtils.trimToEmpty(tokens[2]) : ""; + return of(field, operator, object); + } + + public String getField() { + return field; + } + + public QueryOperator getOperator() { + return operator; + } + + public String getValue() { + return value; + } + + @Override + public String toString() { + String op = Optional.ofNullable(operator).map(QueryOperator::getCode).orElse(""); + return field + ":" + op + ":" + value; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/FilteredItemsQueryRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/FilteredItemsQueryRest.java new file mode 100644 index 000000000000..973dc9738b14 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/FilteredItemsQueryRest.java @@ -0,0 +1,148 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.rest.model; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.EnumSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.contentreport.Filter; +import org.dspace.contentreport.QueryOperator; + +/** + * REST-based version of structured query contents for the Filtered Items report + * @author Jean-François Morin (Université Laval) + */ +public class FilteredItemsQueryRest { + + private List collections = new ArrayList<>(); + private List queryPredicates = new ArrayList<>(); + private int pageLimit; + private Set filters = EnumSet.noneOf(Filter.class); + private List additionalFields = new ArrayList<>(); + + /** + * Shortcut method that builds a FilteredItemsQueryRest 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 FilteredItemsQueryRest instance built from the provided parameters + */ + public static FilteredItemsQueryRest of(Collection collectionUuids, + Collection predicates, int pageLimit, + Collection filters, Collection additionalFields) { + var query = new FilteredItemsQueryRest(); + Optional.ofNullable(collectionUuids).ifPresent(query.collections::addAll); + Optional.ofNullable(predicates).ifPresent(query.queryPredicates::addAll); + 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 = collections; + } + + public List getQueryPredicates() { + return queryPredicates; + } + + public void setQueryPredicates(List queryPredicates) { + this.queryPredicates = queryPredicates; + } + + public List getPredicateFields() { + if (queryPredicates == null) { + return Collections.emptyList(); + } + return queryPredicates.stream() + .map(FilteredItemsQueryPredicate::getField) + .collect(Collectors.toList()); + } + + public List getPredicateOperators() { + if (queryPredicates == null) { + return Collections.emptyList(); + } + return queryPredicates.stream() + .map(FilteredItemsQueryPredicate::getOperator) + .collect(Collectors.toList()); + } + + public List getPredicateValues() { + if (queryPredicates == null) { + return Collections.emptyList(); + } + return queryPredicates.stream() + .map(FilteredItemsQueryPredicate::getValue) + .map(s -> s == null ? "" : s) + .collect(Collectors.toList()); + } + + 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 = filters; + } + + public List getAdditionalFields() { + return additionalFields; + } + + public void setAdditionalFields(List additionalFields) { + this.additionalFields = additionalFields; + } + + public String toQueryString() { + String colls = collections.stream() + .map(coll -> "collection=" + coll) + .collect(Collectors.joining("&")); + String preds = queryPredicates.stream() + .map(pred -> "queryPredicates=" + pred) + .collect(Collectors.joining("&")); + String pgLimit = "pageLimit=" + pageLimit; + String fltrs = filters.stream() + .map(e -> "filters=" + e.getId()) + .collect(Collectors.joining("&")); + String flds = additionalFields.stream() + .map(fld -> "additionalFields=" + fld) + .collect(Collectors.joining("&")); + + return Stream.of(colls, preds, pgLimit, fltrs, flds) + .filter(StringUtils::isNotBlank) + .collect(Collectors.joining("&")); + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/FilteredItemsRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/FilteredItemsRest.java new file mode 100644 index 000000000000..e879a9b9cc31 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/FilteredItemsRest.java @@ -0,0 +1,88 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.rest.model; + +import java.util.ArrayList; +import java.util.List; + +import org.dspace.app.rest.ContentReportRestController; + +/** + * This class serves as a REST representation of a Filtered Items Report. + * The name must match that of the associated resource class (FilteredItemsResource) except for + * the suffix. This is why it is not named something like FilteredItemsReportRest. + * + * @author Jean-François Morin (Université Laval) + */ +public class FilteredItemsRest extends BaseObjectRest { + + private static final long serialVersionUID = -2483812920345013458L; + /** Type of instances of this class, used by the DSpace REST infrastructure */ + public static final String NAME = "filtereditemsreport"; + /** Category of instances of this class, used by the DSpace REST infrastructure */ + public static final String CATEGORY = RestModel.CONTENT_REPORT; + + /** Items included in the report */ + private List items = new ArrayList<>(); + /** Total item count (for pagination) */ + private long itemCount; + + /** + * Builds a FilteredItemsRest instance from a list of items and an total item count. + * To avoid adding a dependency to any Spring-managed service here, the items + * provided here are already converted to FilteredItemRest instances. + * @param items the items to add to the FilteredItemsRest instance to be created + * @param itemCount total number of items found regardless of any pagination constraint + * @return a FilteredItemsRest instance built from the provided data + */ + public static FilteredItemsRest of(List items, long itemCount) { + var itemsRest = new FilteredItemsRest(); + itemsRest.items.addAll(items); + itemsRest.itemCount = itemCount; + return itemsRest; + } + + @Override + public String getCategory() { + return CATEGORY; + } + + /** + * Return controller class responsible for this Rest object + * + * @return Controller class responsible for this Rest object + */ + @Override + public Class getController() { + return ContentReportRestController.class; + } + + @Override + public String getType() { + return NAME; + } + + @Override + public String getTypePlural() { + return getType(); + } + + /** + * 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); + } + + public long getItemCount() { + return itemCount; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/GroupRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/GroupRest.java index 3f60b2d61fd6..0a4963b66fa0 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/GroupRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/GroupRest.java @@ -18,24 +18,14 @@ */ @JsonIgnoreProperties(ignoreUnknown = true) @LinksRest(links = { - @LinkRest( - name = GroupRest.SUBGROUPS, - method = "getGroups" - ), - @LinkRest( - name = GroupRest.EPERSONS, - method = "getMembers" - ), - @LinkRest( - name = GroupRest.OBJECT, - method = "getParentObject" - ) + @LinkRest(name = GroupRest.SUBGROUPS, method = "getGroups"), + @LinkRest(name = GroupRest.EPERSONS, method = "getMembers"), + @LinkRest(name = GroupRest.OBJECT, method = "getParentObject") }) public class GroupRest extends DSpaceObjectRest { public static final String NAME = "group"; + public static final String PLURAL_NAME = "groups"; public static final String CATEGORY = RestAddressableModel.EPERSON; - - public static final String GROUPS = "groups"; public static final String SUBGROUPS = "subgroups"; public static final String EPERSONS = "epersons"; public static final String OBJECT = "object"; @@ -54,6 +44,11 @@ public String getType() { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + public String getName() { return name; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/HarvestedCollectionRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/HarvestedCollectionRest.java index a4a0aac0131f..ae3711b95010 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/HarvestedCollectionRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/HarvestedCollectionRest.java @@ -20,8 +20,9 @@ */ public class HarvestedCollectionRest extends BaseObjectRest { - public static final String NAME = "collections"; - public static final String CATEGORY = "core"; + public static final String NAME = "harvestedcollection"; + public static final String PLURAL_NAME = "harvestedcollections"; + public static final String CATEGORY = RestModel.CORE; @JsonProperty("harvest_type") private HarvestTypeEnum harvestType; @@ -70,6 +71,11 @@ public String getType() { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @JsonIgnore public CollectionRest getCollectionRest() { return this.collectionRest; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/HarvesterMetadataRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/HarvesterMetadataRest.java index a32e901d74c5..f130f4a8198d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/HarvesterMetadataRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/HarvesterMetadataRest.java @@ -21,8 +21,9 @@ */ public class HarvesterMetadataRest extends BaseObjectRest { - public static final String CATEGORY = "config"; + public static final String CATEGORY = RestModel.CONFIGURATION; public static final String NAME = "harvesterMetadata"; + public static final String PLURAL_NAME = NAME; private List> configs; @@ -42,6 +43,14 @@ public String getType() { return NAME; } + /** + * The plural name is the same as the singular name + */ + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + public Class getController() { return HarvesterMetadataController.class; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/IdentifierRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/IdentifierRest.java index 6cfb147ea314..cf16184acc99 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/IdentifierRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/IdentifierRest.java @@ -21,6 +21,7 @@ public class IdentifierRest extends BaseObjectRest implements RestModel // Set names used in component wiring public static final String NAME = "identifier"; public static final String PLURAL_NAME = "identifiers"; + public static final String CATEGORY = RestModel.PID; private String value; private String identifierType; private String identifierStatus; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/IdentifiersRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/IdentifiersRest.java index 169e40979c98..5414ac10b643 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/IdentifiersRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/IdentifiersRest.java @@ -22,6 +22,8 @@ public class IdentifiersRest extends BaseObjectRest { // Set names used in component wiring public static final String NAME = "identifiers"; + public static final String PLURAL_NAME = "identifiers"; + private List identifiers; // Empty constructor @@ -37,6 +39,11 @@ public String getType() { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + public List getIdentifiers() { return identifiers; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ItemFilterRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ItemFilterRest.java new file mode 100644 index 000000000000..5d46c62522e8 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ItemFilterRest.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.app.rest.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import org.dspace.app.rest.RestResourceController; + +/** + * The ItemFilter REST Resource + * + * @author mohamed eskander (mohamed.eskander at 4science.com) + */ +public class ItemFilterRest extends BaseObjectRest { + public static final String NAME = "itemfilter"; + public static final String PLURAL_NAME = "itemfilters"; + public static final String CATEGORY = RestAddressableModel.CONFIGURATION; + + @Override + public String getCategory() { + return CATEGORY; + } + + @Override + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + public String getType() { + return NAME; + } + + public Class getController() { + return RestResourceController.class; + } + + @Override + public String getTypePlural() { + return PLURAL_NAME; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ItemRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ItemRest.java index 1254ef8f9372..a47667441cc8 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ItemRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ItemRest.java @@ -17,42 +17,16 @@ * @author Andrea Bollini (andrea.bollini at 4science.it) */ @LinksRest(links = { - @LinkRest( - name = ItemRest.ACCESS_STATUS, - method = "getAccessStatus" - ), - @LinkRest( - name = ItemRest.BUNDLES, - method = "getBundles" - ), - @LinkRest( - name = ItemRest.IDENTIFIERS, - method = "getIdentifiers" - ), - @LinkRest( - name = ItemRest.MAPPED_COLLECTIONS, - method = "getMappedCollections" - ), - @LinkRest( - name = ItemRest.OWNING_COLLECTION, - method = "getOwningCollection" - ), - @LinkRest( - name = ItemRest.RELATIONSHIPS, - method = "getRelationships" - ), - @LinkRest( - name = ItemRest.VERSION, - method = "getItemVersion" - ), - @LinkRest( - name = ItemRest.TEMPLATE_ITEM_OF, - method = "getTemplateItemOf" - ), - @LinkRest( - name = ItemRest.THUMBNAIL, - method = "getThumbnail" - ) + @LinkRest(name = ItemRest.ACCESS_STATUS, method = "getAccessStatus"), + @LinkRest(name = ItemRest.BUNDLES, method = "getBundles"), + @LinkRest(name = ItemRest.IDENTIFIERS, method = "getIdentifiers"), + @LinkRest(name = ItemRest.MAPPED_COLLECTIONS, method = "getMappedCollections"), + @LinkRest(name = ItemRest.OWNING_COLLECTION, method = "getOwningCollection"), + @LinkRest(name = ItemRest.RELATIONSHIPS, method = "getRelationships"), + @LinkRest(name = ItemRest.VERSION, method = "getItemVersion"), + @LinkRest(name = ItemRest.TEMPLATE_ITEM_OF, method = "getTemplateItemOf"), + @LinkRest(name = ItemRest.THUMBNAIL, method = "getThumbnail"), + @LinkRest(name = ItemRest.SUBMITTER, method = "getItemSubmitter") }) public class ItemRest extends DSpaceObjectRest { public static final String NAME = "item"; @@ -69,6 +43,8 @@ public class ItemRest extends DSpaceObjectRest { public static final String TEMPLATE_ITEM_OF = "templateItemOf"; public static final String THUMBNAIL = "thumbnail"; + public static final String SUBMITTER = "submitter"; + private boolean inArchive = false; private boolean discoverable = false; private boolean withdrawn = false; @@ -87,6 +63,11 @@ public String getType() { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + public boolean getInArchive() { return inArchive; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/LDNMessageEntityRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/LDNMessageEntityRest.java new file mode 100644 index 000000000000..336f2c63c343 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/LDNMessageEntityRest.java @@ -0,0 +1,200 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.rest.model; + +import java.util.Date; +import java.util.UUID; + +import org.dspace.app.rest.RestResourceController; + +/** + * The LDN Message REST resource. + * @author Stefano Maffei (stefano.maffei at 4science.com) + */ +@SuppressWarnings("serial") +public class LDNMessageEntityRest extends BaseObjectRest { + + public static final String NAME = "message"; + public static final String NAME_PLURALS = "messages"; + public static final String CATEGORY = RestAddressableModel.LDN; + + private String notificationId; + + private Integer queueStatus; + + private String queueStatusLabel; + + private UUID context; + + private UUID object; + + private Integer target; + + private Integer origin; + + private String inReplyTo; + + private String activityStreamType; + + private String coarNotifyType; + + private Integer queueAttempts; + + private Date queueLastStartTime; + + private Date queueTimeout; + + private String notificationType; + + private String message; + + public LDNMessageEntityRest() { + super(); + } + + @Override + public String getType() { + return NAME; + } + + @Override + public String getTypePlural() { + return NAME_PLURALS; + } + + @Override + public String getCategory() { + return CATEGORY; + } + + @Override + public Class getController() { + return RestResourceController.class; + } + + public String getNotificationId() { + return notificationId; + } + + public void setNotificationId(String notificationId) { + this.notificationId = notificationId; + } + + public Integer getQueueStatus() { + return queueStatus; + } + + public void setQueueStatus(Integer queueStatus) { + this.queueStatus = queueStatus; + } + + public String getQueueStatusLabel() { + return queueStatusLabel; + } + + public void setQueueStatusLabel(String queueStatusLabel) { + this.queueStatusLabel = queueStatusLabel; + } + + public UUID getContext() { + return context; + } + + public void setContext(UUID context) { + this.context = context; + } + + public UUID getObject() { + return object; + } + + public void setObject(UUID object) { + this.object = object; + } + + public Integer getTarget() { + return target; + } + + public void setTarget(Integer target) { + this.target = target; + } + + public Integer getOrigin() { + return origin; + } + + public void setOrigin(Integer source) { + this.origin = source; + } + + 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; + } + + 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 getNotificationType() { + return notificationType; + } + + public void setNotificationType(String notificationType) { + this.notificationType = notificationType; + } + + public String getInReplyTo() { + return inReplyTo; + } + + public void setInReplyTo(String inReplyTo) { + this.inReplyTo = inReplyTo; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/LicenseRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/LicenseRest.java index 8f8ea51086cf..863335ee882e 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/LicenseRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/LicenseRest.java @@ -14,6 +14,8 @@ */ public class LicenseRest implements RestModel { public static final String NAME = "license"; + public static final String PLURAL_NAME = "licenses"; + private boolean custom = false; private String text; @@ -37,4 +39,9 @@ public void setText(String text) { public String getType() { return NAME; } + + @Override + public String getTypePlural() { + return PLURAL_NAME; + } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/MetadataFieldRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/MetadataFieldRest.java index 4524f82a68cb..f73e51cb7e27 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/MetadataFieldRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/MetadataFieldRest.java @@ -18,7 +18,7 @@ */ public class MetadataFieldRest extends BaseObjectRest { public static final String NAME = "metadatafield"; - public static final String NAME_PLURAL = "metadatafields"; + public static final String PLURAL_NAME = "metadatafields"; public static final String CATEGORY = RestAddressableModel.CORE; @JsonIgnore @@ -68,6 +68,11 @@ public String getType() { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @Override public Class getController() { return RestResourceController.class; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/MetadataSchemaRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/MetadataSchemaRest.java index 655d4c86d87f..c762ccc65bd0 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/MetadataSchemaRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/MetadataSchemaRest.java @@ -17,6 +17,7 @@ */ public class MetadataSchemaRest extends BaseObjectRest { public static final String NAME = "metadataschema"; + public static final String PLURAL_NAME = "metadataschemas"; public static final String CATEGORY = RestAddressableModel.CORE; private String prefix; @@ -45,6 +46,11 @@ public String getType() { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @Override public Class getController() { return RestResourceController.class; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/NotifyQAEventMessageRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/NotifyQAEventMessageRest.java new file mode 100644 index 000000000000..e9acb3c7757e --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/NotifyQAEventMessageRest.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.rest.model; + +/** + * Implementation of {@link QAEventMessageRest} related to COAR NOTIFY events. + * + * @author Francesco Bacchelli (francesco.bacchelli at 4science.it) + * + */ +public class NotifyQAEventMessageRest implements QAEventMessageRest { + + 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-server-webapp/src/main/java/org/dspace/app/rest/model/NotifyRequestStatusRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/NotifyRequestStatusRest.java new file mode 100644 index 000000000000..c8b5070de351 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/NotifyRequestStatusRest.java @@ -0,0 +1,83 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.rest.model; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import org.dspace.app.ldn.model.RequestStatus; +import org.dspace.app.rest.NotifyRequestStatusRestController; + + +/** + * Rest entity for LDN requests targeting items + * + * @author Francesco Bacchelli (francesco.bacchelli at 4science dot it) + */ +@JsonPropertyOrder(value = { + "notifyStatus", + "itemuuid" +}) +public class NotifyRequestStatusRest extends RestAddressableModel { + + private static final long serialVersionUID = 1L; + public static final String CATEGORY = RestAddressableModel.LDN; + public static final String NAME = "notifyrequests"; + public static final String PLURAL_NAME = "notifyrequests"; + + private List notifyStatus; + private UUID itemuuid; + + public NotifyRequestStatusRest(NotifyRequestStatusRest instance) { + this.notifyStatus = instance.getNotifyStatus(); + } + + public NotifyRequestStatusRest() { + this.notifyStatus = new ArrayList(); + } + + public UUID getItemuuid() { + return itemuuid; + } + + public void setItemuuid(UUID itemuuid) { + this.itemuuid = itemuuid; + } + + @Override + public String getCategory() { + return CATEGORY; + } + + @Override + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + public String getType() { + return NAME; + } + + public Class getController() { + return NotifyRequestStatusRestController.class; + } + + public List getNotifyStatus() { + return notifyStatus; + } + + public void setNotifyStatus(List notifyStatus) { + this.notifyStatus = notifyStatus; + } + + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/NotifyServiceInboundPatternRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/NotifyServiceInboundPatternRest.java new file mode 100644 index 000000000000..43090838695b --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/NotifyServiceInboundPatternRest.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.app.rest.model; + + +/** + * representation of the Notify Service Inbound Pattern + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + */ +public class NotifyServiceInboundPatternRest { + + /** + * https://notify.coar-repositories.org/patterns/ + */ + private String pattern; + + /** + * the id of a bean implementing the ItemFilter + */ + private String constraint; + + /** + * means that the pattern is triggered automatically + * by dspace if the item respect the filter + */ + private boolean automatic; + + public String getPattern() { + return pattern; + } + + public void setPattern(String pattern) { + this.pattern = pattern; + } + + public String getConstraint() { + return constraint; + } + + public void setConstraint(String constraint) { + this.constraint = constraint; + } + + public boolean isAutomatic() { + return automatic; + } + + public void setAutomatic(boolean automatic) { + this.automatic = automatic; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/NotifyServiceRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/NotifyServiceRest.java new file mode 100644 index 000000000000..8a50c913d5c8 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/NotifyServiceRest.java @@ -0,0 +1,129 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.rest.model; + +import java.math.BigDecimal; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonProperty; +import org.dspace.app.rest.RestResourceController; + +/** + * The NotifyServiceEntity REST Resource + * + * @author mohamed eskander (mohamed.eskander at 4science.com) + */ +public class NotifyServiceRest extends BaseObjectRest { + public static final String NAME = "ldnservice"; + public static final String PLURAL_NAME = "ldnservices"; + public static final String CATEGORY = RestAddressableModel.LDN; + + private String name; + private String description; + private String url; + private String ldnUrl; + private boolean enabled; + private BigDecimal score; + private String lowerIp; + private String upperIp; + + private List notifyServiceInboundPatterns; + + @Override + public String getCategory() { + return CATEGORY; + } + + @Override + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + public String getType() { + return NAME; + } + + public Class getController() { + return RestResourceController.class; + } + + 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; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getLdnUrl() { + return ldnUrl; + } + + public void setLdnUrl(String ldnUrl) { + this.ldnUrl = ldnUrl; + } + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + public List getNotifyServiceInboundPatterns() { + return notifyServiceInboundPatterns; + } + + public void setNotifyServiceInboundPatterns( + List notifyServiceInboundPatterns) { + this.notifyServiceInboundPatterns = notifyServiceInboundPatterns; + } + + 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; + } + + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/OrcidHistoryRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/OrcidHistoryRest.java index 02e0f4706208..433d5626ca42 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/OrcidHistoryRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/OrcidHistoryRest.java @@ -25,6 +25,7 @@ public class OrcidHistoryRest extends BaseObjectRest { public static final String CATEGORY = RestModel.EPERSON; public static final String NAME = "orcidhistory"; + public static final String PLURAL_NAME = "orcidhistories"; private UUID profileItemId; @@ -38,7 +39,7 @@ public class OrcidHistoryRest extends BaseObjectRest { private String responseMessage; - public OrcidHistoryRest(){} + public OrcidHistoryRest() {} @Override @JsonProperty(access = JsonProperty.Access.READ_ONLY) @@ -46,6 +47,11 @@ public String getType() { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @Override public String getCategory() { return CATEGORY; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/OrcidQueueRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/OrcidQueueRest.java index ba7c30062803..13a0b474b6c1 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/OrcidQueueRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/OrcidQueueRest.java @@ -35,6 +35,11 @@ public String getType() { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @Override public String getCategory() { return CATEGORY; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ParameterValueRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ParameterValueRest.java index 6c236fa1769f..72bfeb8a830a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ParameterValueRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ParameterValueRest.java @@ -10,7 +10,7 @@ import org.apache.commons.lang3.StringUtils; /** - * This class serves as a REST representation for a paramater with a value given to the script + * This class serves as a REST representation for a parameter with a value given to the script */ public class ParameterValueRest { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/PoolTaskRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/PoolTaskRest.java index c32c2c95783a..94c70037330e 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/PoolTaskRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/PoolTaskRest.java @@ -17,10 +17,7 @@ * @author Andrea Bollini (andrea.bollini at 4science.it) */ @LinksRest(links = { - @LinkRest( - name = PoolTaskRest.STEP, - method = "getStep" - ) + @LinkRest(name = PoolTaskRest.STEP, method = "getStep") }) public class PoolTaskRest extends BaseObjectRest { public static final String NAME = "pooltask"; @@ -50,6 +47,11 @@ public String getType() { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @Override public Class getController() { return RestResourceController.class; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/PotentialDuplicateRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/PotentialDuplicateRest.java new file mode 100644 index 000000000000..9d706a8d5791 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/PotentialDuplicateRest.java @@ -0,0 +1,199 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.rest.model; + +import java.util.UUID; + +/** + * REST Model defining a Potential Duplicate for serialisation to JSON + * This is used in lists of potential duplicates for submission section data and item link / embeds. + * + * @author Kim Shepherd + */ +public class PotentialDuplicateRest extends RestAddressableModel { + + public static final String CATEGORY = RestModel.SUBMISSION; + public static final String NAME = RestModel.DUPLICATES; + + /** + * Type of REST resource + */ + private static final String TYPE = "DUPLICATE"; + /** + * Plural type of REST resource + */ + private static final String TYPE_PLURAL = "DUPLICATES"; + /** + * 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 copied across from the duplicate item + */ + private MetadataRest metadata; + + /** + * Default constructor + */ + public PotentialDuplicateRest() { + } + + /** + * 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 metadata (sorted, field->value list) for duplicate item + * @return (sorted, field->value list) for duplicate item + */ + public MetadataRest getMetadata() { + return metadata; + } + + /** + * Set metadata (sorted, field->value list) for duplicate item + * @param metadata MetadataRest list of values mapped to field keys + */ + public void setMetadata(MetadataRest metadata) { + this.metadata = metadata; + } + + /** + * 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 REST resource type name + * @return REST resource type (see static final string) + */ + @Override + public String getType() { + return TYPE; + } + + /** + * Get REST resource type plural name + * @return REST resource type plural name (see static final string) + */ + @Override + public String getTypePlural() { + return TYPE_PLURAL; + } + + /** + * Get REST resource category. + * Not implemented as this model is intended for use only as an ItemLink repository and submission section data, + * it is actually a simple RestModel but has to 'implement' RestAddressableModel to serialize correctly + * + * @return null (not implemented) + */ + @Override + public String getCategory() { + return null; + } + + /** + * Get REST controller for this model. + * Not implemented as this model is intended for use only as an ItemLink repository and submission section data, + * it is actually a simple RestModel but has to 'implement' RestAddressableModel to serialize correctly + * + * @return null (not implemented) + */ + @Override + public Class getController() { + return null; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ProcessFileTypesRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ProcessFileTypesRest.java index ecceea107e43..eec9390388f5 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ProcessFileTypesRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ProcessFileTypesRest.java @@ -65,4 +65,9 @@ public Class getController() { public String getType() { return NAME; } + + @Override + public String getTypePlural() { + return PLURAL_NAME; + } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ProcessRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ProcessRest.java index 0344d7db0422..fee104b4e389 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ProcessRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ProcessRest.java @@ -21,18 +21,9 @@ * This class serves as a REST representation for the {@link Process} class */ @LinksRest(links = { - @LinkRest( - name = ProcessRest.FILES, - method = "getFilesFromProcess" - ), - @LinkRest( - name = ProcessRest.FILE_TYPES, - method = "getFileTypesFromProcess" - ), - @LinkRest( - name = ProcessRest.OUTPUT, - method = "getOutputFromProcess" - ) + @LinkRest(name = ProcessRest.FILES, method = "getFilesFromProcess"), + @LinkRest(name = ProcessRest.FILE_TYPES, method = "getFileTypesFromProcess"), + @LinkRest(name = ProcessRest.OUTPUT, method = "getOutputFromProcess") }) public class ProcessRest extends BaseObjectRest { public static final String NAME = "process"; @@ -55,6 +46,11 @@ public String getType() { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + private String scriptName; private UUID userId; private Integer processId; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/PropertyRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/PropertyRest.java index 365a6790192b..c0e846fbb14e 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/PropertyRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/PropertyRest.java @@ -18,6 +18,7 @@ */ public class PropertyRest extends BaseObjectRest { public static final String NAME = "property"; + public static final String PLURAL_NAME = "properties"; public static final String CATEGORY = RestAddressableModel.CONFIGURATION; public String getName() { @@ -59,4 +60,9 @@ public Class getController() { public String getType() { return NAME; } + + @Override + public String getTypePlural() { + return PLURAL_NAME; + } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/QAEventRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/QAEventRest.java index 7a12ade61d80..5a74d335310d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/QAEventRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/QAEventRest.java @@ -19,14 +19,15 @@ */ @LinksRest( links = { - @LinkRest(name = "topic", method = "getTopic"), - @LinkRest(name = "target", method = "getTarget"), - @LinkRest(name = "related", method = "getRelated") + @LinkRest(name = "topic", method = "getTopic"), + @LinkRest(name = "target", method = "getTarget"), + @LinkRest(name = "related", method = "getRelated") }) public class QAEventRest extends BaseObjectRest { private static final long serialVersionUID = -5001130073350654793L; public static final String NAME = "qualityassuranceevent"; + public static final String PLURAL_NAME = "qualityassuranceevents"; public static final String CATEGORY = RestAddressableModel.INTEGRATION; public static final String TOPIC = "topic"; @@ -46,6 +47,11 @@ public String getType() { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @Override public String getCategory() { return CATEGORY; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/QASourceRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/QASourceRest.java index a1480f409cd7..f62bd19f2d41 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/QASourceRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/QASourceRest.java @@ -22,9 +22,9 @@ public class QASourceRest extends BaseObjectRest { private static final long serialVersionUID = -7455358581579629244L; public static final String NAME = "qualityassurancesource"; + public static final String PLURAL_NAME = "qualityassurancesources"; public static final String CATEGORY = RestAddressableModel.INTEGRATION; - private String id; private Date lastEvent; private long totalEvents; @@ -33,6 +33,11 @@ public String getType() { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @Override public String getCategory() { return CATEGORY; @@ -43,14 +48,6 @@ public Class getController() { return RestResourceController.class; } - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - public Date getLastEvent() { return lastEvent; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/QATopicRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/QATopicRest.java index 05820b919482..54bf3c814142 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/QATopicRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/QATopicRest.java @@ -22,6 +22,7 @@ public class QATopicRest extends BaseObjectRest { private static final long serialVersionUID = -7455358581579629244L; public static final String NAME = "qualityassurancetopic"; + public static final String PLURAL_NAME = "qualityassurancetopics"; public static final String CATEGORY = RestAddressableModel.INTEGRATION; private String id; @@ -34,6 +35,11 @@ public String getType() { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @Override public String getCategory() { return CATEGORY; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RegistrationRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RegistrationRest.java index e8397f8ca763..eb7c58a18cba 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RegistrationRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RegistrationRest.java @@ -21,7 +21,7 @@ public class RegistrationRest extends RestAddressableModel { public static final String NAME = "registration"; - public static final String NAME_PLURAL = "registrations"; + public static final String PLURAL_NAME = "registrations"; public static final String CATEGORY = EPERSON; private String email; @@ -74,4 +74,9 @@ public Class getController() { public String getType() { return NAME; } + + @Override + public String getTypePlural() { + return PLURAL_NAME; + } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RelationshipRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RelationshipRest.java index dd35a0726e9d..723f7e148b27 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RelationshipRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RelationshipRest.java @@ -19,14 +19,12 @@ * Refer to {@link org.dspace.content.Relationship} for explanation about the properties */ @LinksRest(links = { - @LinkRest( - name = RelationshipRest.RELATIONSHIP_TYPE, - method = "getRelationshipType" - ) + @LinkRest(name = RelationshipRest.RELATIONSHIP_TYPE, method = "getRelationshipType") }) public class RelationshipRest extends BaseObjectRest { public static final String NAME = "relationship"; - public static final String CATEGORY = "core"; + public static final String PLURAL_NAME = "relationships"; + public static final String CATEGORY = RestModel.CORE; public static final String RELATIONSHIP_TYPE = "relationshipType"; @@ -47,6 +45,11 @@ public String getType() { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + public String getCategory() { return CATEGORY; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RelationshipTypeRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RelationshipTypeRest.java index e3943643a52a..48a9cf499afc 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RelationshipTypeRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RelationshipTypeRest.java @@ -18,7 +18,8 @@ public class RelationshipTypeRest extends BaseObjectRest { public static final String NAME = "relationshiptype"; - public static final String CATEGORY = "core"; + public static final String PLURAL_NAME = "relationshiptypes"; + public static final String CATEGORY = RestModel.CORE; private String leftwardType; private String rightwardType; @@ -35,6 +36,11 @@ public String getType() { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + public String getCategory() { return CATEGORY; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java index 616a2d631d65..677405bda0e5 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java @@ -21,11 +21,10 @@ @LinksRest(links = { @LinkRest(name = "bitstream", method = "getUuid"), @LinkRest(name = "item", method = "getUuid") - }) -public class RequestItemRest - extends BaseObjectRest { +}) +public class RequestItemRest extends BaseObjectRest { public static final String NAME = "itemrequest"; - public static final String PLURAL_NAME = NAME + "s"; + public static final String PLURAL_NAME = "itemrequests"; public static final String CATEGORY = RestAddressableModel.TOOLS; @@ -230,6 +229,6 @@ public String getType() { @Override public String getTypePlural() { - return super.getTypePlural(); + return PLURAL_NAME; } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ResearcherProfileRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ResearcherProfileRest.java index 4224cfeeb924..629dbdf85821 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ResearcherProfileRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ResearcherProfileRest.java @@ -20,14 +20,15 @@ * */ @LinksRest(links = { - @LinkRest(name = ResearcherProfileRest.ITEM, method = "getItem"), - @LinkRest(name = ResearcherProfileRest.EPERSON, method = "getEPerson") + @LinkRest(name = ResearcherProfileRest.ITEM, method = "getItem"), + @LinkRest(name = ResearcherProfileRest.EPERSON, method = "getEPerson") }) public class ResearcherProfileRest extends BaseObjectRest { private static final long serialVersionUID = 1L; public static final String CATEGORY = RestModel.EPERSON; public static final String NAME = "profile"; + public static final String PLURAL_NAME = "profiles"; public static final String ITEM = "item"; public static final String EPERSON = "eperson"; @@ -69,6 +70,11 @@ public String getType() { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @Override public String getCategory() { return CATEGORY; @@ -129,4 +135,4 @@ public void setFundingsPreference(String fundingsPreference) { } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ResourcePolicyRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ResourcePolicyRest.java index a75c17b13655..564782c251ef 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ResourcePolicyRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ResourcePolicyRest.java @@ -63,6 +63,11 @@ public String getType() { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @Override public String getCategory() { return CATEGORY; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RestModel.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RestModel.java index 5bc85a58b2d1..72aae3b25a7e 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RestModel.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RestModel.java @@ -10,7 +10,6 @@ import java.io.Serializable; import com.fasterxml.jackson.annotation.JsonIgnore; -import org.atteo.evo.inflector.English; /** * A REST resource directly or indirectly (in a collection) exposed must have at @@ -21,9 +20,11 @@ public interface RestModel extends Serializable { public static final String ROOT = "root"; + public static final String CONTENT_REPORT = "contentreport"; public static final String CORE = "core"; public static final String EPERSON = "eperson"; public static final String DISCOVER = "discover"; + public static final String DUPLICATES = "duplicates"; public static final String CONFIGURATION = "config"; public static final String INTEGRATION = "integration"; public static final String STATISTICS = "statistics"; @@ -34,11 +35,11 @@ public interface RestModel extends Serializable { public static final String VERSIONING = "versioning"; public static final String AUTHENTICATION = "authn"; public static final String TOOLS = "tools"; + public static final String LDN = "ldn"; + public static final String PID = "pid"; public String getType(); @JsonIgnore - default public String getTypePlural() { - return English.plural(getType()); - } + String getTypePlural(); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RootRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RootRest.java index cef8965601ca..a4ca592cd52a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RootRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RootRest.java @@ -16,6 +16,7 @@ */ public class RootRest extends RestAddressableModel { public static final String NAME = "root"; + public static final String PLURAL_NAME = NAME; public static final String CATEGORY = RestModel.ROOT; private String dspaceUI; private String dspaceName; @@ -30,6 +31,14 @@ public String getType() { return NAME; } + /** + * The plural name is the same as the singular name + */ + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + public Class getController() { return RootRestResourceController.class; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ScriptRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ScriptRest.java index e9c0eb420334..58c68b37bd9a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ScriptRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ScriptRest.java @@ -39,6 +39,11 @@ public String getType() { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + public String getName() { return name; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchConfigurationRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchConfigurationRest.java index b25d827e75c1..f8a6e698d17b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchConfigurationRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchConfigurationRest.java @@ -22,6 +22,7 @@ public class SearchConfigurationRest extends BaseObjectRest { public static final String NAME = "discover"; + public static final String PLURAL_NAME = NAME; public static final String CATEGORY = RestModel.DISCOVER; @JsonIgnore private String scope; @@ -41,6 +42,14 @@ public String getType() { return NAME; } + /** + * The plural name is the same as the singular name + */ + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + public Class getController() { return DiscoveryRestController.class; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchEventRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchEventRest.java index 46827711f2ea..d2a61ef8c074 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchEventRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchEventRest.java @@ -19,6 +19,7 @@ public class SearchEventRest extends BaseObjectRest { public static final String NAME = "searchevent"; + public static final String PLURAL_NAME = "searchevents"; public static final String CATEGORY = RestAddressableModel.STATISTICS; private String query; @@ -43,6 +44,11 @@ public String getType() { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + public String getQuery() { return query; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchFacetEntryRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchFacetEntryRest.java index 513b4ad54a94..aafa3a110836 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchFacetEntryRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchFacetEntryRest.java @@ -21,6 +21,7 @@ public class SearchFacetEntryRest extends RestAddressableModel { public static final String NAME = "discover"; + public static final String PLURAL_NAME = NAME; public static final String CATEGORY = RestModel.DISCOVER; private String name; @@ -54,6 +55,14 @@ public String getType() { return NAME; } + /** + * The plural name is the same as the singular name + */ + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + public Class getController() { return DiscoveryRestController.class; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchFacetValueRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchFacetValueRest.java index 3f5be08712ab..97860eff4ce2 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchFacetValueRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchFacetValueRest.java @@ -16,6 +16,7 @@ public class SearchFacetValueRest extends RestAddressableModel { public static final String NAME = "discover"; + public static final String PLURAL_NAME = NAME; public static final String CATEGORY = RestModel.DISCOVER; private String label; @@ -36,6 +37,14 @@ public String getType() { return NAME; } + /** + * The plural name is the same as the singular name + */ + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + public Class getController() { return DiscoveryRestController.class; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchResultEntryRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchResultEntryRest.java index ac1d94f5f556..ac4918833cb3 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchResultEntryRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchResultEntryRest.java @@ -20,6 +20,7 @@ public class SearchResultEntryRest extends RestAddressableModel { public static final String NAME = "discover"; + public static final String PLURAL_NAME = NAME; public static final String CATEGORY = RestModel.DISCOVER; private Map> hitHighlights; @@ -35,6 +36,14 @@ public String getType() { return NAME; } + /** + * The plural name is the same as the singular name + */ + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @JsonIgnore public Class getController() { return DiscoveryRestController.class; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchSupportRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchSupportRest.java index 52c07ab13a98..a0743b8cbbc6 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchSupportRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchSupportRest.java @@ -16,6 +16,7 @@ */ public class SearchSupportRest extends BaseObjectRest { public static final String NAME = "discover"; + public static final String PLURAL_NAME = NAME; public static final String CATEGORY = RestModel.DISCOVER; public String getCategory() { @@ -26,6 +27,14 @@ public String getType() { return NAME; } + /** + * The plural name is the same as the singular name + */ + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + public Class getController() { return DiscoveryRestController.class; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SiteRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SiteRest.java index 533ad47df02c..6217505edbe1 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SiteRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SiteRest.java @@ -17,6 +17,7 @@ */ public class SiteRest extends DSpaceObjectRest { public static final String NAME = "site"; + public static final String PLURAL_NAME = "sites"; public static final String CATEGORY = RestAddressableModel.CORE; @Override @@ -29,6 +30,11 @@ public String getType() { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @Override public boolean equals(Object object) { return (object instanceof SiteRest && diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/StatisticsSupportRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/StatisticsSupportRest.java index 55df71e8e68d..e2d42c60a9c2 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/StatisticsSupportRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/StatisticsSupportRest.java @@ -12,6 +12,7 @@ public class StatisticsSupportRest extends BaseObjectRest { public static final String NAME = "statistics"; + public static final String PLURAL_NAME = "statistics"; public static final String CATEGORY = RestModel.STATISTICS; public String getCategory() { @@ -25,4 +26,9 @@ public Class getController() { public String getType() { return NAME; } + + @Override + public String getTypePlural() { + return null; + } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionAccessOptionRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionAccessOptionRest.java index 08f4c82f435e..ad8fa68cdb28 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionAccessOptionRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionAccessOptionRest.java @@ -23,7 +23,7 @@ public class SubmissionAccessOptionRest extends BaseObjectRest { private static final long serialVersionUID = -7708437586052984082L; public static final String NAME = "submissionaccessoption"; - public static final String PLURAL = "submissionaccessoptions"; + public static final String PLURAL_NAME = "submissionaccessoptions"; public static final String CATEGORY = RestAddressableModel.CONFIGURATION; private String id; @@ -64,6 +64,11 @@ public String getType() { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @Override public String getCategory() { return CATEGORY; @@ -76,4 +81,4 @@ public Class getController() { return RestResourceController.class; } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionCCLicenseRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionCCLicenseRest.java index 23589d5a4655..7cc3c124d52c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionCCLicenseRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionCCLicenseRest.java @@ -20,7 +20,7 @@ */ public class SubmissionCCLicenseRest extends BaseObjectRest { public static final String NAME = "submissioncclicense"; - public static final String PLURAL = "submissioncclicenses"; + public static final String PLURAL_NAME = "submissioncclicenses"; public static final String CATEGORY = RestAddressableModel.CONFIGURATION; @@ -66,6 +66,11 @@ public String getType() { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @Override @JsonIgnore public Class getController() { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionCCLicenseUrlRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionCCLicenseUrlRest.java index 77263ba3178b..409cad5f52d6 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionCCLicenseUrlRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionCCLicenseUrlRest.java @@ -17,7 +17,7 @@ */ public class SubmissionCCLicenseUrlRest extends BaseObjectRest { public static final String NAME = "submissioncclicenseUrl"; - public static final String PLURAL = "submissioncclicenseUrls"; + public static final String PLURAL_NAME = "submissioncclicenseUrls"; public static final String CATEGORY = RestAddressableModel.CONFIGURATION; @@ -47,6 +47,11 @@ public String getType() { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @Override public String getCategory() { return SubmissionCCLicenseUrlRest.CATEGORY; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionCOARNotifyRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionCOARNotifyRest.java new file mode 100644 index 000000000000..3767186f7657 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionCOARNotifyRest.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.app.rest.model; + +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.dspace.app.rest.RestResourceController; +import org.dspace.coarnotify.NotifyPattern; +import org.dspace.coarnotify.NotifySubmissionConfiguration; +/** + * This class is the REST representation of the COARNotifySubmissionConfiguration model object + * and acts as a data object for the SubmissionCOARNotifyResource class. + * + * Refer to {@link NotifySubmissionConfiguration} for explanation of the properties + */ +public class SubmissionCOARNotifyRest extends BaseObjectRest { + public static final String NAME = "submissioncoarnotifyconfig"; + public static final String PLURAL_NAME = "submissioncoarnotifyconfigs"; + public static final String CATEGORY = RestAddressableModel.CONFIGURATION; + + private String id; + + private List patterns; + + public String getId() { + return id; + } + + public void setId(final String id) { + this.id = id; + } + + public List getPatterns() { + return patterns; + } + + public void setPatterns(final List patterns) { + this.patterns = patterns; + } + + @JsonIgnore + @Override + public String getCategory() { + return CATEGORY; + } + + @Override + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + public String getType() { + return NAME; + } + + @Override + @JsonIgnore + public Class getController() { + return RestResourceController.class; + } + + @Override + public String getTypePlural() { + return PLURAL_NAME; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionDefinitionRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionDefinitionRest.java index fb440345c286..958701b5d6a1 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionDefinitionRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionDefinitionRest.java @@ -20,6 +20,7 @@ */ public class SubmissionDefinitionRest extends BaseObjectRest { public static final String NAME = "submissiondefinition"; + public static final String PLURAL_NAME = "submissiondefinitions"; public static final String CATEGORY = RestAddressableModel.CONFIGURATION; private String name; @@ -59,6 +60,11 @@ public String getType() { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + public void setDefaultConf(boolean isDefault) { this.defaultConf = isDefault; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionFormRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionFormRest.java index 8dededdabb96..5ef3c87fb358 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionFormRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionFormRest.java @@ -18,6 +18,7 @@ */ public class SubmissionFormRest extends BaseObjectRest { public static final String NAME = "submissionform"; + public static final String PLURAL_NAME = "submissionforms"; public static final String NAME_LINK_ON_PANEL = RestAddressableModel.CONFIGURATION; public static final String CATEGORY = RestAddressableModel.CONFIGURATION; @@ -61,6 +62,11 @@ public String getType() { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @Override public Class getController() { return RestResourceController.class; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionSectionRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionSectionRest.java index 55f7fc035a35..9c5a1c862976 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionSectionRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionSectionRest.java @@ -23,6 +23,7 @@ public class SubmissionSectionRest extends BaseObjectRest { public static final String NAME = "submissionsection"; + public static final String PLURAL_NAME = "submissionsections"; public static final String ATTRIBUTE_NAME = "sections"; private String header; @@ -51,6 +52,11 @@ public String getType() { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + public ScopeEnum getScope() { return scope; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionUploadRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionUploadRest.java index 4d8504fa0be4..48d01a86d7c2 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionUploadRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionUploadRest.java @@ -21,6 +21,7 @@ public class SubmissionUploadRest extends BaseObjectRest { public static final String NAME = "submissionupload"; + public static final String PLURAL_NAME = "submissionuploads"; public static final String NAME_LINK_ON_PANEL = RestAddressableModel.CONFIGURATION; public static final String CATEGORY = RestAddressableModel.CONFIGURATION; @@ -53,6 +54,11 @@ public String getType() { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @Override public Class getController() { return RestResourceController.class; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubscriptionRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubscriptionRest.java index 78a81c38b162..bec1be5fc06b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubscriptionRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubscriptionRest.java @@ -21,8 +21,8 @@ public class SubscriptionRest extends BaseObjectRest { private static final long serialVersionUID = 1L; public static final String NAME = "subscription"; - public static final String NAME_PLURAL = "subscriptions"; - public static final String CATEGORY = "core"; + public static final String PLURAL_NAME = "subscriptions"; + public static final String CATEGORY = RestModel.CORE; public static final String DSPACE_OBJECT = "resource"; public static final String EPERSON = "eperson"; @@ -45,6 +45,11 @@ public String getType() { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + public void setSubscriptionType(String type) { this.subscriptionType = type; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SuggestionRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SuggestionRest.java index b3f67fc2bede..7b1a05127fc7 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SuggestionRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SuggestionRest.java @@ -21,10 +21,13 @@ * * @author Andrea Bollini (andrea.bollini at 4science.it) */ -@LinksRest(links = { @LinkRest(name = SuggestionRest.TARGET, method = "getTarget") }) +@LinksRest(links = { + @LinkRest(name = SuggestionRest.TARGET, method = "getTarget") +}) public class SuggestionRest extends BaseObjectRest { private static final long serialVersionUID = 1L; public static final String NAME = "suggestion"; + public static final String PLURAL_NAME = "suggestions"; public static final String TARGET = "target"; public static final String CATEGORY = RestAddressableModel.INTEGRATION; @@ -41,6 +44,11 @@ public String getType() { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @Override public String getCategory() { return CATEGORY; @@ -111,4 +119,4 @@ public EvidenceRest(String score, String notes) { this.notes = notes; } } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SuggestionSourceRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SuggestionSourceRest.java index 9c2aa80e82e5..f8cc0e603f1d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SuggestionSourceRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SuggestionSourceRest.java @@ -22,6 +22,7 @@ public class SuggestionSourceRest extends BaseObjectRest { private static final long serialVersionUID = 1L; public static final String CATEGORY = RestAddressableModel.INTEGRATION; public static final String NAME = "suggestionsource"; + public static final String PLURAL_NAME = "suggestionsources"; private int total; @@ -31,6 +32,11 @@ public String getType() { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @Override public String getCategory() { return CATEGORY; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SuggestionTargetRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SuggestionTargetRest.java index ba93ab4e52b7..b6518eff7488 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SuggestionTargetRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SuggestionTargetRest.java @@ -19,11 +19,12 @@ * @author Andrea Bollini (andrea.bollini at 4science.it) */ @LinksRest(links = { - @LinkRest(name = SuggestionTargetRest.TARGET, method = "getTarget") + @LinkRest(name = SuggestionTargetRest.TARGET, method = "getTarget") }) public class SuggestionTargetRest extends BaseObjectRest { private static final long serialVersionUID = 1L; public static final String NAME = "suggestiontarget"; + public static final String PLURAL_NAME = "suggestiontargets"; public static final String TARGET = "target"; public static final String CATEGORY = RestAddressableModel.INTEGRATION; @@ -37,6 +38,11 @@ public String getType() { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @Override public String getCategory() { return CATEGORY; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SupervisionOrderRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SupervisionOrderRest.java index e114fdeb39f2..f5e75f1751e2 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SupervisionOrderRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SupervisionOrderRest.java @@ -19,6 +19,7 @@ public class SupervisionOrderRest extends BaseObjectRest { public static final String NAME = "supervisionorder"; + public static final String PLURAL_NAME = "supervisionorders"; public static final String CATEGORY = RestAddressableModel.CORE; private Integer id; @@ -69,4 +70,9 @@ public Class getController() { public String getType() { return NAME; } + + @Override + public String getTypePlural() { + return PLURAL_NAME; + } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SystemWideAlertRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SystemWideAlertRest.java index 995ec8e93404..b0f3e9da4ec3 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SystemWideAlertRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SystemWideAlertRest.java @@ -19,6 +19,7 @@ */ public class SystemWideAlertRest extends BaseObjectRest { public static final String NAME = "systemwidealert"; + public static final String PLURAL_NAME = "systemwidealerts"; public static final String CATEGORY = RestAddressableModel.SYSTEM; public String getCategory() { @@ -34,6 +35,11 @@ public String getType() { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + private Integer alertId; private String message; private String allowSessions; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/TemplateItemRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/TemplateItemRest.java index cc6b11d12af9..d48c713d649e 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/TemplateItemRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/TemplateItemRest.java @@ -21,6 +21,7 @@ public class TemplateItemRest extends BaseObjectRest { private UUID uuid; public static final String NAME = "itemtemplate"; + public static final String PLURAL_NAME = "itemtemplates"; public static final String CATEGORY = RestAddressableModel.CORE; @JsonIgnore private CollectionRest templateItemOf; @@ -67,6 +68,11 @@ public String getType() { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @Override public UUID getId() { return uuid; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/UsageReportPointCityRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/UsageReportPointCityRest.java index 369bcce4d135..0198aaf0cc8c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/UsageReportPointCityRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/UsageReportPointCityRest.java @@ -15,12 +15,18 @@ */ public class UsageReportPointCityRest extends UsageReportPointRest { public static final String NAME = "city"; + public static final String PLURAL_NAME = "cities"; @Override public String getType() { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @Override public void setId(String id) { super.id = id; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/UsageReportPointCountryRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/UsageReportPointCountryRest.java index 1d6723503e61..fbfe9b9ee698 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/UsageReportPointCountryRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/UsageReportPointCountryRest.java @@ -19,6 +19,7 @@ */ public class UsageReportPointCountryRest extends UsageReportPointRest { public static final String NAME = "country"; + public static final String PLURAL_NAME = "countries"; @Override public void setLabel(String label) { @@ -36,4 +37,9 @@ public void setId(String id) { public String getType() { return NAME; } + + @Override + public String getTypePlural() { + return PLURAL_NAME; + } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/UsageReportPointDateRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/UsageReportPointDateRest.java index e9b4ddea1568..d3ccb0163d9c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/UsageReportPointDateRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/UsageReportPointDateRest.java @@ -15,12 +15,18 @@ */ public class UsageReportPointDateRest extends UsageReportPointRest { public static final String NAME = "date"; + public static final String PLURAL_NAME = "dates"; @Override public String getType() { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @Override public void setId(String id) { super.id = id; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/UsageReportPointDsoTotalVisitsRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/UsageReportPointDsoTotalVisitsRest.java index fd8d33478666..721d0b8dc2a4 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/UsageReportPointDsoTotalVisitsRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/UsageReportPointDsoTotalVisitsRest.java @@ -7,6 +7,8 @@ */ package org.dspace.app.rest.model; +import org.atteo.evo.inflector.English; + /** * This class serves as a REST representation of a TotalVisit data Point of a DSO's {@link UsageReportRest} from the * DSpace statistics @@ -25,6 +27,11 @@ public String getType() { return this.type; } + @Override + public String getTypePlural() { + return English.plural(getType()); + } + /** * Sets the type of this {@link UsageReportPointRest} object, should be type of dso concerned (e.g. item, bitstream, ...) * diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/UsageReportPointRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/UsageReportPointRest.java index feb006486fb0..50a8fc46bafc 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/UsageReportPointRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/UsageReportPointRest.java @@ -17,8 +17,9 @@ * * @author Maria Verdonck (Atmire) on 08/06/2020 */ -public class UsageReportPointRest extends BaseObjectRest { +public abstract class UsageReportPointRest extends BaseObjectRest { public static final String NAME = "point"; + public static final String PLURAL_NAME = "points"; public static final String CATEGORY = RestModel.STATISTICS; protected String id; protected String label; @@ -54,6 +55,16 @@ public String getType() { return NAME; } + /** + * Returns the plural type of this {@link UsageReportPointRest} object + * + * @return Plural type of this {@link UsageReportPointRest} object + */ + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + /** * Returns the values of this {@link UsageReportPointRest} object, containing the amount of views * diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/UsageReportRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/UsageReportRest.java index a59535fb9419..a8b267daaf82 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/UsageReportRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/UsageReportRest.java @@ -20,6 +20,7 @@ */ public class UsageReportRest extends BaseObjectRest { public static final String NAME = "usagereport"; + public static final String PLURAL_NAME = "usagereports"; public static final String CATEGORY = RestModel.STATISTICS; @JsonProperty(value = "report-type") @@ -56,6 +57,11 @@ public String getType() { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + /** * Returns the report type of this UsageReport, options listed in * {@link org.dspace.app.rest.utils.UsageReportUtils}, e.g. diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VersionHistoryRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VersionHistoryRest.java index e93e131aadb7..80f704c77936 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VersionHistoryRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VersionHistoryRest.java @@ -13,14 +13,8 @@ * The REST object for the {@link org.dspace.versioning.VersionHistory} object */ @LinksRest(links = { - @LinkRest( - name = VersionHistoryRest.VERSIONS, - method = "getVersions" - ), - @LinkRest( - name = VersionHistoryRest.DRAFT_VERSION, - method = "getDraftVersion" - ) + @LinkRest(name = VersionHistoryRest.VERSIONS, method = "getVersions"), + @LinkRest(name = VersionHistoryRest.DRAFT_VERSION, method = "getDraftVersion") }) public class VersionHistoryRest extends BaseObjectRest { @@ -32,6 +26,7 @@ public class VersionHistoryRest extends BaseObjectRest { private Boolean draftVersion; public static final String NAME = "versionhistory"; + public static final String PLURAL_NAME = "versionhistories"; public static final String CATEGORY = RestAddressableModel.VERSIONING; public static final String VERSIONS = "versions"; public static final String DRAFT_VERSION = "draftVersion"; @@ -51,6 +46,11 @@ public String getType() { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + /** * Generic getter for the id * @return the id value of this VersionHistoryRest diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VersionRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VersionRest.java index abdc64295fdc..d9ebdd67e408 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VersionRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VersionRest.java @@ -16,18 +16,13 @@ * The REST object for the {@link org.dspace.versioning.Version} objects */ @LinksRest(links = { - @LinkRest( - name = VersionRest.VERSION_HISTORY, - method = "getVersionHistory" - ), - @LinkRest( - name = VersionRest.ITEM, - method = "getVersionItem" - ) + @LinkRest(name = VersionRest.VERSION_HISTORY, method = "getVersionHistory"), + @LinkRest(name = VersionRest.ITEM, method = "getVersionItem") }) public class VersionRest extends BaseObjectRest { public static final String NAME = "version"; + public static final String PLURAL_NAME = "versions"; public static final String CATEGORY = RestAddressableModel.VERSIONING; public static final String VERSION_HISTORY = "versionhistory"; @@ -137,4 +132,9 @@ public Class getController() { public String getType() { return NAME; } + + @Override + public String getTypePlural() { + return PLURAL_NAME; + } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ViewEventRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ViewEventRest.java index 897a3f86ae99..2806f7ed8747 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ViewEventRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ViewEventRest.java @@ -19,6 +19,7 @@ public class ViewEventRest extends BaseObjectRest { public static final String NAME = "viewevent"; + public static final String PLURAL_NAME = "viewevents"; public static final String CATEGORY = RestAddressableModel.STATISTICS; private UUID targetId; @@ -67,4 +68,9 @@ public Class getController() { public String getType() { return NAME; } + + @Override + public String getTypePlural() { + return PLURAL_NAME; + } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryDetailsRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryDetailsRest.java index 30e5eb71cbff..884e14642cf9 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryDetailsRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryDetailsRest.java @@ -18,9 +18,9 @@ * @author Andrea Bollini (andrea.bollini at 4science.it) */ @LinksRest(links = { - @LinkRest(name = VocabularyEntryDetailsRest.PARENT, method = "getParent"), - @LinkRest(name = VocabularyEntryDetailsRest.CHILDREN, method = "getChildren") - }) + @LinkRest(name = VocabularyEntryDetailsRest.PARENT, method = "getParent"), + @LinkRest(name = VocabularyEntryDetailsRest.CHILDREN, method = "getChildren") +}) public class VocabularyEntryDetailsRest extends BaseObjectRest { public static final String PLURAL_NAME = "vocabularyEntryDetails"; public static final String NAME = "vocabularyEntryDetail"; @@ -82,6 +82,11 @@ public String getType() { return VocabularyEntryDetailsRest.NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @Override public Class getController() { return RestResourceController.class; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryRest.java index 713d4c520972..3977026efafb 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryRest.java @@ -20,6 +20,7 @@ */ public class VocabularyEntryRest implements RestModel { public static final String NAME = "vocabularyEntry"; + public static final String PLURAL_NAME = "vocabularyEntries"; @JsonInclude(Include.NON_NULL) private String authority; @@ -77,4 +78,9 @@ public VocabularyEntryDetailsRest getVocabularyEntryDetailsRest() { public String getType() { return VocabularyEntryRest.NAME; } + + @Override + public String getTypePlural() { + return PLURAL_NAME; + } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyRest.java index cc848b945b2f..a54d93c643b4 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyRest.java @@ -15,13 +15,12 @@ * @author Andrea Bollini (andrea.bollini at 4science.it) */ @LinksRest(links = { - @LinkRest(name = VocabularyRest.ENTRIES, - method = "filter" - ), + @LinkRest(name = VocabularyRest.ENTRIES, method = "filter"), }) public class VocabularyRest extends BaseObjectRest { public static final String NAME = "vocabulary"; + public static final String PLURAL_NAME = "vocabularies"; public static final String CATEGORY = RestAddressableModel.SUBMISSION; public static final String ENTRIES = "entries"; @@ -75,6 +74,11 @@ public String getType() { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @Override public Class getController() { return RestResourceController.class; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowActionRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowActionRest.java index 07a2c36cff96..c240bee2fb24 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowActionRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowActionRest.java @@ -21,9 +21,9 @@ */ public class WorkflowActionRest extends BaseObjectRest { - public static final String CATEGORY = "config"; + public static final String CATEGORY = RestModel.CONFIGURATION; public static final String NAME = "workflowaction"; - public static final String NAME_PLURAL = "workflowactions"; + public static final String PLURAL_NAME = "workflowactions"; private List options; private List advancedOptions; @@ -44,6 +44,11 @@ public String getType() { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + public List getOptions() { return options; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowDefinitionRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowDefinitionRest.java index 7c2de7071bde..9cef79aaf3be 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowDefinitionRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowDefinitionRest.java @@ -18,20 +18,14 @@ * @author Maria Verdonck (Atmire) on 11/12/2019 */ @LinksRest(links = { - @LinkRest( - name = WorkflowDefinitionRest.COLLECTIONS_MAPPED_TO, - method = "getCollections" - ), - @LinkRest( - name = WorkflowDefinitionRest.STEPS, - method = "getSteps" - ) + @LinkRest(name = WorkflowDefinitionRest.COLLECTIONS_MAPPED_TO, method = "getCollections"), + @LinkRest(name = WorkflowDefinitionRest.STEPS, method = "getSteps") }) public class WorkflowDefinitionRest extends BaseObjectRest { - public static final String CATEGORY = "config"; + public static final String CATEGORY = RestModel.CONFIGURATION; public static final String NAME = "workflowdefinition"; - public static final String NAME_PLURAL = "workflowdefinitions"; + public static final String PLURAL_NAME = "workflowdefinitions"; public static final String COLLECTIONS_MAPPED_TO = "collections"; public static final String STEPS = "steps"; @@ -55,6 +49,11 @@ public String getType() { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @Override @JsonIgnore public String getId() { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowItemRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowItemRest.java index 8f580f441477..d08abb3546a3 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowItemRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowItemRest.java @@ -15,17 +15,23 @@ * @author Andrea Bollini (andrea.bollini at 4science.it) */ @LinksRest(links = { - @LinkRest( - name = WorkflowItemRest.STEP, - method = "getStep" - ) + @LinkRest(name = WorkflowItemRest.STEP, method = "getStep"), + @LinkRest(name = WorkflowItemRest.SUBMITTER, method = "getWorkflowItemSubmitter"), + @LinkRest(name = WorkflowItemRest.ITEM, method = "getWorkflowItemItem"), + @LinkRest(name = WorkflowItemRest.COLLECTION, method = "getWorkflowItemCollection") }) public class WorkflowItemRest extends AInprogressSubmissionRest { public static final String NAME = "workflowitem"; + public static final String PLURAL_NAME = "workflowitems"; public static final String CATEGORY = RestAddressableModel.WORKFLOW; public static final String STEP = "step"; + public static final String SUBMITTER = "submitter"; + public static final String ITEM = "item"; + public static final String COLLECTION = "collection"; + + @Override public String getCategory() { return CATEGORY; @@ -36,6 +42,11 @@ public String getType() { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @Override public Class getController() { return RestResourceController.class; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowStepRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowStepRest.java index 648cffbca80d..53ddf38709e4 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowStepRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowStepRest.java @@ -18,16 +18,13 @@ * @author Maria Verdonck (Atmire) on 10/01/2020 */ @LinksRest(links = { - @LinkRest( - name = WorkflowStepRest.ACTIONS, - method = "getActions" - ), + @LinkRest(name = WorkflowStepRest.ACTIONS, method = "getActions"), }) public class WorkflowStepRest extends BaseObjectRest { - public static final String CATEGORY = "config"; + public static final String CATEGORY = RestModel.CONFIGURATION; public static final String NAME = "workflowstep"; - public static final String NAME_PLURAL = "workflowsteps"; + public static final String PLURAL_NAME = "workflowsteps"; public static final String ACTIONS = "workflowactions"; @@ -48,6 +45,11 @@ public String getType() { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @JsonIgnore public List getWorkflowactions() { return workflowactions; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkspaceItemRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkspaceItemRest.java index 57a5ab5c7f0e..8e0d52123f99 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkspaceItemRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkspaceItemRest.java @@ -15,16 +15,20 @@ * @author Andrea Bollini (andrea.bollini at 4science.it) */ @LinksRest(links = { - @LinkRest( - name = WorkspaceItemRest.SUPERVISION_ORDERS, - method = "getSupervisionOrders" - ) + @LinkRest(name = WorkspaceItemRest.SUPERVISION_ORDERS, method = "getSupervisionOrders"), + @LinkRest(name = WorkspaceItemRest.SUBMITTER, method = "getWorkspaceItemSubmitter"), + @LinkRest(name = WorkspaceItemRest.ITEM, method = "getWorkspaceItemItem"), + @LinkRest(name = WorkspaceItemRest.COLLECTION, method = "getWorkspaceItemCollection") }) public class WorkspaceItemRest extends AInprogressSubmissionRest { public static final String NAME = "workspaceitem"; + public static final String PLURAL_NAME = "workspaceitems"; public static final String CATEGORY = RestAddressableModel.SUBMISSION; public static final String SUPERVISION_ORDERS = "supervisionOrders"; + public static final String SUBMITTER = "submitter"; + public static final String ITEM = "item"; + public static final String COLLECTION = "collection"; @Override public String getCategory() { @@ -36,6 +40,11 @@ public String getType() { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @Override public Class getController() { return RestResourceController.class; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/BrowseIndexResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/BrowseIndexResource.java index 61158704ea5a..de3c605139ea 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/BrowseIndexResource.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/BrowseIndexResource.java @@ -10,7 +10,6 @@ import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo; import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn; -import org.atteo.evo.inflector.English; import org.dspace.app.rest.RestResourceController; import org.dspace.app.rest.model.BrowseIndexRest; import org.dspace.app.rest.model.VocabularyRest; @@ -47,13 +46,13 @@ public BrowseIndexResource(BrowseIndexRest bix, Utils utils) { } if (bix.getBrowseType().equals(BrowseIndexRest.BROWSE_TYPE_HIERARCHICAL)) { ChoiceAuthorityService choiceAuthorityService = - ContentAuthorityServiceFactory.getInstance().getChoiceAuthorityService(); + ContentAuthorityServiceFactory.getInstance().getChoiceAuthorityService(); ChoiceAuthority source = choiceAuthorityService.getChoiceAuthorityByAuthorityName(bix.getVocabulary()); UriComponentsBuilder baseLink = linkTo( - methodOn(RestResourceController.class, VocabularyRest.AUTHENTICATION).findRel(null, - null, VocabularyRest.CATEGORY, - English.plural(VocabularyRest.NAME), source.getPluginInstanceName(), - "", null, null)).toUriComponentsBuilder(); + methodOn(RestResourceController.class, VocabularyRest.AUTHENTICATION).findRel( + null, null, VocabularyRest.CATEGORY, VocabularyRest.PLURAL_NAME, + source.getPluginInstanceName(), "", null, null) + ).toUriComponentsBuilder(); add(Link.of(baseLink.build().encode().toUriString(), BrowseIndexRest.LINK_VOCABULARY)); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/ContentReportSupportResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/ContentReportSupportResource.java new file mode 100644 index 000000000000..363a5e704a5b --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/ContentReportSupportResource.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.rest.model.hateoas; + +import org.dspace.app.rest.model.ContentReportSupportRest; +import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; + +@RelNameDSpaceResource(ContentReportSupportRest.NAME) +public class ContentReportSupportResource extends HALResource { + public ContentReportSupportResource(ContentReportSupportRest content) { + super(content); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/CorrectionTypeResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/CorrectionTypeResource.java new file mode 100644 index 000000000000..cb1ba5552fef --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/CorrectionTypeResource.java @@ -0,0 +1,27 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.rest.model.hateoas; + +import org.dspace.app.rest.model.CorrectionTypeRest; +import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; +import org.dspace.app.rest.utils.Utils; + +/** + * CorrectionType Rest HAL Resource. The HAL Resource wraps the REST Resource + * adding support for the links and embedded resources + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + */ +@RelNameDSpaceResource(CorrectionTypeRest.NAME) +public class CorrectionTypeResource extends DSpaceResource { + + public CorrectionTypeResource(CorrectionTypeRest target, Utils utils) { + super(target, utils); + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/FilteredCollectionsResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/FilteredCollectionsResource.java new file mode 100644 index 000000000000..9bb23464856e --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/FilteredCollectionsResource.java @@ -0,0 +1,21 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.rest.model.hateoas; + +import org.dspace.app.rest.model.FilteredCollectionsRest; +import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; +import org.dspace.app.rest.utils.Utils; + +@RelNameDSpaceResource(FilteredCollectionsRest.NAME) +public class FilteredCollectionsResource extends DSpaceResource { + + public FilteredCollectionsResource(FilteredCollectionsRest data, Utils utils) { + super(data, utils); + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/FilteredItemsResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/FilteredItemsResource.java new file mode 100644 index 000000000000..8758b9db5a74 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/FilteredItemsResource.java @@ -0,0 +1,21 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.rest.model.hateoas; + +import org.dspace.app.rest.model.FilteredItemsRest; +import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; +import org.dspace.app.rest.utils.Utils; + +@RelNameDSpaceResource(FilteredItemsRest.NAME) +public class FilteredItemsResource extends DSpaceResource { + + public FilteredItemsResource(FilteredItemsRest data, Utils utils) { + super(data, utils); + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/ItemFilterResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/ItemFilterResource.java new file mode 100644 index 000000000000..666531e816c9 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/ItemFilterResource.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.rest.model.hateoas; + +import org.dspace.app.rest.model.ItemFilterRest; +import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; +import org.dspace.app.rest.utils.Utils; + +/** + * ItemFilter Rest HAL Resource. The HAL Resource wraps the REST Resource adding + * support for the links and embedded resources + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + */ +@RelNameDSpaceResource(ItemFilterRest.NAME) +public class ItemFilterResource extends DSpaceResource { + public ItemFilterResource(ItemFilterRest data, Utils utils) { + super(data, utils); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/LDNMessageEntityResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/LDNMessageEntityResource.java new file mode 100644 index 000000000000..2ac899fa4bb8 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/LDNMessageEntityResource.java @@ -0,0 +1,27 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.rest.model.hateoas; + +import org.dspace.app.rest.model.LDNMessageEntityRest; +import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; +import org.dspace.app.rest.utils.Utils; + +/** + * Browse Entry Rest HAL Resource. The HAL Resource wraps the REST Resource + * adding support for the links and embedded resources + * + * @author Stefano Maffei (stefano.maffei at 4science.com) + */ +@RelNameDSpaceResource(LDNMessageEntityRest.NAME) +public class LDNMessageEntityResource extends DSpaceResource { + + public LDNMessageEntityResource(LDNMessageEntityRest data, Utils utils) { + super(data, utils); + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/NotifyServiceResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/NotifyServiceResource.java new file mode 100644 index 000000000000..8b2cf509d701 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/NotifyServiceResource.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.rest.model.hateoas; + +import org.dspace.app.rest.model.NotifyServiceRest; +import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; +import org.dspace.app.rest.utils.Utils; + +/** + * NotifyService Rest HAL Resource. The HAL Resource wraps the REST Resource adding + * support for the links and embedded resources + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + */ +@RelNameDSpaceResource(NotifyServiceRest.NAME) +public class NotifyServiceResource extends DSpaceResource { + public NotifyServiceResource(NotifyServiceRest data, Utils utils) { + super(data, utils); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/PotentialDuplicateResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/PotentialDuplicateResource.java new file mode 100644 index 000000000000..e753126133b4 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/PotentialDuplicateResource.java @@ -0,0 +1,22 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.rest.model.hateoas; + +import org.dspace.app.rest.model.PotentialDuplicateRest; + +/** + * + * Wrap PotentialDuplicatesRest REST resource in a very simple HALResource class + * + * @author Kim Shepherd + */ +public class PotentialDuplicateResource extends HALResource { + public PotentialDuplicateResource(PotentialDuplicateRest data) { + super(data); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SubmissionCOARNotifyResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SubmissionCOARNotifyResource.java new file mode 100644 index 000000000000..b49451f8e89b --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SubmissionCOARNotifyResource.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.rest.model.hateoas; + +import org.dspace.app.rest.model.SubmissionCOARNotifyRest; +import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; +import org.dspace.app.rest.utils.Utils; + +/** + * COARNotify HAL Resource. This resource adds the data from the REST object together with embedded objects + * and a set of links if applicable + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + */ +@RelNameDSpaceResource(SubmissionCOARNotifyRest.NAME) +public class SubmissionCOARNotifyResource extends DSpaceResource { + public SubmissionCOARNotifyResource(SubmissionCOARNotifyRest submissionCOARNotifyRest, Utils utils) { + super(submissionCOARNotifyRest, utils); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/patch/JsonValueEvaluator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/patch/JsonValueEvaluator.java index 07d488f518e4..67127ccf8fcf 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/patch/JsonValueEvaluator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/patch/JsonValueEvaluator.java @@ -7,10 +7,9 @@ */ package org.dspace.app.rest.model.patch; -import javax.annotation.Nonnull; - import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.annotation.Nonnull; import org.springframework.data.rest.webmvc.json.patch.PatchException; /** diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/query/RestSearchOperator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/query/RestSearchOperator.java index ae8713bc69e2..9dba4021b463 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/query/RestSearchOperator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/query/RestSearchOperator.java @@ -23,18 +23,18 @@ public enum RestSearchOperator { /** - * The notcontains operator can be used by adding a - (minus) infront of the search query + * The notcontains operator can be used by adding a - (minus) in front of the search query * and a * at the end * It then becomes -VALUE* to call for a search on the notcontains operator for VALUE */ NOTCONTAINS("-(.+)\\*", "notcontains"), /** - * The notauthority operator can be used by adding a -id: infront of the search query + * The notauthority operator can be used by adding a -id: in front of the search query * It then becomes -id:VALUE to call for a search on the notauthority operator for VALUE */ NOTAUTHORITY("-id:(.+)", "notauthority"), /** - * The notequals operator can be used by adding a - infront of the search query + * The notequals operator can be used by adding a - in front of the search query * It then becomes -VALUE to call for a search on the notequals operator for VALUE */ NOTEQUALS("-(.+)", "notequals"), @@ -44,7 +44,7 @@ public enum RestSearchOperator { */ CONTAINS("(.+)\\*", "contains"), /** - * The authority operator can be used by adding an id: infront of the search query + * The authority operator can be used by adding an id: in front of the search query * It then becomes id:VALUE to call for a search on the authority operator for VALUE */ AUTHORITY("id:(.+)", "authority"), diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/step/DataDuplicateDetection.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/step/DataDuplicateDetection.java new file mode 100644 index 000000000000..9506e9676e03 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/step/DataDuplicateDetection.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.app.rest.model.step; + +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonUnwrapped; +import org.dspace.app.rest.model.PotentialDuplicateRest; + +/** + * Section data model for potential duplicate items detected during submission + * + * @author Kim Shepherd + */ +public class DataDuplicateDetection implements SectionData { + public DataDuplicateDetection() { + } + + /** + * A list of potential duplicate items found by DuplicateDetectionService, in their REST model form + */ + @JsonUnwrapped + private List potentialDuplicates; + + /** + * Return the list of detected potential duplicates in REST model form + * @return list of potential duplicate REST models + */ + public List getPotentialDuplicates() { + return potentialDuplicates; + } + + /** + * Set list of potential duplicates. + * @see org.dspace.app.rest.converter.PotentialDuplicateConverter + * @param potentialDuplicates list of potential duplicates + */ + public void setPotentialDuplicates(List potentialDuplicates) { + this.potentialDuplicates = potentialDuplicates; + } +} \ No newline at end of file diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/step/DataIdentifiers.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/step/DataIdentifiers.java index 01e0eabdd380..f0eeaf2c222d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/step/DataIdentifiers.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/step/DataIdentifiers.java @@ -20,7 +20,7 @@ public class DataIdentifiers implements SectionData { // Map of identifier types and values List identifiers; - // Types to display, a hint for te UI + // Types to display, a hint for the UI List displayTypes; public DataIdentifiers() { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/step/DataNotify.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/step/DataNotify.java new file mode 100644 index 000000000000..0ae1fa57174a --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/step/DataNotify.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.rest.model.step; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnore; + +/** + * Java Bean to expose the COAR Notify Section during in progress submission. + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + */ +public class DataNotify implements SectionData { + + private Map> patterns = new HashMap<>(); + + public DataNotify() { + + } + + @JsonAnySetter + public void add(String key, List values) { + patterns.put(key, values); + } + + public DataNotify(Map> patterns) { + this.patterns = patterns; + } + + @JsonIgnore + public void setPatterns(Map> patterns) { + this.patterns = patterns; + } + + @JsonAnyGetter + public Map> getPatterns() { + return patterns; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/step/DataUpload.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/step/DataUpload.java index d1cbdeb4b413..a28a5f3ad397 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/step/DataUpload.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/step/DataUpload.java @@ -9,6 +9,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.UUID; import com.fasterxml.jackson.annotation.JsonUnwrapped; @@ -19,6 +20,11 @@ */ public class DataUpload implements SectionData { + /* + * primary bitstream uuid + */ + private UUID primary; + @JsonUnwrapped private List files; @@ -32,4 +38,13 @@ public List getFiles() { public void setFiles(List files) { this.files = files; } + + public UUID getPrimary() { + return primary; + } + + public void setPrimary(UUID primary) { + this.primary = primary; + } + } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/wrapper/SubmissionCCLicenseUrl.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/wrapper/SubmissionCCLicenseUrl.java index 68ff1166b452..9024779b0376 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/wrapper/SubmissionCCLicenseUrl.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/wrapper/SubmissionCCLicenseUrl.java @@ -16,7 +16,7 @@ public class SubmissionCCLicenseUrl { /** - * The url for ths object + * The url for this object */ private String url; /** diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/projection/Projection.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/projection/Projection.java index 19b67d851d10..42314897a849 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/projection/Projection.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/projection/Projection.java @@ -7,8 +7,7 @@ */ package org.dspace.app.rest.projection; -import javax.persistence.Entity; - +import jakarta.persistence.Entity; import org.dspace.app.rest.model.LinkRest; import org.dspace.app.rest.model.RestAddressableModel; import org.dspace.app.rest.model.RestModel; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorizationEpersonLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorizationEpersonLinkRepository.java index 5ffbd95a775a..ca123d2de84d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorizationEpersonLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorizationEpersonLinkRepository.java @@ -8,9 +8,9 @@ package org.dspace.app.rest.repository; import java.sql.SQLException; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; +import jakarta.annotation.Nullable; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.authorization.AuthorizationRestUtil; import org.dspace.app.rest.model.AuthorizationRest; import org.dspace.app.rest.model.EPersonRest; @@ -25,7 +25,7 @@ /** * Link repository for "eperson" subresource of an individual authorization. */ -@Component(AuthorizationRest.CATEGORY + "." + AuthorizationRest.NAME + "." + AuthorizationRest.EPERSON) +@Component(AuthorizationRest.CATEGORY + "." + AuthorizationRest.PLURAL_NAME + "." + AuthorizationRest.EPERSON) public class AuthorizationEpersonLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorizationFeatureLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorizationFeatureLinkRepository.java index cbfa848df33b..f450aa61b523 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorizationFeatureLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorizationFeatureLinkRepository.java @@ -7,9 +7,8 @@ */ package org.dspace.app.rest.repository; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; - +import jakarta.annotation.Nullable; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.authorization.AuthorizationFeature; import org.dspace.app.rest.authorization.AuthorizationFeatureService; import org.dspace.app.rest.authorization.AuthorizationRestUtil; @@ -24,7 +23,7 @@ /** * Link repository for "feature" subresource of an individual authorization. */ -@Component(AuthorizationRest.CATEGORY + "." + AuthorizationRest.NAME + "." + AuthorizationRest.FEATURE) +@Component(AuthorizationRest.CATEGORY + "." + AuthorizationRest.PLURAL_NAME + "." + AuthorizationRest.FEATURE) public class AuthorizationFeatureLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorizationFeatureRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorizationFeatureRestRepository.java index 62781fe8e891..e161b07ed078 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorizationFeatureRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorizationFeatureRestRepository.java @@ -28,7 +28,7 @@ * * @author Andrea Bollini (andrea.bollini at 4science.it) */ -@Component(AuthorizationFeatureRest.CATEGORY + "." + AuthorizationFeatureRest.NAME) +@Component(AuthorizationFeatureRest.CATEGORY + "." + AuthorizationFeatureRest.PLURAL_NAME) public class AuthorizationFeatureRestRepository extends DSpaceRestRepository { @Autowired diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorizationObjectLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorizationObjectLinkRepository.java index f010b28fa5df..6c9697d4424b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorizationObjectLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorizationObjectLinkRepository.java @@ -8,9 +8,9 @@ package org.dspace.app.rest.repository; import java.sql.SQLException; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; +import jakarta.annotation.Nullable; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.authorization.AuthorizationRestUtil; import org.dspace.app.rest.model.AuthorizationRest; import org.dspace.app.rest.model.BaseObjectRest; @@ -24,7 +24,7 @@ /** * Link repository for "object" subresource of an individual authorization. */ -@Component(AuthorizationRest.CATEGORY + "." + AuthorizationRest.NAME + "." + AuthorizationRest.OBJECT) +@Component(AuthorizationRest.CATEGORY + "." + AuthorizationRest.PLURAL_NAME + "." + AuthorizationRest.OBJECT) public class AuthorizationObjectLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorizationRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorizationRestRepository.java index 137952866959..371733003dac 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorizationRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorizationRestRepository.java @@ -18,6 +18,8 @@ import java.util.stream.Collectors; import org.apache.commons.lang3.ObjectUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.app.rest.Parameter; import org.dspace.app.rest.SearchRestMethod; import org.dspace.app.rest.authorization.Authorization; @@ -34,8 +36,6 @@ import org.dspace.discovery.SearchServiceException; import org.dspace.eperson.EPerson; import org.dspace.eperson.service.EPersonService; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -49,10 +49,10 @@ * @author Andrea Bollini (andrea.bollini at 4science.it) */ -@Component(AuthorizationRest.CATEGORY + "." + AuthorizationRest.NAME) +@Component(AuthorizationRest.CATEGORY + "." + AuthorizationRest.PLURAL_NAME) public class AuthorizationRestRepository extends DSpaceRestRepository { - private static final Logger log = LoggerFactory.getLogger(AuthorizationRestRepository.class); + private static final Logger log = LogManager.getLogger(); @Autowired private AuthorizationFeatureService authorizationFeatureService; @@ -79,7 +79,7 @@ public AuthorizationRest findOne(Context context, String id) { try { featureName = authorizationRestUtil.getFeatureName(id); } catch (IllegalArgumentException e) { - log.warn(e.getMessage(), e); + log.warn(e::getMessage, e); return null; } try { @@ -87,7 +87,7 @@ public AuthorizationRest findOne(Context context, String id) { try { object = authorizationRestUtil.getObject(context, id); } catch (IllegalArgumentException e) { - log.warn("Object informations not found in the specified id " + id, e); + log.warn("Object information not found in the specified id {}", id, e); return null; } @@ -104,7 +104,7 @@ public AuthorizationRest findOne(Context context, String id) { try { user = authorizationRestUtil.getEperson(context, id); } catch (IllegalArgumentException e) { - log.warn("Invalid eperson informations in the specified id " + id, e); + log.warn("Invalid eperson information in the specified id {}", id, e); return null; } EPerson currUser = context.getCurrentUser(); @@ -136,7 +136,7 @@ public AuthorizationRest findOne(Context context, String id) { /** * It returns the list of matching available authorizations granted to the specified eperson or to the anonymous * user. Only administrators and the user identified by the epersonUuid parameter can access this method - * + * * @param uri * the uri of the object to check the authorization against * @param epersonUuid @@ -283,7 +283,7 @@ private List findByObjectAndFeature( /** * Return the user specified in the request parameter if valid - * + * * @param context * @param epersonUuid * @return diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamBundleLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamBundleLinkRepository.java index bcef1ef33d08..a2c3ab95bed5 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamBundleLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamBundleLinkRepository.java @@ -9,9 +9,9 @@ import java.sql.SQLException; import java.util.UUID; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; +import jakarta.annotation.Nullable; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.model.BitstreamRest; import org.dspace.app.rest.model.BundleRest; import org.dspace.app.rest.projection.Projection; @@ -27,7 +27,7 @@ /** * Link repository for "bundle" subresource of an individual bitstream. */ -@Component(BitstreamRest.CATEGORY + "." + BitstreamRest.NAME + "." + BitstreamRest.BUNDLE) +@Component(BitstreamRest.CATEGORY + "." + BitstreamRest.PLURAL_NAME + "." + BitstreamRest.BUNDLE) public class BitstreamBundleLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamFormatLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamFormatLinkRepository.java index 74454161a031..d86128c34469 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamFormatLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamFormatLinkRepository.java @@ -9,9 +9,9 @@ import java.sql.SQLException; import java.util.UUID; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; +import jakarta.annotation.Nullable; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.model.BitstreamFormatRest; import org.dspace.app.rest.model.BitstreamRest; import org.dspace.app.rest.projection.Projection; @@ -27,7 +27,7 @@ /** * Link repository for "format" subresource of an individual bitstream. */ -@Component(BitstreamRest.CATEGORY + "." + BitstreamRest.NAME + "." + BitstreamRest.FORMAT) +@Component(BitstreamRest.CATEGORY + "." + BitstreamRest.PLURAL_NAME + "." + BitstreamRest.FORMAT) public class BitstreamFormatLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamFormatRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamFormatRestRepository.java index 49585ee9db5a..1ae8600a5c94 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamFormatRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamFormatRestRepository.java @@ -10,11 +10,11 @@ import java.io.IOException; import java.sql.SQLException; import java.util.List; -import javax.servlet.ServletInputStream; -import javax.servlet.http.HttpServletRequest; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.ServletInputStream; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.exception.DSpaceBadRequestException; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.BitstreamFormatRest; @@ -35,7 +35,7 @@ * * @author Andrea Bollini (andrea.bollini at 4science.it) */ -@Component(BitstreamFormatRest.CATEGORY + "." + BitstreamFormatRest.NAME) +@Component(BitstreamFormatRest.CATEGORY + "." + BitstreamFormatRest.PLURAL_NAME) public class BitstreamFormatRestRepository extends DSpaceRestRepository { @Autowired diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamRestRepository.java index 12e27dccacf2..0350a5178e20 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamRestRepository.java @@ -13,10 +13,10 @@ import java.util.LinkedList; import java.util.List; import java.util.UUID; -import javax.servlet.http.HttpServletRequest; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; import org.dspace.app.rest.Parameter; import org.dspace.app.rest.SearchRestMethod; @@ -56,7 +56,7 @@ * @author Andrea Bollini (andrea.bollini at 4science.it) */ -@Component(BitstreamRest.CATEGORY + "." + BitstreamRest.NAME) +@Component(BitstreamRest.CATEGORY + "." + BitstreamRest.PLURAL_NAME) public class BitstreamRestRepository extends DSpaceObjectRestRepository { private final BitstreamService bs; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamThumbnailLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamThumbnailLinkRepository.java index afe864a8660d..eab0ba19407f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamThumbnailLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamThumbnailLinkRepository.java @@ -10,9 +10,9 @@ import java.sql.SQLException; import java.util.UUID; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; +import jakarta.annotation.Nullable; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.model.BitstreamRest; import org.dspace.app.rest.projection.Projection; import org.dspace.content.Bitstream; @@ -27,7 +27,7 @@ /** * Link repository for the thumbnail Bitstream of a Bitstream */ -@Component(BitstreamRest.CATEGORY + "." + BitstreamRest.NAME + "." + BitstreamRest.THUMBNAIL) +@Component(BitstreamRest.CATEGORY + "." + BitstreamRest.PLURAL_NAME + "." + BitstreamRest.THUMBNAIL) public class BitstreamThumbnailLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { @Autowired BitstreamService bitstreamService; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BrowseEntryLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BrowseEntryLinkRepository.java index f608595c3dda..52fcbeecfdd8 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BrowseEntryLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BrowseEntryLinkRepository.java @@ -10,8 +10,8 @@ import java.sql.SQLException; import java.util.Arrays; import java.util.Iterator; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; import org.dspace.app.rest.converter.BrowseEntryConverter; import org.dspace.app.rest.model.BrowseEntryRest; @@ -40,7 +40,7 @@ * * @author Andrea Bollini (andrea.bollini at 4science.it) */ -@Component(BrowseIndexRest.CATEGORY + "." + BrowseIndexRest.NAME + "." + BrowseIndexRest.LINK_ENTRIES) +@Component(BrowseIndexRest.CATEGORY + "." + BrowseIndexRest.PLURAL_NAME + "." + BrowseIndexRest.LINK_ENTRIES) public class BrowseEntryLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BrowseIndexRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BrowseIndexRestRepository.java index 6aedcee6c0e7..315ea8dbe23c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BrowseIndexRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BrowseIndexRestRepository.java @@ -32,7 +32,7 @@ * * @author Andrea Bollini (andrea.bollini at 4science.it) */ -@Component(BrowseIndexRest.CATEGORY + "." + BrowseIndexRest.NAME) +@Component(BrowseIndexRest.CATEGORY + "." + BrowseIndexRest.PLURAL_NAME) public class BrowseIndexRestRepository extends DSpaceRestRepository { @Autowired diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BrowseItemLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BrowseItemLinkRepository.java index baa79bc80ae7..286a3216bf04 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BrowseItemLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BrowseItemLinkRepository.java @@ -11,8 +11,8 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; import org.dspace.app.rest.model.BrowseIndexRest; import org.dspace.app.rest.model.ItemRest; @@ -42,7 +42,7 @@ * * @author Andrea Bollini (andrea.bollini at 4science.it) */ -@Component(BrowseIndexRest.CATEGORY + "." + BrowseIndexRest.NAME + "." + BrowseIndexRest.LINK_ITEMS) +@Component(BrowseIndexRest.CATEGORY + "." + BrowseIndexRest.PLURAL_NAME + "." + BrowseIndexRest.LINK_ITEMS) public class BrowseItemLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BulkAccessConditionRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BulkAccessConditionRestRepository.java index 2bf25978efc4..7d46031134ea 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BulkAccessConditionRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BulkAccessConditionRestRepository.java @@ -27,7 +27,7 @@ * * @author Mohamed Eskander (mohamed.eskander at 4science.it) */ -@Component(BulkAccessConditionRest.CATEGORY + "." + BulkAccessConditionRest.NAME) +@Component(BulkAccessConditionRest.CATEGORY + "." + BulkAccessConditionRest.PLURAL_NAME) public class BulkAccessConditionRestRepository extends DSpaceRestRepository { @Autowired @@ -82,4 +82,4 @@ private boolean isAuthorized(Context context) { } } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundleBitstreamLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundleBitstreamLinkRepository.java index b0a4488e037e..3893ef561b28 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundleBitstreamLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundleBitstreamLinkRepository.java @@ -9,9 +9,9 @@ import java.sql.SQLException; import java.util.UUID; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; +import jakarta.annotation.Nullable; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.model.BitstreamRest; import org.dspace.app.rest.model.BundleRest; import org.dspace.app.rest.projection.Projection; @@ -29,7 +29,7 @@ /** * Link repository for "bitstreams" subresource of an individual bundle. */ -@Component(BundleRest.CATEGORY + "." + BundleRest.NAME + "." + BundleRest.BITSTREAMS) +@Component(BundleRest.CATEGORY + "." + BundleRest.PLURAL_NAME + "." + BundleRest.BITSTREAMS) public class BundleBitstreamLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundleItemLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundleItemLinkRepository.java index 4df81d5054ef..d23e9e96e6ef 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundleItemLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundleItemLinkRepository.java @@ -9,9 +9,9 @@ import java.sql.SQLException; import java.util.UUID; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; +import jakarta.annotation.Nullable; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.model.BundleRest; import org.dspace.app.rest.model.ItemRest; import org.dspace.app.rest.projection.Projection; @@ -28,7 +28,7 @@ /** * Link repository for "item" subresource of an individual bundle. */ -@Component(BundleRest.CATEGORY + "." + BundleRest.NAME + "." + BundleRest.ITEM) +@Component(BundleRest.CATEGORY + "." + BundleRest.PLURAL_NAME + "." + BundleRest.ITEM) public class BundleItemLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundlePrimaryBitstreamLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundlePrimaryBitstreamLinkRepository.java index 3d11379cd328..e6f0e1cd2e42 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundlePrimaryBitstreamLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundlePrimaryBitstreamLinkRepository.java @@ -9,9 +9,9 @@ import java.sql.SQLException; import java.util.UUID; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; +import jakarta.annotation.Nullable; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.exception.DSpaceBadRequestException; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.BitstreamRest; @@ -30,7 +30,7 @@ /** * Link repository for "primaryBitstream" subresource of an individual bundle. */ -@Component(BundleRest.CATEGORY + "." + BundleRest.NAME + "." + BundleRest.PRIMARY_BITSTREAM) +@Component(BundleRest.CATEGORY + "." + BundleRest.PLURAL_NAME + "." + BundleRest.PRIMARY_BITSTREAM) public class BundlePrimaryBitstreamLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundleRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundleRestRepository.java index f750743db66e..dbc67dd0155b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundleRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundleRestRepository.java @@ -12,9 +12,9 @@ import java.sql.SQLException; import java.util.List; import java.util.UUID; -import javax.servlet.http.HttpServletRequest; import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -49,7 +49,7 @@ * @author Jelle Pelgrims (jelle.pelgrims at atmire.com) */ -@Component(BundleRest.CATEGORY + "." + BundleRest.NAME) +@Component(BundleRest.CATEGORY + "." + BundleRest.PLURAL_NAME) public class BundleRestRepository extends DSpaceObjectRestRepository { private static final Logger log = LogManager.getLogger(); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ClaimedTaskRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ClaimedTaskRestRepository.java index dbd37093a9fc..f68cb85c2be1 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ClaimedTaskRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ClaimedTaskRestRepository.java @@ -12,9 +12,9 @@ import java.util.Arrays; import java.util.List; 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.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -64,7 +64,7 @@ * @author Andrea Bollini (andrea.bollini at 4science.it) */ -@Component(PoolTaskRest.CATEGORY + "." + ClaimedTaskRest.NAME) +@Component(PoolTaskRest.CATEGORY + "." + ClaimedTaskRest.PLURAL_NAME) public class ClaimedTaskRestRepository extends DSpaceRestRepository implements InitializingBean { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ClaimedTaskStepLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ClaimedTaskStepLinkRepository.java index 9ee277171e84..3079d0360923 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ClaimedTaskStepLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ClaimedTaskStepLinkRepository.java @@ -8,9 +8,9 @@ package org.dspace.app.rest.repository; import java.sql.SQLException; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; +import jakarta.annotation.Nullable; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.model.ClaimedTaskRest; import org.dspace.app.rest.model.WorkflowStepRest; import org.dspace.app.rest.projection.Projection; @@ -26,7 +26,7 @@ /** * Link repository for the Steps subresources for an individual ClaimedTask */ -@Component(ClaimedTaskRest.CATEGORY + "." + ClaimedTaskRest.NAME + "." + ClaimedTaskRest.STEP) +@Component(ClaimedTaskRest.CATEGORY + "." + ClaimedTaskRest.PLURAL_NAME + "." + ClaimedTaskRest.STEP) public class ClaimedTaskStepLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { @Autowired diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionAdminGroupLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionAdminGroupLinkRepository.java index b239dc045771..22a6c31f1e94 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionAdminGroupLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionAdminGroupLinkRepository.java @@ -9,9 +9,9 @@ import java.sql.SQLException; import java.util.UUID; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; +import jakarta.annotation.Nullable; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.model.CollectionRest; import org.dspace.app.rest.model.GroupRest; import org.dspace.app.rest.projection.Projection; @@ -32,7 +32,7 @@ * Link repository for "admingroup" subresource of an individual collection. * */ -@Component(CollectionRest.CATEGORY + "." + CollectionRest.NAME + "." + CollectionRest.ADMIN_GROUP) +@Component(CollectionRest.CATEGORY + "." + CollectionRest.PLURAL_NAME + "." + CollectionRest.ADMIN_GROUP) public class CollectionAdminGroupLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { @Autowired diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionBitstreamReadGroupLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionBitstreamReadGroupLinkRepository.java index c1b322a49010..f96af09cb958 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionBitstreamReadGroupLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionBitstreamReadGroupLinkRepository.java @@ -10,9 +10,9 @@ import java.sql.SQLException; 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.dspace.app.rest.model.CollectionRest; import org.dspace.app.rest.model.GroupRest; import org.dspace.app.rest.projection.Projection; @@ -35,7 +35,7 @@ * Link repository for "BitstreamReadGroup" subresource of an individual collection. * */ -@Component(CollectionRest.CATEGORY + "." + CollectionRest.NAME + "." + CollectionRest.BITSTREAM_READ_GROUP) +@Component(CollectionRest.CATEGORY + "." + CollectionRest.PLURAL_NAME + "." + CollectionRest.BITSTREAM_READ_GROUP) public class CollectionBitstreamReadGroupLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionItemReadGroupLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionItemReadGroupLinkRepository.java index 77acb8e359c8..5badc4cb7bc2 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionItemReadGroupLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionItemReadGroupLinkRepository.java @@ -10,9 +10,9 @@ import java.sql.SQLException; 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.dspace.app.rest.model.CollectionRest; import org.dspace.app.rest.model.GroupRest; import org.dspace.app.rest.projection.Projection; @@ -35,7 +35,7 @@ * Link repository for "ItemReadGroup" subresource of an individual collection. * */ -@Component(CollectionRest.CATEGORY + "." + CollectionRest.NAME + "." + CollectionRest.ITEM_READ_GROUP) +@Component(CollectionRest.CATEGORY + "." + CollectionRest.PLURAL_NAME + "." + CollectionRest.ITEM_READ_GROUP) public class CollectionItemReadGroupLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { @Autowired diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionLicenseLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionLicenseLinkRepository.java index 4aceea599c4b..ed0b253bd764 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionLicenseLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionLicenseLinkRepository.java @@ -9,9 +9,9 @@ import java.sql.SQLException; 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.lang3.StringUtils; import org.dspace.app.rest.model.CollectionRest; import org.dspace.app.rest.model.LicenseRest; @@ -31,7 +31,7 @@ * * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it) */ -@Component(CollectionRest.CATEGORY + "." + CollectionRest.NAME + "." + CollectionRest.LICENSE) +@Component(CollectionRest.CATEGORY + "." + CollectionRest.PLURAL_NAME + "." + CollectionRest.LICENSE) public class CollectionLicenseLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionLogoLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionLogoLinkRepository.java index 94bf99fc1ff7..3549e4972d8f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionLogoLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionLogoLinkRepository.java @@ -9,9 +9,9 @@ import java.sql.SQLException; import java.util.UUID; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; +import jakarta.annotation.Nullable; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.model.BitstreamRest; import org.dspace.app.rest.model.CollectionRest; import org.dspace.app.rest.projection.Projection; @@ -27,7 +27,7 @@ /** * Link repository for "logo" subresource of an individual collection. */ -@Component(CollectionRest.CATEGORY + "." + CollectionRest.NAME + "." + CollectionRest.LOGO) +@Component(CollectionRest.CATEGORY + "." + CollectionRest.PLURAL_NAME + "." + CollectionRest.LOGO) public class CollectionLogoLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionMappedItemLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionMappedItemLinkRepository.java index 1118d0bd7bd4..892130239e76 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionMappedItemLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionMappedItemLinkRepository.java @@ -11,9 +11,9 @@ import java.util.ArrayList; 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.dspace.app.rest.model.CollectionRest; import org.dspace.app.rest.model.ItemRest; import org.dspace.app.rest.projection.Projection; @@ -32,7 +32,7 @@ /** * Link repository for "mappedItems" subresource of an individual collection. */ -@Component(CollectionRest.CATEGORY + "." + CollectionRest.NAME + "." + CollectionRest.MAPPED_ITEMS) +@Component(CollectionRest.CATEGORY + "." + CollectionRest.PLURAL_NAME + "." + CollectionRest.MAPPED_ITEMS) public class CollectionMappedItemLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionParentCommunityLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionParentCommunityLinkRepository.java index fd21397b36af..bbec0aa1d40b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionParentCommunityLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionParentCommunityLinkRepository.java @@ -9,9 +9,9 @@ import java.sql.SQLException; import java.util.UUID; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; +import jakarta.annotation.Nullable; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.model.CollectionRest; import org.dspace.app.rest.model.CommunityRest; import org.dspace.app.rest.projection.Projection; @@ -28,7 +28,7 @@ /** * LinkRepository for the ParentCommunity object for a Collection */ -@Component(CollectionRest.CATEGORY + "." + CollectionRest.NAME + "." + CollectionRest.PARENT_COMMUNITY) +@Component(CollectionRest.CATEGORY + "." + CollectionRest.PLURAL_NAME + "." + CollectionRest.PARENT_COMMUNITY) public class CollectionParentCommunityLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java index ba3163a4447c..0d59aeb254b5 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java @@ -14,11 +14,11 @@ import java.util.Objects; import java.util.SortedMap; import java.util.UUID; -import javax.servlet.ServletInputStream; -import javax.servlet.http.HttpServletRequest; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.ServletInputStream; +import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.dspace.app.rest.Parameter; @@ -81,7 +81,7 @@ * @author Andrea Bollini (andrea.bollini at 4science.it) */ -@Component(CollectionRest.CATEGORY + "." + CollectionRest.NAME) +@Component(CollectionRest.CATEGORY + "." + CollectionRest.PLURAL_NAME) public class CollectionRestRepository extends DSpaceObjectRestRepository { public static Logger log = org.apache.logging.log4j.LogManager.getLogger(CollectionRestRepository.class); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionSubmitterGroupLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionSubmitterGroupLinkRepository.java index 4e6d901387ee..48acc2b83294 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionSubmitterGroupLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionSubmitterGroupLinkRepository.java @@ -9,9 +9,9 @@ import java.sql.SQLException; import java.util.UUID; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; +import jakarta.annotation.Nullable; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.model.CollectionRest; import org.dspace.app.rest.model.GroupRest; import org.dspace.app.rest.projection.Projection; @@ -32,7 +32,7 @@ * Link repository for "submittergroup" subresource of an individual collection. * */ -@Component(CollectionRest.CATEGORY + "." + CollectionRest.NAME + "." + CollectionRest.SUBMITTERS_GROUP) +@Component(CollectionRest.CATEGORY + "." + CollectionRest.PLURAL_NAME + "." + CollectionRest.SUBMITTERS_GROUP) public class CollectionSubmitterGroupLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { @Autowired diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityAdminGroupLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityAdminGroupLinkRepository.java index b2ca20a7bceb..cf36b8c915c2 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityAdminGroupLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityAdminGroupLinkRepository.java @@ -9,9 +9,9 @@ import java.sql.SQLException; import java.util.UUID; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; +import jakarta.annotation.Nullable; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.model.CommunityRest; import org.dspace.app.rest.model.GroupRest; import org.dspace.app.rest.projection.Projection; @@ -32,7 +32,7 @@ * Link repository for "admingroup" subresource of an individual community. * */ -@Component(CommunityRest.CATEGORY + "." + CommunityRest.NAME + "." + CommunityRest.ADMIN_GROUP) +@Component(CommunityRest.CATEGORY + "." + CommunityRest.PLURAL_NAME + "." + CommunityRest.ADMIN_GROUP) public class CommunityAdminGroupLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { @Autowired diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityCollectionLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityCollectionLinkRepository.java index 3c728d8c31b9..1fa3468a519e 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityCollectionLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityCollectionLinkRepository.java @@ -12,9 +12,9 @@ import java.util.LinkedList; 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.dspace.app.rest.model.CollectionRest; import org.dspace.app.rest.model.CommunityRest; import org.dspace.app.rest.projection.Projection; @@ -40,7 +40,7 @@ /** * Link repository for "collections" subresource of an individual community. */ -@Component(CommunityRest.CATEGORY + "." + CommunityRest.NAME + "." + CommunityRest.COLLECTIONS) +@Component(CommunityRest.CATEGORY + "." + CommunityRest.PLURAL_NAME + "." + CommunityRest.COLLECTIONS) public class CommunityCollectionLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityLogoLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityLogoLinkRepository.java index e3892462a32b..1c828f81de8a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityLogoLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityLogoLinkRepository.java @@ -9,9 +9,9 @@ import java.sql.SQLException; import java.util.UUID; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; +import jakarta.annotation.Nullable; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.model.BitstreamRest; import org.dspace.app.rest.model.CommunityRest; import org.dspace.app.rest.projection.Projection; @@ -27,7 +27,7 @@ /** * Link repository for "logo" subresource of an individual community. */ -@Component(CommunityRest.CATEGORY + "." + CommunityRest.NAME + "." + CommunityRest.LOGO) +@Component(CommunityRest.CATEGORY + "." + CommunityRest.PLURAL_NAME + "." + CommunityRest.LOGO) public class CommunityLogoLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityParentCommunityLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityParentCommunityLinkRepository.java index ab23f3afed0d..a9c778501596 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityParentCommunityLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityParentCommunityLinkRepository.java @@ -9,9 +9,9 @@ import java.sql.SQLException; import java.util.UUID; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; +import jakarta.annotation.Nullable; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.model.CommunityRest; import org.dspace.app.rest.projection.Projection; import org.dspace.content.Community; @@ -26,7 +26,7 @@ /** * LinkRepository for the ParentCommunity object for a Community */ -@Component(CommunityRest.CATEGORY + "." + CommunityRest.NAME + "." + CommunityRest.PARENT_COMMUNITY) +@Component(CommunityRest.CATEGORY + "." + CommunityRest.PLURAL_NAME + "." + CommunityRest.PARENT_COMMUNITY) public class CommunityParentCommunityLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { @@ -51,7 +51,7 @@ public CommunityRest getParentCommunity(@Nullable HttpServletRequest httpServlet Context context = obtainContext(); Community community = communityService.find(context, communityId); if (community == null) { - throw new ResourceNotFoundException("No such community: " + community); + throw new ResourceNotFoundException("No such community: " + communityId); } Community parentCommunity = (Community) communityService.getParentObject(context, community); if (parentCommunity == null) { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityRestRepository.java index 927b6a0b98f5..0d4e6be133e8 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityRestRepository.java @@ -13,11 +13,11 @@ import java.util.List; import java.util.SortedMap; import java.util.UUID; -import javax.servlet.ServletInputStream; -import javax.servlet.http.HttpServletRequest; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.ServletInputStream; +import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.dspace.app.rest.Parameter; @@ -61,7 +61,7 @@ * @author Andrea Bollini (andrea.bollini at 4science.it) */ -@Component(CommunityRest.CATEGORY + "." + CommunityRest.NAME) +@Component(CommunityRest.CATEGORY + "." + CommunityRest.PLURAL_NAME) public class CommunityRestRepository extends DSpaceObjectRestRepository { private static final Logger log = org.apache.logging.log4j.LogManager diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunitySubcommunityLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunitySubcommunityLinkRepository.java index 135d964f3f42..e47e684080cb 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunitySubcommunityLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunitySubcommunityLinkRepository.java @@ -12,9 +12,9 @@ import java.util.LinkedList; 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.dspace.app.rest.model.CommunityRest; import org.dspace.app.rest.projection.Projection; import org.dspace.content.Community; @@ -38,7 +38,7 @@ /** * Link repository for "subcommunities" subresource of an individual community. */ -@Component(CommunityRest.CATEGORY + "." + CommunityRest.NAME + "." + CommunityRest.SUBCOMMUNITIES) +@Component(CommunityRest.CATEGORY + "." + CommunityRest.PLURAL_NAME + "." + CommunityRest.SUBCOMMUNITIES) public class CommunitySubcommunityLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ConfigurationRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ConfigurationRestRepository.java index caadb9f6f34e..b524ca776df2 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ConfigurationRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ConfigurationRestRepository.java @@ -24,7 +24,7 @@ /** * This is the repository responsible of exposing configuration properties */ -@Component(PropertyRest.CATEGORY + "." + PropertyRest.NAME) +@Component(PropertyRest.CATEGORY + "." + PropertyRest.PLURAL_NAME) public class ConfigurationRestRepository extends DSpaceRestRepository { private ConfigurationService configurationService; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ContentReportRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ContentReportRestRepository.java new file mode 100644 index 000000000000..567fa79a448f --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ContentReportRestRepository.java @@ -0,0 +1,97 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.rest.repository; + +import java.sql.SQLException; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import org.dspace.app.rest.converter.FilteredItemConverter; +import org.dspace.app.rest.model.ContentReportSupportRest; +import org.dspace.app.rest.model.FilteredCollectionsQuery; +import org.dspace.app.rest.model.FilteredCollectionsRest; +import org.dspace.app.rest.model.FilteredItemRest; +import org.dspace.app.rest.model.FilteredItemsQueryPredicate; +import org.dspace.app.rest.model.FilteredItemsQueryRest; +import org.dspace.app.rest.model.FilteredItemsRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.content.MetadataField; +import org.dspace.contentreport.Filter; +import org.dspace.contentreport.FilteredCollection; +import org.dspace.contentreport.FilteredCollections; +import org.dspace.contentreport.FilteredItems; +import org.dspace.contentreport.FilteredItemsQuery; +import org.dspace.contentreport.QueryPredicate; +import org.dspace.contentreport.service.ContentReportService; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Component; + +/** + * This repository serves the content reports ported from DSpace 6.x + * (Filtered Collections and Filtered Items). + * @author Jean-François Morin (Université Laval) + */ +@Component(ContentReportSupportRest.CATEGORY + "." + ContentReportSupportRest.NAME) +public class ContentReportRestRepository extends AbstractDSpaceRestRepository { + + @Autowired + private ContentReportService contentReportService; + @Autowired + private FilteredItemConverter itemConverter; + + public ContentReportSupportRest getContentReportSupport() { + return new ContentReportSupportRest(); + } + + public FilteredCollectionsRest findFilteredCollections(Context context, FilteredCollectionsQuery query) { + Set filters = query.getFilters(); + + List colls = contentReportService.findFilteredCollections(context, filters); + FilteredCollections report = FilteredCollections.of(colls); + + FilteredCollectionsRest reportRest = FilteredCollectionsRest.of(report); + reportRest.setId("filteredcollections"); + return reportRest; + } + + public FilteredItemsRest findFilteredItems(Context context, FilteredItemsQueryRest queryRest, Pageable pageable) { + List predicates = queryRest.getQueryPredicates().stream() + .map(pred -> convertPredicate(context, pred)) + .collect(Collectors.toList()); + FilteredItemsQuery query = new FilteredItemsQuery(); + query.setCollections(queryRest.getCollections()); + query.setQueryPredicates(predicates); + query.setFilters(queryRest.getFilters()); + query.setAdditionalFields(queryRest.getAdditionalFields()); + query.setOffset(pageable.getOffset()); + query.setPageLimit(pageable.getPageSize()); + + FilteredItems items = contentReportService.findFilteredItems(context, query); + + List filteredItemsRest = items.getItems().stream() + .map(item -> itemConverter.convert(item, Projection.DEFAULT)) + .collect(Collectors.toList()); + FilteredItemsRest report = FilteredItemsRest.of(filteredItemsRest, items.getItemCount()); + report.setId("filtereditems"); + + return report; + } + + private QueryPredicate convertPredicate(Context context, FilteredItemsQueryPredicate predicate) { + try { + List fields = contentReportService.getMetadataFields(context, predicate.getField()); + return QueryPredicate.of(fields, predicate.getOperator(), predicate.getValue()); + } catch (SQLException e) { + throw new IllegalArgumentException(e.getMessage(), e); + } + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CorrectionTypeRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CorrectionTypeRestRepository.java new file mode 100644 index 000000000000..14dc7d9fb265 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CorrectionTypeRestRepository.java @@ -0,0 +1,93 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.rest.repository; + +import java.sql.SQLException; +import java.util.List; +import java.util.Objects; +import java.util.UUID; + +import org.dspace.app.rest.Parameter; +import org.dspace.app.rest.SearchRestMethod; +import org.dspace.app.rest.exception.RESTAuthorizationException; +import org.dspace.app.rest.exception.UnprocessableEntityException; +import org.dspace.app.rest.model.CorrectionTypeRest; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.Item; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.dspace.correctiontype.CorrectionType; +import org.dspace.correctiontype.service.CorrectionTypeService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + +/** + * The CorrectionType REST Repository + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + */ +@Component(CorrectionTypeRest.CATEGORY + "." + CorrectionTypeRest.PLURAL_NAME) +public class CorrectionTypeRestRepository extends DSpaceRestRepository { + + @Autowired + private ItemService itemService; + @Autowired + private CorrectionTypeService correctionTypeService; + + @PreAuthorize("hasAuthority('AUTHENTICATED')") + @Override + public CorrectionTypeRest findOne(Context context, String id) { + CorrectionType correctionType = correctionTypeService.findOne(id); + return Objects.nonNull(correctionType) ? converter.toRest(correctionType, utils.obtainProjection()) : null; + } + + @PreAuthorize("hasAuthority('AUTHENTICATED')") + @Override + public Page findAll(Context context, Pageable pageable) { + return converter.toRestPage(correctionTypeService.findAll(), pageable, utils.obtainProjection()); + } + + @PreAuthorize("hasAuthority('AUTHENTICATED')") + @SearchRestMethod(name = "findByItem") + public Page findByItem(@Parameter(value = "uuid",required = true) UUID uuid,Pageable pageable) { + Context context = obtainContext(); + try { + Item item = itemService.find(context, uuid); + if (Objects.isNull(item)) { + throw new UnprocessableEntityException("Item with uuid:" + uuid + " not found"); + } + + List correctionTypes; + try { + correctionTypes = correctionTypeService.findByItem(context, item); + } catch (AuthorizeException e) { + throw new RESTAuthorizationException(e); + } + + return converter.toRestPage(correctionTypes, pageable, utils.obtainProjection()); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + + @PreAuthorize("hasAuthority('AUTHENTICATED')") + @SearchRestMethod(name = "findByTopic") + public CorrectionTypeRest findByTopic(@Parameter(value = "topic", required = true) String topic) { + CorrectionType correctionType = correctionTypeService.findByTopic(topic); + return Objects.nonNull(correctionType) ? converter.toRest(correctionType, utils.obtainProjection()) : null; + } + + @Override + public Class getDomainClass() { + return CorrectionTypeRest.class; + } + +} \ No newline at end of file diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java index a93f5e55dc02..cf7c556e3ac3 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java @@ -14,10 +14,9 @@ import java.util.List; import java.util.Optional; import java.util.UUID; -import javax.servlet.http.HttpServletRequest; import com.fasterxml.jackson.databind.JsonNode; -import org.apache.logging.log4j.Logger; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.exception.DSpaceBadRequestException; import org.dspace.app.rest.exception.RESTAuthorizationException; import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException; @@ -26,7 +25,6 @@ import org.dspace.app.rest.model.RestAddressableModel; import org.dspace.app.rest.model.patch.Patch; import org.dspace.authorize.AuthorizeException; -import org.dspace.content.service.MetadataFieldService; import org.dspace.core.Context; import org.springframework.beans.factory.BeanNameAware; import org.springframework.beans.factory.annotation.Autowired; @@ -34,8 +32,8 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; +import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.PagingAndSortingRepository; -import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.multipart.MultipartFile; /** @@ -48,9 +46,7 @@ */ public abstract class DSpaceRestRepository extends AbstractDSpaceRestRepository - implements PagingAndSortingRepository, BeanNameAware { - - private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(DSpaceRestRepository.class); + implements CrudRepository, PagingAndSortingRepository, BeanNameAware { private String thisRepositoryBeanName; private DSpaceRestRepository thisRepository; @@ -58,9 +54,6 @@ public abstract class DSpaceRestRepository findById(ID id) { * the rest object id * @return the REST object identified by its ID */ - @PreAuthorize("hasAuthority('ADMIN')") public abstract T findOne(Context context, ID id); @Override /** * Return true if an object exist for the specified ID. The default implementation is inefficient as it retrieves - * the actual object to state that it exists. This could lead to retrieve and inizialize lot of linked objects + * the actual object to state that it exists. This could lead to retrieve and initialize lot of linked objects */ public boolean existsById(ID id) { return findById(id).isPresent(); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/DiscoveryRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/DiscoveryRestRepository.java index 4b9b7d764478..fb2bff8e55d9 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/DiscoveryRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/DiscoveryRestRepository.java @@ -44,7 +44,7 @@ * information lookup * that has to be done for the endpoint */ -@Component(SearchResultsRest.CATEGORY + "." + SearchResultsRest.NAME) +@Component(SearchResultsRest.CATEGORY + "." + SearchResultsRest.PLURAL_NAME) public class DiscoveryRestRepository extends AbstractDSpaceRestRepository { private static final Logger log = LogManager.getLogger(); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/DuplicateRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/DuplicateRestRepository.java new file mode 100644 index 000000000000..8730211a5eba --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/DuplicateRestRepository.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.app.rest.repository; + +import java.sql.SQLException; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.UUID; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.dspace.app.rest.DiscoverableEndpointsService; +import org.dspace.app.rest.Parameter; +import org.dspace.app.rest.SearchRestMethod; +import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException; +import org.dspace.app.rest.model.PotentialDuplicateRest; +import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.content.Item; +import org.dspace.content.service.DuplicateDetectionService; +import org.dspace.content.service.ItemService; +import org.dspace.content.virtual.PotentialDuplicate; +import org.dspace.core.Context; +import org.dspace.discovery.SearchServiceException; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.hateoas.Link; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + +/** + * The REST repository for the api/submission/duplicates endpoint, which handles requests for finding + * potential duplicates of a given item (archived or in-progress). + * + * Find one and find all are not implemented as actual REST methods because a duplicate is the result + * of comparing an item with other indexed items, not an object that can be referenced by some kind of ID, but + * we must at least implement the Java methods here in order to extend DSpaceRestRepository and implement + * SearchRestMethods. + * + * @author Kim Shepherd + */ +@ConditionalOnProperty("duplicate.enable") +@Component(PotentialDuplicateRest.CATEGORY + "." + PotentialDuplicateRest.NAME) +public class DuplicateRestRepository extends DSpaceRestRepository + implements InitializingBean { + + /** + * Discoverable endpoints service + */ + @Autowired + DiscoverableEndpointsService discoverableEndpointsService; + + /** + * Duplicate detection service + */ + @Autowired + DuplicateDetectionService duplicateDetectionService; + + /** + * Item service + */ + @Autowired + ItemService itemService; + + /** + * Logger + */ + private final static Logger log = LogManager.getLogger(); + + /** + * Register this repository endpoint as /api/submission/duplicates + * @throws Exception + */ + @Override + public void afterPropertiesSet() throws Exception { + discoverableEndpointsService + .register(this, Arrays.asList(Link.of( + "/api/" + PotentialDuplicateRest.CATEGORY + "/" + PotentialDuplicateRest.NAME + "/search", + PotentialDuplicateRest.NAME + "-search"))); + } + + /** + * This REST method is NOT IMPLEMENTED - it does not make sense in duplicate detection, in which the only + * real addressable objects involved are Items. + * + * @param context + * the dspace context + * @param name + * the rest object id + * @return not implemented + * @throws RepositoryMethodNotImplementedException + */ + @PreAuthorize("permitAll()") + @Override + public PotentialDuplicateRest findOne(Context context, String name) { + throw new RepositoryMethodNotImplementedException("Duplicate detection endpoint only implements searchBy", ""); + } + + /** + * This REST method is NOT IMPLEMENTED - it does not make sense in duplicate detection, where there can be no "all" + * + * @param context + * the dspace context + * @return not implemented + * @throws RepositoryMethodNotImplementedException + */ + @PreAuthorize("permitAll()") + @Override + public Page findAll(Context context, Pageable pageable) { + throw new RepositoryMethodNotImplementedException("Duplicate detection endpoint only implements searchBy", ""); + } + + /** + * Return a paged list of potential duplicate matches for the given item ID. This may be an item wrapped in + * an in-progress item wrapper like workspace or workflow, as long as the current user has READ access to this item. + * Results from the service search method will only contain matches that lead to items which are readable by + * the current user. + * + * @param uuid The item UUID to search + * @param pageable Pagination options + * @return Paged list of potential duplicates + * @throws Exception + */ + @PreAuthorize("hasPermission(#uuid, 'ITEM', 'READ')") + @SearchRestMethod(name = "findByItem") + public Page findByItem(@Parameter(value = "uuid", required = true) UUID uuid, + Pageable pageable) { + // Instantiate object to represent this item + Item item; + // Instantiate list of potential duplicates which we will convert and return as paged ItemRest list + List potentialDuplicates = new LinkedList<>(); + // Instantiate total count + int total = 0; + // Obtain context + Context context = ContextUtil.obtainCurrentRequestContext(); + + // Try to get item based on UUID parameter + try { + item = itemService.find(context, uuid); + } catch (SQLException e) { + throw new ResourceNotFoundException(e.getMessage()); + } + + // If the item is null or otherwise invalid (template, etc) then throw an appropriate error + if (item == null) { + throw new ResourceNotFoundException("No such item: " + uuid); + } + if (item.getTemplateItemOf() != null) { + throw new IllegalArgumentException("Cannot get duplicates for template item"); + } + + try { + // Search for the list of potential duplicates + potentialDuplicates = duplicateDetectionService.getPotentialDuplicates(context, item); + } catch (SearchServiceException e) { + // If the search fails, log an error and return an empty list rather than throwing a fatal error + log.error("Search service error retrieving duplicates: {}", e.getMessage()); + } + + // Construct rest pages and return + Page restPage = converter.toRestPage(potentialDuplicates, pageable, total, + utils.obtainProjection()); + + return restPage; + + } + + /** + * Return the domain class for potential duplicate objects + * @return PotentialDuplicateRest.class + */ + @Override + public Class getDomainClass() { + return PotentialDuplicateRest.class; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonGroupLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonGroupLinkRepository.java index 0aeda20678a4..5825fa003e93 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonGroupLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonGroupLinkRepository.java @@ -9,9 +9,9 @@ import java.sql.SQLException; import java.util.UUID; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; +import jakarta.annotation.Nullable; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.model.EPersonRest; import org.dspace.app.rest.model.GroupRest; import org.dspace.app.rest.projection.Projection; @@ -29,7 +29,7 @@ /** * Link repository for the direct "groups" subresource of an individual eperson. */ -@Component(EPersonRest.CATEGORY + "." + EPersonRest.NAME + "." + EPersonRest.GROUPS) +@Component(EPersonRest.CATEGORY + "." + EPersonRest.PLURAL_NAME + "." + EPersonRest.GROUPS) public class EPersonGroupLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java index bd42b7420649..ce1edc1eed92 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java @@ -12,9 +12,9 @@ import java.util.Arrays; import java.util.List; import java.util.UUID; -import javax.servlet.http.HttpServletRequest; import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -60,7 +60,7 @@ * @author Andrea Bollini (andrea.bollini at 4science.it) */ -@Component(EPersonRest.CATEGORY + "." + EPersonRest.NAME) +@Component(EPersonRest.CATEGORY + "." + EPersonRest.PLURAL_NAME) public class EPersonRestRepository extends DSpaceObjectRestRepository implements InitializingBean { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EntityTypeRelationshipLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EntityTypeRelationshipLinkRepository.java index 3d71ddd9bb30..8b30f8643513 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EntityTypeRelationshipLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EntityTypeRelationshipLinkRepository.java @@ -9,9 +9,9 @@ import java.sql.SQLException; import java.util.List; import java.util.Objects; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; +import jakarta.annotation.Nullable; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.model.EntityTypeRest; import org.dspace.app.rest.model.RelationshipTypeRest; import org.dspace.app.rest.projection.Projection; @@ -31,7 +31,7 @@ * * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.it) */ -@Component(EntityTypeRest.CATEGORY + "." + EntityTypeRest.NAME + "." + EntityTypeRest.RELATION_SHIP_TYPES) +@Component(EntityTypeRest.CATEGORY + "." + EntityTypeRest.PLURAL_NAME + "." + EntityTypeRest.RELATION_SHIP_TYPES) public class EntityTypeRelationshipLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { @@ -70,4 +70,4 @@ public Page getEntityTypeRelationship(@Nullable HttpServle } } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EntityTypeRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EntityTypeRestRepository.java index eec67b6fc93b..4ad7aa092c1e 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EntityTypeRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EntityTypeRestRepository.java @@ -31,7 +31,7 @@ /** * This is the repository that is responsible to manage EntityType Rest objects */ -@Component(EntityTypeRest.CATEGORY + "." + EntityTypeRest.NAME) +@Component(EntityTypeRest.CATEGORY + "." + EntityTypeRest.PLURAL_NAME) public class EntityTypeRestRepository extends DSpaceRestRepository { @Autowired diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ExternalSourceEntityTypeLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ExternalSourceEntityTypeLinkRepository.java index 4ed39c1893ea..4e371f44257a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ExternalSourceEntityTypeLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ExternalSourceEntityTypeLinkRepository.java @@ -10,9 +10,9 @@ import java.util.Collections; import java.util.List; import java.util.Objects; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; +import jakarta.annotation.Nullable; +import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.collections4.CollectionUtils; import org.dspace.app.rest.model.EntityTypeRest; import org.dspace.app.rest.model.ExternalSourceRest; @@ -34,7 +34,7 @@ * * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.it) */ -@Component(ExternalSourceRest.CATEGORY + "." + ExternalSourceRest.NAME + "." + ExternalSourceRest.ENTITY_TYPES) +@Component(ExternalSourceRest.CATEGORY + "." + ExternalSourceRest.PLURAL_NAME + "." + ExternalSourceRest.ENTITY_TYPES) public class ExternalSourceEntityTypeLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { @@ -70,4 +70,4 @@ public Page getSupportedEntityTypes(@Nullable HttpServletRequest return converter.toRestPage(entityTypes, pageable, total, utils.obtainProjection()); } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ExternalSourceRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ExternalSourceRestRepository.java index 7888603f1967..736261cd263b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ExternalSourceRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ExternalSourceRestRepository.java @@ -31,7 +31,7 @@ * This is the Repository that is responsible for the functionality and implementations coming from * {@link org.dspace.app.rest.ExternalSourcesRestController} */ -@Component(ExternalSourceRest.CATEGORY + "." + ExternalSourceRest.NAME) +@Component(ExternalSourceRest.CATEGORY + "." + ExternalSourceRest.PLURAL_NAME) public class ExternalSourceRestRepository extends DSpaceRestRepository { @Autowired diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/FeedbackRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/FeedbackRestRepository.java index 8fd28bbe94b8..2db412e5de74 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/FeedbackRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/FeedbackRestRepository.java @@ -8,10 +8,10 @@ package org.dspace.app.rest.repository; import java.io.IOException; import java.sql.SQLException; -import javax.mail.MessagingException; -import javax.servlet.http.HttpServletRequest; import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.mail.MessagingException; +import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.lang.StringUtils; import org.dspace.app.rest.exception.DSpaceBadRequestException; import org.dspace.app.rest.exception.DSpaceFeedbackNotFoundException; @@ -33,7 +33,7 @@ * * @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com) */ -@Component(FeedbackRest.CATEGORY + "." + FeedbackRest.NAME) +@Component(FeedbackRest.CATEGORY + "." + FeedbackRest.PLURAL_NAME) public class FeedbackRestRepository extends DSpaceRestRepository { @Autowired @@ -79,8 +79,14 @@ protected FeedbackRest createAndReturn(Context context) throws AuthorizeExceptio throw new DSpaceBadRequestException("e-mail and message fields are mandatory!"); } + String pageUrl = feedbackRest.getPage(); + String urlPrefix = configurationService.getProperty("dspace.ui.url"); + if (StringUtils.isNotBlank(pageUrl) && ! StringUtils.startsWith(pageUrl, urlPrefix)) { + throw new DSpaceBadRequestException("unexpected page url was submitted"); + } + try { - feedbackService.sendEmail(context, req, recipientEmail, senderEmail, message, feedbackRest.getPage()); + feedbackService.sendEmail(context, req, recipientEmail, senderEmail, message, pageUrl); } catch (IOException | MessagingException e) { throw new RuntimeException(e.getMessage(), e); } @@ -100,4 +106,4 @@ public void setFeedbackService(FeedbackService feedbackService) { this.feedbackService = feedbackService; } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/GroupEPersonLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/GroupEPersonLinkRepository.java index 1ce278893d17..d1ec7f693e7a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/GroupEPersonLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/GroupEPersonLinkRepository.java @@ -11,9 +11,9 @@ import java.util.List; import java.util.Set; import java.util.UUID; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; +import jakarta.annotation.Nullable; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.model.GroupRest; import org.dspace.app.rest.projection.Projection; import org.dspace.core.Context; @@ -31,7 +31,7 @@ /** * Link repository for "epersons" subresource of an individual group. */ -@Component(GroupRest.CATEGORY + "." + GroupRest.NAME + "." + GroupRest.EPERSONS) +@Component(GroupRest.CATEGORY + "." + GroupRest.PLURAL_NAME + "." + GroupRest.EPERSONS) public class GroupEPersonLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/GroupGroupLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/GroupGroupLinkRepository.java index 564e941d45cc..36f0c47374d2 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/GroupGroupLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/GroupGroupLinkRepository.java @@ -10,9 +10,9 @@ import java.sql.SQLException; 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.dspace.app.rest.model.GroupRest; import org.dspace.app.rest.projection.Projection; import org.dspace.core.Context; @@ -28,7 +28,7 @@ /** * Link repository for "groups" subresource of an individual group. */ -@Component(GroupRest.CATEGORY + "." + GroupRest.NAME + "." + GroupRest.SUBGROUPS) +@Component(GroupRest.CATEGORY + "." + GroupRest.PLURAL_NAME + "." + GroupRest.SUBGROUPS) public class GroupGroupLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/GroupParentObjectLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/GroupParentObjectLinkRepository.java index 3d7cdf8f80c9..973d695d6604 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/GroupParentObjectLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/GroupParentObjectLinkRepository.java @@ -9,9 +9,9 @@ import java.sql.SQLException; import java.util.UUID; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; +import jakarta.annotation.Nullable; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.model.DSpaceObjectRest; import org.dspace.app.rest.model.GroupRest; import org.dspace.app.rest.projection.Projection; @@ -29,7 +29,7 @@ /** * Link repository for the parent object of a group. */ -@Component(GroupRest.CATEGORY + "." + GroupRest.NAME + "." + GroupRest.OBJECT) +@Component(GroupRest.CATEGORY + "." + GroupRest.PLURAL_NAME + "." + GroupRest.OBJECT) public class GroupParentObjectLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/GroupRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/GroupRestRepository.java index a3b525387c62..160f838741ae 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/GroupRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/GroupRestRepository.java @@ -13,9 +13,9 @@ import java.sql.SQLException; import java.util.List; import java.util.UUID; -import javax.servlet.http.HttpServletRequest; import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.Parameter; import org.dspace.app.rest.SearchRestMethod; import org.dspace.app.rest.converter.MetadataConverter; @@ -42,7 +42,7 @@ * @author Andrea Bollini (andrea.bollini at 4science.it) */ -@Component(GroupRest.CATEGORY + "." + GroupRest.NAME) +@Component(GroupRest.CATEGORY + "." + GroupRest.PLURAL_NAME) public class GroupRestRepository extends DSpaceObjectRestRepository { @Autowired GroupService gs; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/HarvestedCollectionRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/HarvestedCollectionRestRepository.java index ccacc24418fd..7794e1fc9193 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/HarvestedCollectionRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/HarvestedCollectionRestRepository.java @@ -13,10 +13,10 @@ import java.util.Arrays; import java.util.List; import java.util.Map; -import javax.servlet.ServletInputStream; -import javax.servlet.http.HttpServletRequest; import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.ServletInputStream; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.converter.HarvestedCollectionConverter; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.HarvestTypeEnum; @@ -35,7 +35,7 @@ * * @author Jelle Pelgrims (jelle.pelgrims at atmire.com) */ -@Component(HarvestedCollectionRest.CATEGORY + "." + HarvestedCollectionRest.NAME) +@Component(HarvestedCollectionRest.CATEGORY + "." + HarvestedCollectionRest.PLURAL_NAME) public class HarvestedCollectionRestRepository extends AbstractDSpaceRestRepository { @Autowired @@ -148,7 +148,7 @@ private void updateCollectionHarvestSettings(Context context, HarvestedCollectio /** * Function used to verify that the harvest settings work - * @param harvestedCollectionRest A object containg the harvest settings to be tested + * @param harvestedCollectionRest A object containing the harvest settings to be tested * @return */ private List testHarvestSettings(HarvestedCollectionRest harvestedCollectionRest) { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/IdentifierRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/IdentifierRestRepository.java index 1be569d18e5d..bbc79cbf426d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/IdentifierRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/IdentifierRestRepository.java @@ -16,11 +16,10 @@ import java.util.Arrays; import java.util.List; import java.util.UUID; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.ws.rs.NotSupportedException; -import org.atteo.evo.inflector.English; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.ws.rs.NotSupportedException; import org.dspace.app.rest.DiscoverableEndpointsService; import org.dspace.app.rest.Parameter; import org.dspace.app.rest.SearchRestMethod; @@ -68,13 +67,13 @@ /** * Item REST Repository and Controller for persistent identifiers. * The controller annotation and endpoint registration allows the "find DSO by identifier" method which was - * previously implmented in org.dspace.app.rest.IdentifierRestController + * previously implemented in org.dspace.app.rest.IdentifierRestController * * @author Kim Shepherd */ @RestController -@RequestMapping("/api/" + IdentifierRestRepository.CATEGORY) -@Component(IdentifierRestRepository.CATEGORY + "." + IdentifierRestRepository.NAME) +@RequestMapping("/api/" + IdentifierRest.CATEGORY) +@Component(IdentifierRest.CATEGORY + "." + IdentifierRest.PLURAL_NAME) public class IdentifierRestRepository extends DSpaceRestRepository implements InitializingBean { @Autowired private DiscoverableEndpointsService discoverableEndpointsService; @@ -87,10 +86,6 @@ public class IdentifierRestRepository extends DSpaceRestRepository findByItem(@Parameter(value = "uuid", required = tru results.add(new IdentifierRest(handleUrl, "handle", null)); } } catch (SQLException | IdentifierException e) { - throw new LinkNotFoundException(IdentifierRestRepository.CATEGORY, IdentifierRest.NAME, uuid); + throw new LinkNotFoundException(IdentifierRest.CATEGORY, IdentifierRest.NAME, uuid); } // Return list of identifiers for this DSpaceObject return new PageImpl<>(results, pageable, results.size()); @@ -281,8 +276,7 @@ public void getDSObyIdentifier(HttpServletRequest request, if (dso != null) { // Convert and respond with a redirect to the object itself DSpaceObjectRest dsor = converter.toRest(dso, utils.obtainProjection()); - URI link = linkTo(dsor.getController(), dsor.getCategory(), - English.plural(dsor.getType())) + URI link = linkTo(dsor.getController(), dsor.getCategory(), dsor.getTypePlural()) .slash(dsor.getId()).toUri(); response.setStatus(HttpServletResponse.SC_FOUND); response.sendRedirect(link.toString()); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemAccessStatusLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemAccessStatusLinkRepository.java index b2660f51e09c..975171fba3c3 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemAccessStatusLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemAccessStatusLinkRepository.java @@ -10,9 +10,9 @@ import java.sql.SQLException; import java.util.UUID; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; +import jakarta.annotation.Nullable; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.access.status.service.AccessStatusService; import org.dspace.app.rest.model.AccessStatusRest; import org.dspace.app.rest.model.ItemRest; @@ -29,7 +29,7 @@ /** * Link repository for calculating the access status of an Item */ -@Component(ItemRest.CATEGORY + "." + ItemRest.NAME + "." + ItemRest.ACCESS_STATUS) +@Component(ItemRest.CATEGORY + "." + ItemRest.PLURAL_NAME + "." + ItemRest.ACCESS_STATUS) public class ItemAccessStatusLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemBundleLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemBundleLinkRepository.java index d7525c881a6e..a6089c2331e9 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemBundleLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemBundleLinkRepository.java @@ -9,9 +9,9 @@ import java.sql.SQLException; import java.util.UUID; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; +import jakarta.annotation.Nullable; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.model.BundleRest; import org.dspace.app.rest.model.ItemRest; import org.dspace.app.rest.projection.Projection; @@ -28,7 +28,7 @@ /** * Link repository for "bundles" subresource of an individual item. */ -@Component(ItemRest.CATEGORY + "." + ItemRest.NAME + "." + ItemRest.BUNDLES) +@Component(ItemRest.CATEGORY + "." + ItemRest.PLURAL_NAME + "." + ItemRest.BUNDLES) public class ItemBundleLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemFilterRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemFilterRestRepository.java new file mode 100644 index 000000000000..cb8dc6112af1 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemFilterRestRepository.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.rest.repository; + +import org.dspace.app.ldn.ItemFilter; +import org.dspace.app.rest.model.ItemFilterRest; +import org.dspace.content.ItemFilterService; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + +/** + * This is the repository responsible to manage ItemFilter Rest object + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + */ + +@Component(ItemFilterRest.CATEGORY + "." + ItemFilterRest.PLURAL_NAME) +public class ItemFilterRestRepository extends DSpaceRestRepository { + + @Autowired + private ItemFilterService itemFilterService; + + @Override + @PreAuthorize("hasAuthority('ADMIN')") + public ItemFilterRest findOne(Context context, String id) { + ItemFilter itemFilter = itemFilterService.findOne(id); + + if (itemFilter == null) { + throw new ResourceNotFoundException( + "No such logical item filter: " + id); + } + + return converter.toRest(itemFilter, utils.obtainProjection()); + } + + @Override + @PreAuthorize("hasAuthority('ADMIN')") + public Page findAll(Context context, Pageable pageable) { + return converter.toRestPage(itemFilterService.findAll(), + pageable, utils.obtainProjection()); + } + + @Override + public Class getDomainClass() { + return ItemFilterRest.class; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemIdentifierLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemIdentifierLinkRepository.java index 0714b7329bff..e2ad964bf43d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemIdentifierLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemIdentifierLinkRepository.java @@ -12,9 +12,9 @@ import java.util.ArrayList; 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.dspace.app.rest.model.IdentifierRest; import org.dspace.app.rest.model.IdentifiersRest; import org.dspace.app.rest.model.ItemRest; @@ -39,7 +39,7 @@ /** * Link repository for the identifier of an Item */ -@Component(ItemRest.CATEGORY + "." + ItemRest.NAME + "." + ItemRest.IDENTIFIERS) +@Component(ItemRest.CATEGORY + "." + ItemRest.PLURAL_NAME + "." + ItemRest.IDENTIFIERS) public class ItemIdentifierLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { @Autowired ItemService itemService; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemMappedCollectionLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemMappedCollectionLinkRepository.java index c632cd9d613a..41e438427cae 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemMappedCollectionLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemMappedCollectionLinkRepository.java @@ -11,9 +11,9 @@ import java.util.List; import java.util.UUID; import java.util.stream.Collectors; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; +import jakarta.annotation.Nullable; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.model.CollectionRest; import org.dspace.app.rest.model.ItemRest; import org.dspace.app.rest.projection.Projection; @@ -31,7 +31,7 @@ /** * Link repository for "mappedCollections" subresource of an individual item. */ -@Component(ItemRest.CATEGORY + "." + ItemRest.NAME + "." + ItemRest.MAPPED_COLLECTIONS) +@Component(ItemRest.CATEGORY + "." + ItemRest.PLURAL_NAME + "." + ItemRest.MAPPED_COLLECTIONS) public class ItemMappedCollectionLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemOwningCollectionLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemOwningCollectionLinkRepository.java index a7ceed900d7e..b41ace1dd307 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemOwningCollectionLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemOwningCollectionLinkRepository.java @@ -9,9 +9,9 @@ import java.sql.SQLException; import java.util.UUID; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; +import jakarta.annotation.Nullable; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.model.CollectionRest; import org.dspace.app.rest.model.ItemRest; import org.dspace.app.rest.projection.Projection; @@ -27,7 +27,7 @@ /** * Link repository for "owningCollection" subresource of an individual item. */ -@Component(ItemRest.CATEGORY + "." + ItemRest.NAME + "." + ItemRest.OWNING_COLLECTION) +@Component(ItemRest.CATEGORY + "." + ItemRest.PLURAL_NAME + "." + ItemRest.OWNING_COLLECTION) public class ItemOwningCollectionLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemRelationshipLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemRelationshipLinkRepository.java index 4a282ee46693..5ae8179d0bd0 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemRelationshipLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemRelationshipLinkRepository.java @@ -10,9 +10,9 @@ import java.sql.SQLException; 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.dspace.app.rest.model.ItemRest; import org.dspace.app.rest.model.RelationshipRest; import org.dspace.app.rest.projection.Projection; @@ -31,7 +31,7 @@ /** * Link repository for "relationships" subresource of an individual item. */ -@Component(ItemRest.CATEGORY + "." + ItemRest.NAME + "." + ItemRest.RELATIONSHIPS) +@Component(ItemRest.CATEGORY + "." + ItemRest.PLURAL_NAME + "." + ItemRest.RELATIONSHIPS) public class ItemRelationshipLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemRestRepository.java index 16ce8629d17d..1a22a5f7477f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemRestRepository.java @@ -14,11 +14,11 @@ import java.util.List; import java.util.Objects; import java.util.UUID; -import javax.servlet.ServletInputStream; -import javax.servlet.http.HttpServletRequest; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.ServletInputStream; +import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -59,7 +59,7 @@ * @author Andrea Bollini (andrea.bollini at 4science.it) */ -@Component(ItemRest.CATEGORY + "." + ItemRest.NAME) +@Component(ItemRest.CATEGORY + "." + ItemRest.PLURAL_NAME) public class ItemRestRepository extends DSpaceObjectRestRepository { private static final Logger log = LogManager.getLogger(ItemRestRepository.class); @@ -365,4 +365,5 @@ protected ItemRest createAndReturn(Context context, List stringList) Item item = uriListHandlerService.handle(context, req, stringList, Item.class); return converter.toRest(item, utils.obtainProjection()); } + } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemSubmitterLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemSubmitterLinkRepository.java new file mode 100644 index 000000000000..a345f0abe788 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemSubmitterLinkRepository.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.rest.repository; + +import java.sql.SQLException; +import java.util.UUID; +import javax.annotation.Nullable; + +import jakarta.servlet.http.HttpServletRequest; +import org.dspace.app.rest.model.EPersonRest; +import org.dspace.app.rest.model.ItemRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.content.Item; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Pageable; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + +/** + * Link repository for "submitter" subresource of an item. + */ +@Component(ItemRest.CATEGORY + "." + ItemRest.PLURAL_NAME + "." + ItemRest.SUBMITTER) +public class ItemSubmitterLinkRepository extends AbstractDSpaceRestRepository + implements LinkRestRepository { + + @Autowired + ItemService itemService; + + /** + * Retrieve the submitter for an item. + * + * @param request - The current request + * @param id - The item ID for which to retrieve the submitter + * @param optionalPageable - optional pageable object + * @param projection - the current projection + * @return the submitter for the item + */ + @PreAuthorize("hasPermission(#id, 'ITEM', 'READ')") + public EPersonRest getItemSubmitter(@Nullable HttpServletRequest request, UUID id, + @Nullable Pageable optionalPageable, Projection projection) { + try { + Context context = obtainContext(); + Item item = itemService.find(context, id); + if (item == null) { + throw new ResourceNotFoundException("No such item: " + id); + } + + return converter.toRest(item.getSubmitter(), projection); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } +} \ No newline at end of file diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemTemplateItemOfLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemTemplateItemOfLinkRepository.java index 63f25bc668b3..45ac2420fa7f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemTemplateItemOfLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemTemplateItemOfLinkRepository.java @@ -9,9 +9,9 @@ import java.sql.SQLException; import java.util.UUID; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; +import jakarta.annotation.Nullable; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.model.CollectionRest; import org.dspace.app.rest.model.ItemRest; import org.dspace.app.rest.projection.Projection; @@ -27,7 +27,7 @@ /** * Link repository for "templateItemOf" subresource of an individual item. */ -@Component(ItemRest.CATEGORY + "." + ItemRest.NAME + "." + ItemRest.TEMPLATE_ITEM_OF) +@Component(ItemRest.CATEGORY + "." + ItemRest.PLURAL_NAME + "." + ItemRest.TEMPLATE_ITEM_OF) public class ItemTemplateItemOfLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemThumbnailLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemThumbnailLinkRepository.java index 7391d410b6f6..fd56151d7edf 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemThumbnailLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemThumbnailLinkRepository.java @@ -10,9 +10,9 @@ import java.sql.SQLException; import java.util.UUID; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; +import jakarta.annotation.Nullable; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.model.BitstreamRest; import org.dspace.app.rest.model.ItemRest; import org.dspace.app.rest.projection.Projection; @@ -29,7 +29,7 @@ /** * Link repository for the thumbnail Bitstream of an Item */ -@Component(ItemRest.CATEGORY + "." + ItemRest.NAME + "." + ItemRest.THUMBNAIL) +@Component(ItemRest.CATEGORY + "." + ItemRest.PLURAL_NAME + "." + ItemRest.THUMBNAIL) public class ItemThumbnailLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { @Autowired ItemService itemService; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemVersionLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemVersionLinkRepository.java index 95bbddc66587..abf107b82241 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemVersionLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemVersionLinkRepository.java @@ -9,9 +9,9 @@ import java.sql.SQLException; import java.util.UUID; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; +import jakarta.annotation.Nullable; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.model.ItemRest; import org.dspace.app.rest.model.VersionRest; import org.dspace.app.rest.projection.Projection; @@ -29,7 +29,7 @@ /** * This is the Repository that will take care of fetching the Version for a given Item */ -@Component(ItemRest.CATEGORY + "." + ItemRest.NAME + "." + ItemRest.VERSION) +@Component(ItemRest.CATEGORY + "." + ItemRest.PLURAL_NAME + "." + ItemRest.VERSION) public class ItemVersionLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/LDNMessageRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/LDNMessageRestRepository.java new file mode 100644 index 000000000000..d28080ab36cd --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/LDNMessageRestRepository.java @@ -0,0 +1,87 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.rest.repository; + +import java.sql.SQLException; + +import jakarta.servlet.http.HttpServletRequest; +import org.dspace.app.ldn.LDNMessageEntity; +import org.dspace.app.ldn.service.LDNMessageService; +import org.dspace.app.rest.exception.MethodNotAllowedException; +import org.dspace.app.rest.model.LDNMessageEntityRest; +import org.dspace.app.rest.model.patch.Patch; +import org.dspace.authorize.AuthorizeException; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + +/** + * This is the repository responsible to manage LDNMessageEntry Rest object + * + * @author Stefano Maffei(stefano.maffei at 4science.com) + */ + +@Component(LDNMessageEntityRest.CATEGORY + "." + LDNMessageEntityRest.NAME_PLURALS) +public class LDNMessageRestRepository extends DSpaceRestRepository { + + @Autowired + private LDNMessageService ldnMessageService; + + @Override + @PreAuthorize("hasAuthority('ADMIN')") + public LDNMessageEntityRest findOne(Context context, String id) { + try { + LDNMessageEntity ldnMessageEntity = ldnMessageService.find(context, id); + if (ldnMessageEntity == null) { + throw new ResourceNotFoundException("The LDNMessageEntity for ID: " + id + " could not be found"); + } + return converter.toRest(ldnMessageEntity, utils.obtainProjection()); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + + @Override + @PreAuthorize("hasAuthority('ADMIN')") + public Page findAll(Context context, Pageable pageable) { + try { + return converter.toRestPage(ldnMessageService.findAll(context), pageable, utils.obtainProjection()); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + + @Override + @PreAuthorize("hasAuthority('ADMIN')") + protected LDNMessageEntityRest createAndReturn(Context context) throws AuthorizeException, SQLException { + throw new MethodNotAllowedException("Creation of LDN Message is not supported via Endpoint"); + } + + @Override + @PreAuthorize("hasAuthority('ADMIN')") + protected void patch(Context context, HttpServletRequest request, String apiCategory, String model, String id, + Patch patch) throws AuthorizeException, SQLException { + throw new MethodNotAllowedException("Patch of LDN Message is not supported via Endpoint"); + } + + @Override + @PreAuthorize("hasAuthority('ADMIN')") + protected void delete(Context context, String id) throws AuthorizeException { + throw new MethodNotAllowedException("Deletion of LDN Message is not supported via Endpoint"); + } + + @Override + public Class getDomainClass() { + return LDNMessageEntityRest.class; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/MetadataFieldRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/MetadataFieldRestRepository.java index 5152f11902f5..0d0bc1729a26 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/MetadataFieldRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/MetadataFieldRestRepository.java @@ -17,11 +17,11 @@ import java.util.Iterator; import java.util.List; import java.util.Objects; -import javax.servlet.http.HttpServletRequest; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.dspace.app.rest.Parameter; @@ -55,7 +55,7 @@ * * @author Andrea Bollini (andrea.bollini at 4science.it) */ -@Component(MetadataFieldRest.CATEGORY + "." + MetadataFieldRest.NAME) +@Component(MetadataFieldRest.CATEGORY + "." + MetadataFieldRest.PLURAL_NAME) public class MetadataFieldRestRepository extends DSpaceRestRepository { /** * log4j logger diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/MetadataSchemaRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/MetadataSchemaRestRepository.java index d9c148b71c0d..d7338dc31419 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/MetadataSchemaRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/MetadataSchemaRestRepository.java @@ -13,11 +13,11 @@ import java.sql.SQLException; import java.util.List; import java.util.Objects; -import javax.servlet.http.HttpServletRequest; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.exception.DSpaceBadRequestException; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.MetadataSchemaRest; @@ -38,7 +38,7 @@ * * @author Andrea Bollini (andrea.bollini at 4science.it) */ -@Component(MetadataSchemaRest.CATEGORY + "." + MetadataSchemaRest.NAME) +@Component(MetadataSchemaRest.CATEGORY + "." + MetadataSchemaRest.PLURAL_NAME) public class MetadataSchemaRestRepository extends DSpaceRestRepository { @Autowired diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NotifyServiceRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NotifyServiceRestRepository.java new file mode 100644 index 000000000000..bff0abc961d9 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NotifyServiceRestRepository.java @@ -0,0 +1,211 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.rest.repository; + +import static java.lang.String.format; + +import java.io.IOException; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.ServletInputStream; +import jakarta.servlet.http.HttpServletRequest; +import org.dspace.app.ldn.NotifyServiceEntity; +import org.dspace.app.ldn.NotifyServiceInboundPattern; +import org.dspace.app.ldn.service.NotifyService; +import org.dspace.app.ldn.service.NotifyServiceInboundPatternService; +import org.dspace.app.rest.Parameter; +import org.dspace.app.rest.SearchRestMethod; +import org.dspace.app.rest.exception.UnprocessableEntityException; +import org.dspace.app.rest.model.NotifyServiceInboundPatternRest; +import org.dspace.app.rest.model.NotifyServiceRest; +import org.dspace.app.rest.model.patch.Patch; +import org.dspace.app.rest.repository.patch.ResourcePatch; +import org.dspace.authorize.AuthorizeException; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + + +/** + * This is the repository responsible to manage NotifyService Rest object + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + */ + +@Component(NotifyServiceRest.CATEGORY + "." + NotifyServiceRest.PLURAL_NAME) +public class NotifyServiceRestRepository extends DSpaceRestRepository { + + @Autowired + private NotifyService notifyService; + + @Autowired + private NotifyServiceInboundPatternService inboundPatternService; + + @Autowired + ResourcePatch resourcePatch; + + @Override + @PreAuthorize("hasAuthority('AUTHENTICATED')") + public NotifyServiceRest findOne(Context context, Integer id) { + try { + NotifyServiceEntity notifyServiceEntity = notifyService.find(context, id); + if (notifyServiceEntity == null) { + throw new ResourceNotFoundException("The notifyService for ID: " + id + " could not be found"); + } + return converter.toRest(notifyServiceEntity, utils.obtainProjection()); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + + @Override + @PreAuthorize("hasAuthority('AUTHENTICATED')") + public Page findAll(Context context, Pageable pageable) { + try { + return converter.toRestPage(notifyService.findAll(context), pageable, utils.obtainProjection()); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + + @Override + @PreAuthorize("hasAuthority('ADMIN')") + protected NotifyServiceRest createAndReturn(Context context) throws AuthorizeException, SQLException { + HttpServletRequest req = getRequestService().getCurrentRequest().getHttpServletRequest(); + ObjectMapper mapper = new ObjectMapper(); + NotifyServiceRest notifyServiceRest; + try { + ServletInputStream input = req.getInputStream(); + notifyServiceRest = mapper.readValue(input, NotifyServiceRest.class); + } catch (IOException e1) { + throw new UnprocessableEntityException("Error parsing request body", e1); + } + + if (notifyServiceRest.getScore() != null) { + if (notifyServiceRest.getScore().compareTo(java.math.BigDecimal.ZERO) == -1 || + notifyServiceRest.getScore().compareTo(java.math.BigDecimal.ONE) == 1) { + throw new UnprocessableEntityException(format("Score out of range [0, 1] %s", + notifyServiceRest.getScore().setScale(4).toPlainString())); + } + } + + if (notifyService.findByLdnUrl(context,notifyServiceRest.getLdnUrl()) != null) { + throw new UnprocessableEntityException(format("LDN url already in use %s", + notifyServiceRest.getLdnUrl())); + } + + NotifyServiceEntity notifyServiceEntity = notifyService.create(context, notifyServiceRest.getName()); + notifyServiceEntity.setDescription(notifyServiceRest.getDescription()); + notifyServiceEntity.setUrl(notifyServiceRest.getUrl()); + notifyServiceEntity.setLdnUrl(notifyServiceRest.getLdnUrl()); + notifyServiceEntity.setEnabled(notifyServiceRest.isEnabled()); + notifyServiceEntity.setLowerIp(notifyServiceRest.getLowerIp()); + notifyServiceEntity.setUpperIp(notifyServiceRest.getUpperIp()); + + if (notifyServiceRest.getNotifyServiceInboundPatterns() != null) { + appendNotifyServiceInboundPatterns(context, notifyServiceEntity, + notifyServiceRest.getNotifyServiceInboundPatterns()); + } + + notifyServiceEntity.setScore(notifyServiceRest.getScore()); + + notifyService.update(context, notifyServiceEntity); + + return converter.toRest(notifyServiceEntity, utils.obtainProjection()); + } + + private void appendNotifyServiceInboundPatterns(Context context, NotifyServiceEntity notifyServiceEntity, + List inboundPatternRests) throws SQLException { + + List inboundPatterns = new ArrayList<>(); + + for (NotifyServiceInboundPatternRest inboundPatternRest : inboundPatternRests) { + NotifyServiceInboundPattern inboundPattern = inboundPatternService.create(context, notifyServiceEntity); + inboundPattern.setPattern(inboundPatternRest.getPattern()); + inboundPattern.setConstraint(inboundPatternRest.getConstraint()); + inboundPattern.setAutomatic(inboundPatternRest.isAutomatic()); + + inboundPatterns.add(inboundPattern); + } + + notifyServiceEntity.setInboundPatterns(inboundPatterns); + } + + @Override + @PreAuthorize("hasAuthority('ADMIN')") + protected void patch(Context context, HttpServletRequest request, String apiCategory, String model, Integer id, + Patch patch) throws AuthorizeException, SQLException { + NotifyServiceEntity notifyServiceEntity = notifyService.find(context, id); + if (notifyServiceEntity == null) { + throw new ResourceNotFoundException( + NotifyServiceRest.CATEGORY + "." + NotifyServiceRest.NAME + " with id: " + id + " not found"); + } + resourcePatch.patch(context, notifyServiceEntity, patch.getOperations()); + notifyService.update(context, notifyServiceEntity); + } + + @Override + @PreAuthorize("hasAuthority('ADMIN')") + protected void delete(Context context, Integer id) throws AuthorizeException { + try { + NotifyServiceEntity notifyServiceEntity = notifyService.find(context, id); + if (notifyServiceEntity == null) { + throw new ResourceNotFoundException(NotifyServiceRest.CATEGORY + "." + + NotifyServiceRest.NAME + " with id: " + id + " not found"); + } + notifyService.delete(context, notifyServiceEntity); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + + @SearchRestMethod(name = "byLdnUrl") + @PreAuthorize("hasAuthority('AUTHENTICATED')") + public NotifyServiceRest findByLdnUrl(@Parameter(value = "ldnUrl", required = true) String ldnUrl) { + try { + NotifyServiceEntity notifyServiceEntity = notifyService.findByLdnUrl(obtainContext(), ldnUrl); + if (notifyServiceEntity == null) { + return null; + } + return converter.toRest(notifyServiceEntity, utils.obtainProjection()); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + + @SearchRestMethod(name = "byInboundPattern") + @PreAuthorize("hasAuthority('AUTHENTICATED')") + public Page findManualServicesByInboundPattern( + @Parameter(value = "pattern", required = true) String pattern, + Pageable pageable) { + try { + List notifyServiceEntities = + notifyService.findManualServicesByInboundPattern(obtainContext(), pattern) + .stream() + .filter(NotifyServiceEntity::isEnabled) + .collect(Collectors.toList()); + + return converter.toRestPage(notifyServiceEntities, pageable, utils.obtainProjection()); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + + @Override + public Class getDomainClass() { + return NotifyServiceRest.class; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/OrcidHistoryRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/OrcidHistoryRestRepository.java index 0c44baaf0dbd..77d7ba23611e 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/OrcidHistoryRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/OrcidHistoryRestRepository.java @@ -9,8 +9,8 @@ import java.sql.SQLException; import java.util.List; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException; import org.dspace.app.rest.model.OrcidHistoryRest; import org.dspace.app.rest.repository.handler.service.UriListHandlerService; @@ -32,7 +32,7 @@ * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.it) * */ -@Component(OrcidHistoryRest.CATEGORY + "." + OrcidHistoryRest.NAME) +@Component(OrcidHistoryRest.CATEGORY + "." + OrcidHistoryRest.PLURAL_NAME) @ConditionalOnProperty("orcid.synchronization-enabled") public class OrcidHistoryRestRepository extends DSpaceRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/OrcidQueueRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/OrcidQueueRestRepository.java index 0a1614cded94..9334103fb60c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/OrcidQueueRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/OrcidQueueRestRepository.java @@ -35,7 +35,7 @@ * @author Luca Giamminonni (luca.giamminonni at 4science.it) * */ -@Component(OrcidQueueRest.CATEGORY + "." + OrcidQueueRest.NAME) +@Component(OrcidQueueRest.CATEGORY + "." + OrcidQueueRest.PLURAL_NAME) @ConditionalOnProperty("orcid.synchronization-enabled") public class OrcidQueueRestRepository extends DSpaceRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/PoolTaskRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/PoolTaskRestRepository.java index da5253608e65..18995b4a026c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/PoolTaskRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/PoolTaskRestRepository.java @@ -48,7 +48,7 @@ * @author Andrea Bollini (andrea.bollini at 4science.it) */ -@Component(PoolTaskRest.CATEGORY + "." + PoolTaskRest.NAME) +@Component(PoolTaskRest.CATEGORY + "." + PoolTaskRest.PLURAL_NAME) public class PoolTaskRestRepository extends DSpaceRestRepository implements InitializingBean { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/PoolTaskStepLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/PoolTaskStepLinkRepository.java index 6e7f4f84ace3..9af6a5730918 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/PoolTaskStepLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/PoolTaskStepLinkRepository.java @@ -8,9 +8,9 @@ package org.dspace.app.rest.repository; import java.sql.SQLException; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; +import jakarta.annotation.Nullable; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.model.PoolTaskRest; import org.dspace.app.rest.model.WorkflowStepRest; import org.dspace.app.rest.projection.Projection; @@ -26,7 +26,7 @@ /** * Link repositoy for the Steps subresources of an individual PoolTask */ -@Component(PoolTaskRest.CATEGORY + "." + PoolTaskRest.NAME + "." + PoolTaskRest.STEP) +@Component(PoolTaskRest.CATEGORY + "." + PoolTaskRest.PLURAL_NAME + "." + PoolTaskRest.STEP) public class PoolTaskStepLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { @Autowired diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ProcessFileTypesLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ProcessFileTypesLinkRepository.java index 16c8115b29f8..cb83379d8b08 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ProcessFileTypesLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ProcessFileTypesLinkRepository.java @@ -9,9 +9,9 @@ import java.sql.SQLException; import java.util.List; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; +import jakarta.annotation.Nullable; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.model.ProcessFileTypesRest; import org.dspace.app.rest.model.ProcessRest; import org.dspace.app.rest.projection.Projection; @@ -30,7 +30,7 @@ * It'll retrieve all the bitstreams for the given Process and return a {@link ProcessFileTypesRest} object that holds * a list of Strings where each String represents a unique fileType of the Bitstreams for that Process */ -@Component(ProcessRest.CATEGORY + "." + ProcessRest.NAME + "." + ProcessRest.FILE_TYPES) +@Component(ProcessRest.CATEGORY + "." + ProcessRest.PLURAL_NAME + "." + ProcessRest.FILE_TYPES) public class ProcessFileTypesLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { @Autowired diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ProcessFilesLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ProcessFilesLinkRepository.java index 5d8251cf19ba..d981a9c7976b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ProcessFilesLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ProcessFilesLinkRepository.java @@ -9,9 +9,9 @@ import java.sql.SQLException; import java.util.List; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; +import jakarta.annotation.Nullable; +import jakarta.servlet.http.HttpServletRequest; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.app.rest.model.BitstreamRest; @@ -29,7 +29,7 @@ * {@link org.dspace.content.Bitstream} objects for the Process endpoints * */ -@Component(ProcessRest.CATEGORY + "." + ProcessRest.NAME + "." + ProcessRest.FILES) +@Component(ProcessRest.CATEGORY + "." + ProcessRest.PLURAL_NAME + "." + ProcessRest.FILES) public class ProcessFilesLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { private static final Logger log = LogManager.getLogger(); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ProcessOutputLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ProcessOutputLinkRepository.java index f5b3edced2db..016450c92569 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ProcessOutputLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ProcessOutputLinkRepository.java @@ -8,9 +8,9 @@ package org.dspace.app.rest.repository; import java.sql.SQLException; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; +import jakarta.annotation.Nullable; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.model.BitstreamRest; import org.dspace.app.rest.model.ProcessRest; import org.dspace.app.rest.projection.Projection; @@ -29,7 +29,7 @@ * This linkRepository will deal with calls to the /output endpoint of a given Process. * It'll retrieve the output for the given Process and return this as a {@link BitstreamRest} object */ -@Component(ProcessRest.CATEGORY + "." + ProcessRest.NAME + "." + ProcessRest.OUTPUT) +@Component(ProcessRest.CATEGORY + "." + ProcessRest.PLURAL_NAME + "." + ProcessRest.OUTPUT) public class ProcessOutputLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { @Autowired @@ -68,4 +68,4 @@ public BitstreamRest getOutputFromProcess(@Nullable HttpServletRequest request, } return converter.toRest(bitstream, projection); } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ProcessRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ProcessRestRepository.java index 5f3b093b2007..24152ed5697a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ProcessRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ProcessRestRepository.java @@ -47,7 +47,7 @@ /** * The repository for the Process workload */ -@Component(ProcessRest.CATEGORY + "." + ProcessRest.NAME) +@Component(ProcessRest.CATEGORY + "." + ProcessRest.PLURAL_NAME) public class ProcessRestRepository extends DSpaceRestRepository { private static final Logger log = LogManager.getLogger(); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventRelatedLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventRelatedLinkRepository.java index c711d2ec376a..f4e84a233b2e 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventRelatedLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventRelatedLinkRepository.java @@ -9,9 +9,9 @@ import java.sql.SQLException; import java.util.UUID; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; +import jakarta.annotation.Nullable; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.model.ItemRest; import org.dspace.app.rest.model.QAEventRest; import org.dspace.app.rest.projection.Projection; @@ -32,7 +32,7 @@ * @author Andrea Bollini (andrea.bollini at 4science.it) * */ -@Component(QAEventRest.CATEGORY + "." + QAEventRest.NAME + "." + QAEventRest.RELATED) +@Component(QAEventRest.CATEGORY + "." + QAEventRest.PLURAL_NAME + "." + QAEventRest.RELATED) public class QAEventRelatedLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { @Autowired @@ -51,7 +51,7 @@ public class QAEventRelatedLinkRepository extends AbstractDSpaceRestRepository i * @param projection the projection object * @return the item rest representation of the secondary item related to qa event */ - @PreAuthorize("hasAuthority('ADMIN')") + @PreAuthorize("hasPermission(#id, 'QUALITYASSURANCEEVENT', 'READ')") public ItemRest getRelated(@Nullable HttpServletRequest request, String id, @Nullable Pageable pageable, Projection projection) { Context context = obtainContext(); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventRestRepository.java index dc0654ee494a..e6c25dfd9f2f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventRestRepository.java @@ -7,13 +7,20 @@ */ package org.dspace.app.rest.repository; +import java.io.IOException; import java.sql.SQLException; import java.util.List; -import javax.servlet.http.HttpServletRequest; +import java.util.Objects; +import java.util.UUID; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.http.HttpServletRequest; +import org.apache.commons.lang.StringUtils; import org.dspace.app.rest.Parameter; import org.dspace.app.rest.SearchRestMethod; import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException; +import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.QAEventRest; import org.dspace.app.rest.model.patch.Patch; import org.dspace.app.rest.repository.patch.ResourcePatch; @@ -22,14 +29,16 @@ import org.dspace.content.QAEvent; import org.dspace.content.service.ItemService; import org.dspace.core.Context; +import org.dspace.correctiontype.CorrectionType; +import org.dspace.correctiontype.service.CorrectionTypeService; import org.dspace.eperson.EPerson; import org.dspace.qaevent.dao.QAEventsDAO; import org.dspace.qaevent.service.QAEventService; +import org.dspace.qaevent.service.dto.CorrectionTypeMessageDTO; import org.dspace.util.UUIDUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort.Direction; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Component; @@ -39,7 +48,7 @@ * @author Andrea Bollini (andrea.bollini at 4science.it) * */ -@Component(QAEventRest.CATEGORY + "." + QAEventRest.NAME) +@Component(QAEventRest.CATEGORY + "." + QAEventRest.PLURAL_NAME) public class QAEventRestRepository extends DSpaceRestRepository { final static String ORDER_FIELD = "trust"; @@ -56,8 +65,16 @@ public class QAEventRestRepository extends DSpaceRestRepository resourcePatch; + @Autowired + private CorrectionTypeService correctionTypeService; + + @Override + public Page findAll(Context context, Pageable pageable) { + throw new RepositoryMethodNotImplementedException(QAEventRest.NAME, "findAll"); + } + @Override - @PreAuthorize("hasAuthority('ADMIN')") + @PreAuthorize("hasPermission(#id, 'QUALITYASSURANCEEVENT', 'READ')") public QAEventRest findOne(Context context, String id) { QAEvent qaEvent = qaEventService.findEventByEventId(id); if (qaEvent == null) { @@ -73,18 +90,20 @@ public QAEventRest findOne(Context context, String id) { } @SearchRestMethod(name = "findByTopic") - @PreAuthorize("hasAuthority('ADMIN')") - public Page findByTopic(Context context, @Parameter(value = "topic", required = true) String topic, + @PreAuthorize("hasAuthority('AUTHENTICATED')") + public Page findByTopic(@Parameter(value = "topic", required = true) String topic, Pageable pageable) { - List qaEvents = null; - long count = 0L; - boolean ascending = false; - if (pageable.getSort() != null && pageable.getSort().getOrderFor(ORDER_FIELD) != null) { - ascending = pageable.getSort().getOrderFor(ORDER_FIELD).getDirection() == Direction.ASC; + Context context = obtainContext(); + String[] topicIdSplitted = topic.split(":", 3); + if (topicIdSplitted.length < 2) { + return null; } - qaEvents = qaEventService.findEventsByTopicAndPage(topic, - pageable.getOffset(), pageable.getPageSize(), ORDER_FIELD, ascending); - count = qaEventService.countEventsByTopic(topic); + String sourceName = topicIdSplitted[0]; + String topicName = topicIdSplitted[1].replaceAll("!", "/"); + UUID target = topicIdSplitted.length == 3 ? UUID.fromString(topicIdSplitted[2]) : null; + List qaEvents = qaEventService.findEventsByTopicAndTarget(context, sourceName, topicName, target, + pageable.getOffset(), pageable.getPageSize()); + long count = qaEventService.countEventsByTopicAndTarget(context, sourceName, topicName, target); if (qaEvents == null) { return null; } @@ -92,21 +111,16 @@ public Page findByTopic(Context context, @Parameter(value = "topic" } @Override - @PreAuthorize("hasAuthority('ADMIN')") - protected void delete(Context context, String eventId) throws AuthorizeException { - Item item = findTargetItem(context, eventId); + @PreAuthorize("hasPermission(#id, 'QUALITYASSURANCEEVENT', 'DELETE')") + protected void delete(Context context, String id) throws AuthorizeException { + Item item = findTargetItem(context, id); EPerson eperson = context.getCurrentUser(); - qaEventService.deleteEventByEventId(eventId); - qaEventDao.storeEvent(context, eventId, eperson, item); + qaEventService.deleteEventByEventId(id); + qaEventDao.storeEvent(context, id, eperson, item); } @Override - public Page findAll(Context context, Pageable pageable) { - throw new RepositoryMethodNotImplementedException(QAEventRest.NAME, "findAll"); - } - - @Override - @PreAuthorize("hasAuthority('ADMIN')") + @PreAuthorize("hasPermission(#id, 'QUALITYASSURANCEEVENT', 'WRITE')") protected void patch(Context context, HttpServletRequest request, String apiCategory, String model, String id, Patch patch) throws SQLException, AuthorizeException { QAEvent qaEvent = qaEventService.findEventByEventId(id); @@ -126,6 +140,64 @@ private Item findTargetItem(Context context, String eventId) { } } + @Override + @PreAuthorize("hasAuthority('AUTHENTICATED')") + protected QAEventRest createAndReturn(Context context) throws SQLException, AuthorizeException { + ServletRequest request = getRequestService().getCurrentRequest().getServletRequest(); + + String itemUUID = request.getParameter("target"); + String relatedItemUUID = request.getParameter("related"); + String correctionTypeStr = request.getParameter("correctionType"); + + + if (StringUtils.isBlank(correctionTypeStr) || StringUtils.isBlank(itemUUID)) { + throw new UnprocessableEntityException("The target item and correctionType must be provided!"); + } + + Item targetItem = null; + Item relatedItem = null; + try { + targetItem = itemService.find(context, UUID.fromString(itemUUID)); + relatedItem = StringUtils.isNotBlank(relatedItemUUID) ? + itemService.find(context, UUID.fromString(relatedItemUUID)) : null; + } catch (Exception e) { + throw new UnprocessableEntityException(e.getMessage(), e); + } + + if (Objects.isNull(targetItem)) { + throw new UnprocessableEntityException("The target item UUID is not valid!"); + } + + CorrectionType correctionType = correctionTypeService.findOne(correctionTypeStr); + if (Objects.isNull(correctionType)) { + throw new UnprocessableEntityException("The given correction type in the request is not valid!"); + } + + if (correctionType.isRequiredRelatedItem() && Objects.isNull(relatedItem)) { + throw new UnprocessableEntityException("The given correction type in the request is not valid!"); + } + + if (!correctionType.isAllowed(context, targetItem)) { + throw new UnprocessableEntityException("This item cannot be processed by this correction type!"); + } + + ObjectMapper mapper = new ObjectMapper(); + CorrectionTypeMessageDTO reason = null; + try { + reason = mapper.readValue(request.getInputStream(), CorrectionTypeMessageDTO.class); + } catch (IOException exIO) { + throw new UnprocessableEntityException("error parsing the body " + exIO.getMessage(), exIO); + } + + QAEvent qaEvent; + if (correctionType.isRequiredRelatedItem()) { + qaEvent = correctionType.createCorrection(context, targetItem, relatedItem, reason); + } else { + qaEvent = correctionType.createCorrection(context, targetItem, reason); + } + return converter.toRest(qaEvent, utils.obtainProjection()); + } + @Override public Class getDomainClass() { return QAEventRest.class; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventTargetLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventTargetLinkRepository.java index 2df3836b9bdf..eb2ca8317b51 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventTargetLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventTargetLinkRepository.java @@ -9,9 +9,9 @@ import java.sql.SQLException; import java.util.UUID; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; +import jakarta.annotation.Nullable; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.model.ItemRest; import org.dspace.app.rest.model.QAEventRest; import org.dspace.app.rest.projection.Projection; @@ -32,7 +32,7 @@ * @author Andrea Bollini (andrea.bollini at 4science.it) * */ -@Component(QAEventRest.CATEGORY + "." + QAEventRest.NAME + "." + QAEventRest.TARGET) +@Component(QAEventRest.CATEGORY + "." + QAEventRest.PLURAL_NAME + "." + QAEventRest.TARGET) public class QAEventTargetLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { @Autowired @@ -48,9 +48,9 @@ public class QAEventTargetLinkRepository extends AbstractDSpaceRestRepository im * @param id the qa event id * @param pageable the optional pageable * @param projection the projection object - * @return the item rest representation of the qa event target + * @return the item rest representation of the qa event target */ - @PreAuthorize("hasAuthority('ADMIN')") + @PreAuthorize("hasPermission(#id, 'QUALITYASSURANCEEVENT', 'READ')") public ItemRest getTarget(@Nullable HttpServletRequest request, String id, @Nullable Pageable pageable, Projection projection) { Context context = obtainContext(); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventTopicLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventTopicLinkRepository.java index f9ed48442872..02eeaae0d759 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventTopicLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventTopicLinkRepository.java @@ -7,9 +7,8 @@ */ package org.dspace.app.rest.repository; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; - +import jakarta.annotation.Nullable; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.model.QAEventRest; import org.dspace.app.rest.model.QATopicRest; import org.dspace.app.rest.projection.Projection; @@ -29,7 +28,7 @@ * @author Andrea Bollini (andrea.bollini at 4science.it) * */ -@Component(QAEventRest.CATEGORY + "." + QAEventRest.NAME + "." + QAEventRest.TOPIC) +@Component(QAEventRest.CATEGORY + "." + QAEventRest.PLURAL_NAME + "." + QAEventRest.TOPIC) public class QAEventTopicLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { @Autowired @@ -42,9 +41,9 @@ public class QAEventTopicLinkRepository extends AbstractDSpaceRestRepository imp * @param id the qa event id * @param pageable the optional pageable * @param projection the projection object - * @return the qa topic rest representation + * @return the qa topic rest representation */ - @PreAuthorize("hasAuthority('ADMIN')") + @PreAuthorize("hasPermission(#id, 'QUALITYASSURANCEEVENT', 'READ')") public QATopicRest getTopic(@Nullable HttpServletRequest request, String id, @Nullable Pageable pageable, Projection projection) { Context context = obtainContext(); @@ -52,9 +51,12 @@ public QATopicRest getTopic(@Nullable HttpServletRequest request, String id, @Nu if (qaEvent == null) { throw new ResourceNotFoundException("No qa event with ID: " + id); } - QATopic topic = qaEventService.findTopicByTopicId(qaEvent.getTopic().replace("/", "!")); + String source = qaEvent.getSource(); + String topicName = qaEvent.getTopic(); + QATopic topic = qaEventService + .findTopicBySourceAndNameAndTarget(context, source, topicName, null); if (topic == null) { - throw new ResourceNotFoundException("No topic found with id : " + id); + throw new ResourceNotFoundException("No topic found with source: " + source + " topic: " + topicName); } return converter.toRest(topic, projection); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QASourceRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QASourceRestRepository.java index dad2310a77c0..a227f81dadb2 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QASourceRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QASourceRestRepository.java @@ -8,7 +8,10 @@ package org.dspace.app.rest.repository; import java.util.List; +import java.util.UUID; +import org.dspace.app.rest.Parameter; +import org.dspace.app.rest.SearchRestMethod; import org.dspace.app.rest.model.QASourceRest; import org.dspace.core.Context; import org.dspace.qaevent.QASource; @@ -25,16 +28,16 @@ * @author Luca Giamminonni (luca.giamminonni at 4science.it) * */ -@Component(QASourceRest.CATEGORY + "." + QASourceRest.NAME) +@Component(QASourceRest.CATEGORY + "." + QASourceRest.PLURAL_NAME) public class QASourceRestRepository extends DSpaceRestRepository { @Autowired private QAEventService qaEventService; @Override - @PreAuthorize("hasAuthority('ADMIN')") + @PreAuthorize("hasPermission(#id, 'QUALITYASSURANCESOURCE', 'READ')") public QASourceRest findOne(Context context, String id) { - QASource qaSource = qaEventService.findSource(id); + QASource qaSource = qaEventService.findSource(context, id); if (qaSource == null) { return null; } @@ -42,13 +45,26 @@ public QASourceRest findOne(Context context, String id) { } @Override - @PreAuthorize("hasAuthority('ADMIN')") + @PreAuthorize("hasAuthority('AUTHENTICATED')") public Page findAll(Context context, Pageable pageable) { - List qaSources = qaEventService.findAllSources(pageable.getOffset(), pageable.getPageSize()); - long count = qaEventService.countSources(); + List qaSources = qaEventService.findAllSources(context, pageable.getOffset(), pageable.getPageSize()); + long count = qaEventService.countSources(context); return converter.toRestPage(qaSources, pageable, count, utils.obtainProjection()); } + @SearchRestMethod(name = "byTarget") + @PreAuthorize("hasAuthority('AUTHENTICATED')") + public Page findByTarget(@Parameter(value = "target", required = true) UUID target, + Pageable pageable) { + Context context = obtainContext(); + List topics = qaEventService.findAllSourcesByTarget(context, target, + pageable.getOffset(), pageable.getPageSize()); + long count = qaEventService.countSourcesByTarget(context, target); + if (topics == null) { + return null; + } + return converter.toRestPage(topics, pageable, count, utils.obtainProjection()); + } @Override public Class getDomainClass() { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QATopicRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QATopicRestRepository.java index 97a0ee978123..ccd85e81e749 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QATopicRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QATopicRestRepository.java @@ -8,9 +8,13 @@ package org.dspace.app.rest.repository; import java.util.List; +import java.util.UUID; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.app.rest.Parameter; import org.dspace.app.rest.SearchRestMethod; +import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException; import org.dspace.app.rest.model.QATopicRest; import org.dspace.core.Context; import org.dspace.qaevent.QATopic; @@ -28,7 +32,7 @@ * @author Andrea Bollini (andrea.bollini at 4science.it) * */ -@Component(QATopicRest.CATEGORY + "." + QATopicRest.NAME) +@Component(QATopicRest.CATEGORY + "." + QATopicRest.PLURAL_NAME) public class QATopicRestRepository extends DSpaceRestRepository { final static String ORDER_FIELD = "topic"; @@ -36,44 +40,57 @@ public class QATopicRestRepository extends DSpaceRestRepository findAll(Context context, Pageable pageable) { + throw new RepositoryMethodNotImplementedException("Method not allowed!", ""); + } + @Override - @PreAuthorize("hasAuthority('ADMIN')") + @PreAuthorize("hasPermission(#id, 'QUALITYASSURANCETOPIC', 'READ')") public QATopicRest findOne(Context context, String id) { - QATopic topic = qaEventService.findTopicByTopicId(id); - if (topic == null) { + String[] topicIdSplitted = id.split(":", 3); + if (topicIdSplitted.length < 2) { return null; } - return converter.toRest(topic, utils.obtainProjection()); + String sourceName = topicIdSplitted[0]; + String topicName = topicIdSplitted[1].replaceAll("!", "/"); + UUID target = topicIdSplitted.length == 3 ? UUID.fromString(topicIdSplitted[2]) : null; + QATopic topic = qaEventService.findTopicBySourceAndNameAndTarget(context, sourceName, topicName, target); + return (topic != null) ? converter.toRest(topic, utils.obtainProjection()) : null; } - @Override - @PreAuthorize("hasAuthority('ADMIN')") - public Page findAll(Context context, Pageable pageable) { + @SearchRestMethod(name = "bySource") + @PreAuthorize("hasPermission(#source, 'QUALITYASSURANCETOPIC', 'READ')") + public Page findBySource(@Parameter(value = "source", required = true) String source, + Pageable pageable) { + Context context = obtainContext(); boolean ascending = false; if (pageable.getSort() != null && pageable.getSort().getOrderFor(ORDER_FIELD) != null) { - ascending = pageable.getSort() - .getOrderFor(ORDER_FIELD).getDirection() == Direction.ASC; + ascending = pageable.getSort().getOrderFor(ORDER_FIELD).getDirection() == Direction.ASC; } - List topics = qaEventService.findAllTopics(pageable.getOffset(), pageable.getPageSize(), - ORDER_FIELD, ascending); - long count = qaEventService.countTopics(); + List topics = qaEventService.findAllTopicsBySource(context, source, + pageable.getOffset(), pageable.getPageSize(), ORDER_FIELD, ascending); + long count = qaEventService.countTopicsBySource(context, source); if (topics == null) { return null; } return converter.toRestPage(topics, pageable, count, utils.obtainProjection()); } - @SearchRestMethod(name = "bySource") - @PreAuthorize("hasAuthority('ADMIN')") - public Page findBySource(Context context, + @SearchRestMethod(name = "byTarget") + @PreAuthorize("hasPermission(#target, 'ITEM', 'READ')") + public Page findByTarget(@Parameter(value = "target", required = true) UUID target, @Parameter(value = "source", required = true) String source, Pageable pageable) { + Context context = obtainContext(); boolean ascending = false; if (pageable.getSort() != null && pageable.getSort().getOrderFor(ORDER_FIELD) != null) { ascending = pageable.getSort().getOrderFor(ORDER_FIELD).getDirection() == Direction.ASC; } - List topics = qaEventService.findAllTopicsBySource(source, - pageable.getOffset(), pageable.getPageSize(), ORDER_FIELD, ascending); - long count = qaEventService.countTopicsBySource(source); + List topics = qaEventService.findAllTopicsBySourceAndTarget(context, source, target, + pageable.getOffset(), pageable.getPageSize(), ORDER_FIELD, ascending); + long count = qaEventService.countTopicsBySourceAndTarget(context, source, target); if (topics == null) { return null; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RegistrationRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RegistrationRestRepository.java index 3d183fd3418c..9c1c3b393bbb 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RegistrationRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RegistrationRestRepository.java @@ -11,11 +11,11 @@ import java.io.IOException; import java.sql.SQLException; -import javax.mail.MessagingException; -import javax.servlet.ServletInputStream; -import javax.servlet.http.HttpServletRequest; import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.mail.MessagingException; +import jakarta.servlet.ServletInputStream; +import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -43,12 +43,13 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.rest.webmvc.ResourceNotFoundException; import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Component; /** * This is the repository that is responsible for managing Registration Rest objects */ -@Component(RegistrationRest.CATEGORY + "." + RegistrationRest.NAME) +@Component(RegistrationRest.CATEGORY + "." + RegistrationRest.PLURAL_NAME) public class RegistrationRestRepository extends DSpaceRestRepository { private static Logger log = LogManager.getLogger(RegistrationRestRepository.class); @@ -79,6 +80,7 @@ public class RegistrationRestRepository extends DSpaceRestRepository { private static final Logger log = LogManager.getLogger(); @@ -60,6 +62,9 @@ public class RelationshipRestRepository extends DSpaceRestRepository findByLabel(@Parameter(value = "label", required = true) String label, @Parameter(value = "dso", required = false) UUID dsoId, + @Parameter(value = "relatedEntityType") String relatedEntityType, Pageable pageable) throws SQLException { Context context = obtainContext(); @@ -352,14 +359,28 @@ public Page findByLabel(@Parameter(value = "label", required = if (item == null) { throw new ResourceNotFoundException("The request DSO with id: " + dsoId + " was not found"); } + + EntityType dsoEntityType = itemService.getEntityType(context, item); + + if (dsoEntityType == null) { + throw new UnprocessableEntityException(String.format( + "The request DSO with id: %s doesn't have an entity type", dsoId)); + } + for (RelationshipType relationshipType : relationshipTypeList) { - boolean isLeft = false; - if (relationshipType.getLeftwardType().equalsIgnoreCase(label)) { - isLeft = true; + if (relatedEntityType == null || + relationshipType.getRightType().getLabel().equals(dsoEntityType.getLabel()) && + relationshipType.getLeftType().getLabel().equals(relatedEntityType) || + relationshipType.getRightType().getLabel().equals(relatedEntityType) && + relationshipType.getLeftType().getLabel().equals(dsoEntityType.getLabel())) { + boolean isLeft = relationshipType.getLeftwardType().equalsIgnoreCase(label); + total += + relationshipService.countByItemAndRelationshipType(context, item, relationshipType, isLeft); + relationships.addAll( + relationshipService.findByItemAndRelationshipType(context, item, relationshipType, + isLeft, pageable.getPageSize(), + Math.toIntExact(pageable.getOffset()))); } - total += relationshipService.countByItemAndRelationshipType(context, item, relationshipType, isLeft); - relationships.addAll(relationshipService.findByItemAndRelationshipType(context, item, relationshipType, - isLeft, pageable.getPageSize(), Math.toIntExact(pageable.getOffset()))); } } else { for (RelationshipType relationshipType : relationshipTypeList) { @@ -377,7 +398,7 @@ public Page findByLabel(@Parameter(value = "label", required = * of potentially related items we need to know which of these other items * are already in a specific relationship with the focus item and, * by exclusion which ones are not yet related. - * + * * @param typeId The relationship type id to apply as a filter to the returned relationships * @param label The name of the relation as defined from the side of the 'focusItem' * @param focusUUID The uuid of the item to be checked on the side defined by 'relationshipLabel' diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RelationshipTypeRelationshipLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RelationshipTypeRelationshipLinkRepository.java index a6c31d0c1632..9e1048929d9a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RelationshipTypeRelationshipLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RelationshipTypeRelationshipLinkRepository.java @@ -8,9 +8,9 @@ package org.dspace.app.rest.repository; import java.sql.SQLException; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; +import jakarta.annotation.Nullable; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.model.RelationshipRest; import org.dspace.app.rest.model.RelationshipTypeRest; import org.dspace.app.rest.projection.Projection; @@ -27,7 +27,7 @@ /** * Link repository for "relationshipType" subresource of an individual Relationship. */ -@Component(RelationshipRest.CATEGORY + "." + RelationshipRest.NAME + "." + RelationshipRest.RELATIONSHIP_TYPE) +@Component(RelationshipRest.CATEGORY + "." + RelationshipRest.PLURAL_NAME + "." + RelationshipRest.RELATIONSHIP_TYPE) public class RelationshipTypeRelationshipLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RelationshipTypeRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RelationshipTypeRestRepository.java index a4e65d75a390..31a1e091686c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RelationshipTypeRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RelationshipTypeRestRepository.java @@ -29,7 +29,7 @@ /** * This is the repository that is responsible to manage RelationshipType Rest objects */ -@Component(RelationshipTypeRest.CATEGORY + "." + RelationshipTypeRest.NAME) +@Component(RelationshipTypeRest.CATEGORY + "." + RelationshipTypeRest.PLURAL_NAME) public class RelationshipTypeRestRepository extends DSpaceRestRepository { @Autowired diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java index 6eb631cfa56e..eaae877f283e 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java @@ -12,15 +12,17 @@ import java.io.IOException; import java.net.MalformedURLException; -import java.net.URI; import java.net.URISyntaxException; import java.sql.SQLException; import java.util.Date; +import java.util.LinkedList; +import java.util.List; import java.util.UUID; -import javax.servlet.http.HttpServletRequest; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.http.HttpServletRequest; +import org.apache.commons.lang3.StringUtils; import org.apache.commons.validator.routines.EmailValidator; import org.apache.http.client.utils.URIBuilder; import org.apache.logging.log4j.LogManager; @@ -53,7 +55,7 @@ * * @author Mark H. Wood */ -@Component(RequestItemRest.CATEGORY + '.' + RequestItemRest.NAME) +@Component(RequestItemRest.CATEGORY + '.' + RequestItemRest.PLURAL_NAME) public class RequestItemRepository extends DSpaceRestRepository { private static final Logger LOG = LogManager.getLogger(); @@ -247,11 +249,15 @@ public RequestItemRest put(Context context, HttpServletRequest request, message = responseMessageNode.asText(); } + JsonNode responseSubjectNode = requestBody.findValue("subject"); + String subject = null; + if (responseSubjectNode != null && !responseSubjectNode.isNull()) { + subject = responseSubjectNode.asText(); + } ri.setDecision_date(new Date()); requestItemService.update(context, ri); // Send the response email - String subject = requestBody.findValue("subject").asText(); try { requestItemEmailNotifier.sendResponse(context, ri, subject, message); } catch (IOException ex) { @@ -283,19 +289,24 @@ public Class getDomainClass() { * Generate a link back to DSpace, to act on a request. * * @param token identifies the request. - * @return URL to the item request API, with the token as request parameter - * "token". + * @return URL to the item request API, with /request-a-copy/{token} as the last URL segments * @throws URISyntaxException passed through. * @throws MalformedURLException passed through. */ - private String getLinkTokenEmail(String token) + public String getLinkTokenEmail(String token) throws URISyntaxException, MalformedURLException { final String base = configurationService.getProperty("dspace.ui.url"); - URI link = new URIBuilder(base) - .setPathSegments("request-a-copy", token) - .build(); + // Construct the link, making sure to support sub-paths + URIBuilder uriBuilder = new URIBuilder(base); + List segments = new LinkedList<>(); + if (StringUtils.isNotBlank(uriBuilder.getPath())) { + segments.add(StringUtils.strip(uriBuilder.getPath(), "/")); + } + segments.add("request-a-copy"); + segments.add(token); - return link.toURL().toExternalForm(); + // Build and return the URL from segments (or throw exception) + return uriBuilder.setPathSegments(segments).build().toURL().toExternalForm(); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ResearcherProfileEPersonLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ResearcherProfileEPersonLinkRepository.java index 92bbf6996d48..3f0a1e84b74c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ResearcherProfileEPersonLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ResearcherProfileEPersonLinkRepository.java @@ -9,9 +9,9 @@ import java.sql.SQLException; import java.util.UUID; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; +import jakarta.annotation.Nullable; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.model.EPersonRest; import org.dspace.app.rest.model.ResearcherProfileRest; import org.dspace.app.rest.projection.Projection; @@ -34,7 +34,8 @@ * @author Luca Giamminonni (luca.giamminonni at 4science.it) * */ -@Component(ResearcherProfileRest.CATEGORY + "." + ResearcherProfileRest.NAME + "." + ResearcherProfileRest.EPERSON) +@Component(ResearcherProfileRest.CATEGORY + "." + ResearcherProfileRest.PLURAL_NAME + "." + + ResearcherProfileRest.EPERSON) public class ResearcherProfileEPersonLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { @Autowired diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ResearcherProfileItemLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ResearcherProfileItemLinkRepository.java index 5f212b966f86..c46f9dbf7c0d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ResearcherProfileItemLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ResearcherProfileItemLinkRepository.java @@ -9,9 +9,9 @@ import java.sql.SQLException; import java.util.UUID; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; +import jakarta.annotation.Nullable; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.model.ItemRest; import org.dspace.app.rest.model.ResearcherProfileRest; import org.dspace.app.rest.projection.Projection; @@ -31,7 +31,7 @@ * @author Luca Giamminonni (luca.giamminonni at 4science.it) * */ -@Component(ResearcherProfileRest.CATEGORY + "." + ResearcherProfileRest.NAME + "." + ResearcherProfileRest.ITEM) +@Component(ResearcherProfileRest.CATEGORY + "." + ResearcherProfileRest.PLURAL_NAME + "." + ResearcherProfileRest.ITEM) public class ResearcherProfileItemLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { @Autowired diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ResearcherProfileRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ResearcherProfileRestRepository.java index 37717f6268d8..4212f01782d6 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ResearcherProfileRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ResearcherProfileRestRepository.java @@ -11,8 +11,8 @@ import java.sql.SQLException; import java.util.List; import java.util.UUID; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.collections.CollectionUtils; import org.dspace.app.rest.exception.DSpaceBadRequestException; import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException; @@ -45,7 +45,7 @@ * @author Luca Giamminonni (luca.giamminonni at 4science.it) * */ -@Component(ResearcherProfileRest.CATEGORY + "." + ResearcherProfileRest.NAME) +@Component(ResearcherProfileRest.CATEGORY + "." + ResearcherProfileRest.PLURAL_NAME) @ConditionalOnProperty(value = "researcher-profile.entity-type") public class ResearcherProfileRestRepository extends DSpaceRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ResourcePolicyRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ResourcePolicyRestRepository.java index 0b77f96b9b5f..8b598aeeae32 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ResourcePolicyRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ResourcePolicyRestRepository.java @@ -12,9 +12,9 @@ import java.util.Arrays; import java.util.List; import java.util.UUID; -import javax.servlet.http.HttpServletRequest; import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.DiscoverableEndpointsService; import org.dspace.app.rest.Parameter; import org.dspace.app.rest.SearchRestMethod; @@ -51,7 +51,7 @@ * * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it) */ -@Component(ResourcePolicyRest.CATEGORY + "." + ResourcePolicyRest.NAME) +@Component(ResourcePolicyRest.CATEGORY + "." + ResourcePolicyRest.PLURAL_NAME) public class ResourcePolicyRestRepository extends DSpaceRestRepository implements InitializingBean { @@ -254,14 +254,6 @@ protected ResourcePolicyRest createAndReturn(Context context) throws AuthorizeEx if (dspaceObject == null) { throw new UnprocessableEntityException("DSpaceObject with this uuid: " + resourceUuid + " not found"); } - resourcePolicy = resourcePolicyService.create(context); - resourcePolicy.setRpType(resourcePolicyRest.getPolicyType()); - resourcePolicy.setdSpaceObject(dspaceObject); - resourcePolicy.setRpName(resourcePolicyRest.getName()); - resourcePolicy.setRpDescription(resourcePolicyRest.getDescription()); - resourcePolicy.setAction(Constants.getActionID(resourcePolicyRest.getAction())); - resourcePolicy.setStartDate(resourcePolicyRest.getStartDate()); - resourcePolicy.setEndDate(resourcePolicyRest.getEndDate()); if (epersonUuidStr != null) { try { @@ -270,12 +262,11 @@ protected ResourcePolicyRest createAndReturn(Context context) throws AuthorizeEx if (ePerson == null) { throw new UnprocessableEntityException("EPerson with uuid: " + epersonUuid + " not found"); } - resourcePolicy.setEPerson(ePerson); - resourcePolicyService.update(context, resourcePolicy); + resourcePolicy = resourcePolicyService.create(context, ePerson, null); + } catch (SQLException excSQL) { throw new RuntimeException(excSQL.getMessage(), excSQL); } - return converter.toRest(resourcePolicy, utils.obtainProjection()); } else { try { UUID groupUuid = UUID.fromString(groupUuidStr); @@ -283,13 +274,27 @@ protected ResourcePolicyRest createAndReturn(Context context) throws AuthorizeEx if (group == null) { throw new UnprocessableEntityException("Group with uuid: " + groupUuid + " not found"); } - resourcePolicy.setGroup(group); - resourcePolicyService.update(context, resourcePolicy); + resourcePolicy = resourcePolicyService.create(context, null, group); } catch (SQLException excSQL) { throw new RuntimeException(excSQL.getMessage(), excSQL); } + } + + if (resourcePolicy != null) { + + resourcePolicy.setRpType(resourcePolicyRest.getPolicyType()); + resourcePolicy.setdSpaceObject(dspaceObject); + resourcePolicy.setRpName(resourcePolicyRest.getName()); + resourcePolicy.setRpDescription(resourcePolicyRest.getDescription()); + resourcePolicy.setAction(Constants.getActionID(resourcePolicyRest.getAction())); + resourcePolicy.setStartDate(resourcePolicyRest.getStartDate()); + resourcePolicy.setEndDate(resourcePolicyRest.getEndDate()); + resourcePolicyService.update(context, resourcePolicy); return converter.toRest(resourcePolicy, utils.obtainProjection()); + } else { + throw new UnprocessableEntityException("A resource policy must contain a valid eperson or group"); } + } @Override diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ScriptRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ScriptRestRepository.java index 1eea06a4ee8d..e26f91c88981 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ScriptRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ScriptRestRepository.java @@ -46,7 +46,7 @@ /** * This is the REST repository dealing with the Script logic */ -@Component(ScriptRest.CATEGORY + "." + ScriptRest.NAME) +@Component(ScriptRest.CATEGORY + "." + ScriptRest.PLURAL_NAME) public class ScriptRestRepository extends DSpaceRestRepository { private static final Logger log = LogManager.getLogger(); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SearchEventRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SearchEventRestRepository.java index b55536d75edd..029047b1dca2 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SearchEventRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SearchEventRestRepository.java @@ -8,10 +8,10 @@ package org.dspace.app.rest.repository; import java.io.IOException; -import javax.servlet.ServletInputStream; -import javax.servlet.http.HttpServletRequest; import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.ServletInputStream; +import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; import org.dspace.app.rest.converter.SearchEventConverter; import org.dspace.app.rest.exception.DSpaceBadRequestException; @@ -25,7 +25,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -@Component(SearchEventRest.CATEGORY + "." + SearchEventRest.NAME) +@Component(SearchEventRest.CATEGORY + "." + SearchEventRest.PLURAL_NAME) public class SearchEventRestRepository extends AbstractDSpaceRestRepository { @Autowired diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SiteRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SiteRestRepository.java index bf13700078ab..1d31b989672e 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SiteRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SiteRestRepository.java @@ -11,8 +11,8 @@ import java.util.Arrays; import java.util.List; import java.util.UUID; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.model.SiteRest; import org.dspace.app.rest.model.patch.Patch; import org.dspace.authorize.AuthorizeException; @@ -31,7 +31,7 @@ * @author Andrea Bollini (andrea.bollini at 4science.it) */ -@Component(SiteRest.CATEGORY + "." + SiteRest.NAME) +@Component(SiteRest.CATEGORY + "." + SiteRest.PLURAL_NAME) public class SiteRestRepository extends DSpaceObjectRestRepository { private final SiteService sitesv; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/StatisticsRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/StatisticsRestRepository.java index 09588a60453b..d1336e5d8aa2 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/StatisticsRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/StatisticsRestRepository.java @@ -31,7 +31,7 @@ import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Component; -@Component(StatisticsSupportRest.CATEGORY + "." + UsageReportRest.NAME) +@Component(StatisticsSupportRest.CATEGORY + "." + UsageReportRest.PLURAL_NAME) public class StatisticsRestRepository extends DSpaceRestRepository { @Autowired diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionAccessOptionRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionAccessOptionRestRepository.java index a4117a617683..85893dd16105 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionAccessOptionRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionAccessOptionRestRepository.java @@ -25,7 +25,7 @@ * * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.com) */ -@Component(SubmissionAccessOptionRest.CATEGORY + "." + SubmissionAccessOptionRest.NAME) +@Component(SubmissionAccessOptionRest.CATEGORY + "." + SubmissionAccessOptionRest.PLURAL_NAME) public class SubmissionAccessOptionRestRepository extends DSpaceRestRepository { @Autowired @@ -48,4 +48,4 @@ public Class getDomainClass() { return SubmissionAccessOptionRest.class; } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionCCLicenseRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionCCLicenseRestRepository.java index 356b3f22bfc4..40a9bc622d50 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionCCLicenseRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionCCLicenseRestRepository.java @@ -26,7 +26,7 @@ /** * This is the repository that is responsible to manage CCLicense Rest objects */ -@Component(SubmissionCCLicenseRest.CATEGORY + "." + SubmissionCCLicenseRest.NAME) +@Component(SubmissionCCLicenseRest.CATEGORY + "." + SubmissionCCLicenseRest.PLURAL_NAME) public class SubmissionCCLicenseRestRepository extends DSpaceRestRepository { @Autowired diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionCoarNotifyRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionCoarNotifyRestRepository.java new file mode 100644 index 000000000000..305c1a4f2912 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionCoarNotifyRestRepository.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.rest.repository; + +import org.dspace.app.rest.model.SubmissionCOARNotifyRest; +import org.dspace.coarnotify.NotifySubmissionConfiguration; +import org.dspace.coarnotify.service.SubmissionNotifyService; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + +/** + * This is the repository that is responsible to manage Submission COAR Notify Rest objects + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + */ +@Component(SubmissionCOARNotifyRest.CATEGORY + "." + SubmissionCOARNotifyRest.PLURAL_NAME) +public class SubmissionCoarNotifyRestRepository extends DSpaceRestRepository { + + @Autowired + protected SubmissionNotifyService submissionCOARNotifyService; + + @Override + @PreAuthorize("hasAuthority('AUTHENTICATED')") + public SubmissionCOARNotifyRest findOne(final Context context, final String id) { + NotifySubmissionConfiguration coarNotifySubmissionConfiguration = submissionCOARNotifyService.findOne(id); + if (coarNotifySubmissionConfiguration == null) { + throw new ResourceNotFoundException( + "No COAR Notify Submission Configuration found for ID: " + id ); + } + return converter.toRest(coarNotifySubmissionConfiguration, utils.obtainProjection()); + } + + @Override + @PreAuthorize("hasAuthority('AUTHENTICATED')") + public Page findAll(final Context context, final Pageable pageable) { + return converter.toRestPage(submissionCOARNotifyService.findAll(), + pageable, utils.obtainProjection()); + } + + @Override + public Class getDomainClass() { + return SubmissionCOARNotifyRest.class; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionDefinitionRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionDefinitionRestRepository.java index 17eb90b7901e..784058ae1c37 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionDefinitionRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionDefinitionRestRepository.java @@ -32,7 +32,7 @@ * * @author Andrea Bollini (andrea.bollini at 4science.it) */ -@Component(SubmissionDefinitionRest.CATEGORY + "." + SubmissionDefinitionRest.NAME) +@Component(SubmissionDefinitionRest.CATEGORY + "." + SubmissionDefinitionRest.PLURAL_NAME) public class SubmissionDefinitionRestRepository extends DSpaceRestRepository { private SubmissionConfigService submissionConfigService; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionFormRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionFormRestRepository.java index 76d680cf276d..a5b9592aa096 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionFormRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionFormRestRepository.java @@ -28,7 +28,7 @@ * * @author Andrea Bollini (andrea.bollini at 4science.it) */ -@Component(SubmissionFormRest.CATEGORY + "." + SubmissionFormRest.NAME) +@Component(SubmissionFormRest.CATEGORY + "." + SubmissionFormRest.PLURAL_NAME) public class SubmissionFormRestRepository extends DSpaceRestRepository { private Map inputReaders; private DCInputsReader defaultInputReader; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionPanelRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionPanelRestRepository.java index 62d104c0a6d3..5eb02d54a55c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionPanelRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionPanelRestRepository.java @@ -28,7 +28,7 @@ * * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it) */ -@Component(SubmissionDefinitionRest.CATEGORY + "." + SubmissionSectionRest.NAME) +@Component(SubmissionDefinitionRest.CATEGORY + "." + SubmissionSectionRest.PLURAL_NAME) public class SubmissionPanelRestRepository extends DSpaceRestRepository { private SubmissionConfigService submissionConfigService; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionUploadRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionUploadRestRepository.java index 24b6b969611b..9b5bed759e98 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionUploadRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionUploadRestRepository.java @@ -36,7 +36,7 @@ * * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it) */ -@Component(SubmissionUploadRest.CATEGORY + "." + SubmissionUploadRest.NAME) +@Component(SubmissionUploadRest.CATEGORY + "." + SubmissionUploadRest.PLURAL_NAME) public class SubmissionUploadRestRepository extends DSpaceRestRepository { private static final Logger log = org.apache.logging.log4j.LogManager diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionDSpaceObjectLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionDSpaceObjectLinkRepository.java index 95c4714e9cae..7e2e32eb9e27 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionDSpaceObjectLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionDSpaceObjectLinkRepository.java @@ -9,9 +9,9 @@ import java.sql.SQLException; import java.util.Objects; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; +import jakarta.annotation.Nullable; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.model.DSpaceObjectRest; import org.dspace.app.rest.model.SubscriptionRest; import org.dspace.app.rest.projection.Projection; @@ -26,7 +26,7 @@ /** * Link repository for "DSpaceObject" of subscription */ -@Component(SubscriptionRest.CATEGORY + "." + SubscriptionRest.NAME + "." + SubscriptionRest.DSPACE_OBJECT) +@Component(SubscriptionRest.CATEGORY + "." + SubscriptionRest.PLURAL_NAME + "." + SubscriptionRest.DSPACE_OBJECT) public class SubscriptionDSpaceObjectLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { @Autowired @@ -46,4 +46,4 @@ public DSpaceObjectRest getDSpaceObject(@Nullable HttpServletRequest request, In } } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionEPersonLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionEPersonLinkRepository.java index dcf612e52daa..25144810bcf9 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionEPersonLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionEPersonLinkRepository.java @@ -9,9 +9,9 @@ import java.sql.SQLException; import java.util.Objects; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; +import jakarta.annotation.Nullable; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.model.EPersonRest; import org.dspace.app.rest.model.SubscriptionRest; import org.dspace.app.rest.projection.Projection; @@ -26,7 +26,7 @@ /** * Link repository for "eperson" of subscription */ -@Component(SubscriptionRest.CATEGORY + "." + SubscriptionRest.NAME + "." + SubscriptionRest.EPERSON) +@Component(SubscriptionRest.CATEGORY + "." + SubscriptionRest.PLURAL_NAME + "." + SubscriptionRest.EPERSON) public class SubscriptionEPersonLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { @Autowired @@ -46,4 +46,4 @@ public EPersonRest getEPerson(@Nullable HttpServletRequest request, Integer subs } } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java index ce1bcff11fbc..37d16414b286 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java @@ -20,11 +20,11 @@ import java.util.List; import java.util.Objects; import java.util.UUID; -import javax.servlet.ServletInputStream; -import javax.servlet.http.HttpServletRequest; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.ServletInputStream; +import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.dspace.app.rest.DiscoverableEndpointsService; @@ -61,7 +61,7 @@ * * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.com) */ -@Component(SubscriptionRest.CATEGORY + "." + SubscriptionRest.NAME) +@Component(SubscriptionRest.CATEGORY + "." + SubscriptionRest.PLURAL_NAME) public class SubscriptionRestRepository extends DSpaceRestRepository implements InitializingBean { @@ -154,7 +154,7 @@ protected SubscriptionRest createAndReturn(Context context) throws SQLException, String dsoId = req.getParameter("resource"); if (StringUtils.isBlank(dsoId) || StringUtils.isBlank(epersonId)) { - throw new UnprocessableEntityException("Both eperson than DSpaceObject uuids must be provieded!"); + throw new UnprocessableEntityException("Both eperson than DSpaceObject uuids must be provided!"); } try { @@ -278,7 +278,7 @@ public Class getDomainClass() { @Override public void afterPropertiesSet() throws Exception { discoverableEndpointsService.register(this, Arrays.asList(Link.of("/api/" + SubscriptionRest.CATEGORY + - "/" + SubscriptionRest.NAME_PLURAL + "/search", SubscriptionRest.NAME_PLURAL + "-search"))); + "/" + SubscriptionRest.PLURAL_NAME + "/search", SubscriptionRest.PLURAL_NAME + "-search"))); } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SuggestionRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SuggestionRestRepository.java index e2e1c3ce7ccb..d8af5d78d452 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SuggestionRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SuggestionRestRepository.java @@ -33,7 +33,7 @@ * @author Andrea Bollini (andrea.bollini at 4science.it) */ -@Component(SuggestionRest.CATEGORY + "." + SuggestionRest.NAME) +@Component(SuggestionRest.CATEGORY + "." + SuggestionRest.PLURAL_NAME) public class SuggestionRestRepository extends DSpaceRestRepository { private final static String ORDER_FIELD = "trust"; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SuggestionSourceRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SuggestionSourceRestRepository.java index 6bc251749bce..30663e5cd557 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SuggestionSourceRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SuggestionSourceRestRepository.java @@ -26,7 +26,7 @@ * @author Andrea Bollini (andrea.bollini at 4science.it) */ -@Component(SuggestionSourceRest.CATEGORY + "." + SuggestionSourceRest.NAME) +@Component(SuggestionSourceRest.CATEGORY + "." + SuggestionSourceRest.PLURAL_NAME) public class SuggestionSourceRestRepository extends DSpaceRestRepository { private static final Logger log = org.apache.logging.log4j.LogManager diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SuggestionTargetRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SuggestionTargetRestRepository.java index e8498cb68c2b..4691ea3600a7 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SuggestionTargetRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SuggestionTargetRestRepository.java @@ -30,7 +30,7 @@ * @author Andrea Bollini (andrea.bollini at 4science.it) */ -@Component(SuggestionTargetRest.CATEGORY + "." + SuggestionTargetRest.NAME) +@Component(SuggestionTargetRest.CATEGORY + "." + SuggestionTargetRest.PLURAL_NAME) public class SuggestionTargetRestRepository extends DSpaceRestRepository { private static final Logger log = org.apache.logging.log4j.LogManager diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SuggestionTargetTargetLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SuggestionTargetTargetLinkRepository.java index 50c6e4d48e27..34d006cef198 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SuggestionTargetTargetLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SuggestionTargetTargetLinkRepository.java @@ -9,9 +9,9 @@ import java.sql.SQLException; 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.lang3.StringUtils; import org.dspace.app.rest.model.ItemRest; import org.dspace.app.rest.model.SuggestionTargetRest; @@ -31,7 +31,7 @@ * @author Andrea Bollini (andrea.bollini at 4science.it) * */ -@Component(SuggestionTargetRest.CATEGORY + "." + SuggestionTargetRest.NAME + "." + SuggestionTargetRest.TARGET) +@Component(SuggestionTargetRest.CATEGORY + "." + SuggestionTargetRest.PLURAL_NAME + "." + SuggestionTargetRest.TARGET) public class SuggestionTargetTargetLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { @Autowired diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SupervisionOrderRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SupervisionOrderRestRepository.java index fb2f589dbc2a..0f47aee3fdff 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SupervisionOrderRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SupervisionOrderRestRepository.java @@ -15,8 +15,8 @@ import java.util.Objects; import java.util.UUID; import java.util.stream.Collectors; -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.app.exception.ResourceAlreadyExistsException; @@ -52,7 +52,7 @@ * * @author Mohamed Eskander (mohamed.eskander at 4science dot it) */ -@Component(SupervisionOrderRest.CATEGORY + "." + SupervisionOrderRest.NAME) +@Component(SupervisionOrderRest.CATEGORY + "." + SupervisionOrderRest.PLURAL_NAME) public class SupervisionOrderRestRepository extends DSpaceRestRepository { private static final Logger log = diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SystemWideAlertRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SystemWideAlertRestRepository.java index 73544145b20f..b3df9b6a939c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SystemWideAlertRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SystemWideAlertRestRepository.java @@ -12,12 +12,12 @@ import java.io.IOException; import java.sql.SQLException; import java.util.List; -import javax.servlet.ServletInputStream; -import javax.servlet.http.HttpServletRequest; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.ServletInputStream; +import jakarta.servlet.http.HttpServletRequest; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.alerts.AllowSessionsEnum; @@ -40,7 +40,7 @@ /** * The repository for the SystemWideAlert workload */ -@Component(SystemWideAlertRest.CATEGORY + "." + SystemWideAlertRest.NAME) +@Component(SystemWideAlertRest.CATEGORY + "." + SystemWideAlertRest.PLURAL_NAME) public class SystemWideAlertRestRepository extends DSpaceRestRepository { private static final Logger log = LogManager.getLogger(); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/TemplateItemRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/TemplateItemRestRepository.java index ad801404819b..1bacaa4ddb1f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/TemplateItemRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/TemplateItemRestRepository.java @@ -34,7 +34,7 @@ /** * This is the repository class that is responsible for handling {@link TemplateItemRest} objects */ -@Component(TemplateItemRest.CATEGORY + "." + TemplateItemRest.NAME) +@Component(TemplateItemRest.CATEGORY + "." + TemplateItemRest.PLURAL_NAME) public class TemplateItemRestRepository extends DSpaceRestRepository { @Autowired diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionHistoryDraftVersionLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionHistoryDraftVersionLinkRepository.java index 21d90ddda07f..56a413b7e864 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionHistoryDraftVersionLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionHistoryDraftVersionLinkRepository.java @@ -8,9 +8,9 @@ package org.dspace.app.rest.repository; import java.sql.SQLException; import java.util.Objects; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; +import jakarta.annotation.Nullable; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.exception.DSpaceBadRequestException; import org.dspace.app.rest.model.AInprogressSubmissionRest; import org.dspace.app.rest.model.VersionHistoryRest; @@ -35,7 +35,7 @@ * * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.it) */ -@Component(VersionHistoryRest.CATEGORY + "." + VersionHistoryRest.NAME + "." + VersionHistoryRest.DRAFT_VERSION) +@Component(VersionHistoryRest.CATEGORY + "." + VersionHistoryRest.PLURAL_NAME + "." + VersionHistoryRest.DRAFT_VERSION) public class VersionHistoryDraftVersionLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { @@ -79,4 +79,4 @@ public AInprogressSubmissionRest getDraftVersion(@Nullable HttpServletRequest re return null; } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionHistoryLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionHistoryLinkRepository.java index 6456a0c2b5dc..46e751ad1e0b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionHistoryLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionHistoryLinkRepository.java @@ -8,9 +8,9 @@ package org.dspace.app.rest.repository; import java.sql.SQLException; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; +import jakarta.annotation.Nullable; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.model.VersionHistoryRest; import org.dspace.app.rest.model.VersionRest; import org.dspace.app.rest.projection.Projection; @@ -27,7 +27,7 @@ * This is the Repository that takes care of the retrieval of the {@link VersionHistory} object * for a given {@link Version} */ -@Component(VersionRest.CATEGORY + "." + VersionRest.NAME + "." + VersionRest.VERSION_HISTORY) +@Component(VersionRest.CATEGORY + "." + VersionRest.PLURAL_NAME + "." + VersionRest.VERSION_HISTORY) public class VersionHistoryLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionHistoryRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionHistoryRestRepository.java index f41003d17dbd..9207add5f756 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionHistoryRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionHistoryRestRepository.java @@ -26,7 +26,7 @@ /** * Repository for the operations on the {@link VersionHistoryRest} endpoints */ -@Component(VersionHistoryRest.CATEGORY + "." + VersionHistoryRest.NAME) +@Component(VersionHistoryRest.CATEGORY + "." + VersionHistoryRest.PLURAL_NAME) public class VersionHistoryRestRepository extends DSpaceRestRepository { private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(VersionHistoryRestRepository.class); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionItemLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionItemLinkRepository.java index 4fd692506b71..d171492e9fa1 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionItemLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionItemLinkRepository.java @@ -8,9 +8,9 @@ package org.dspace.app.rest.repository; import java.sql.SQLException; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; +import jakarta.annotation.Nullable; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.model.ItemRest; import org.dspace.app.rest.model.VersionRest; import org.dspace.app.rest.projection.Projection; @@ -27,7 +27,7 @@ * This Repository takes care of the retrieval of the {@link org.dspace.content.Item} objects * for a given {@link Version} */ -@Component(VersionRest.CATEGORY + "." + VersionRest.NAME + "." + VersionRest.ITEM) +@Component(VersionRest.CATEGORY + "." + VersionRest.PLURAL_NAME + "." + VersionRest.ITEM) public class VersionItemLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionRestRepository.java index 585fc1de9f0b..a2c8453bda73 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionRestRepository.java @@ -9,8 +9,8 @@ import java.sql.SQLException; import java.util.List; import java.util.Objects; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.lang.StringUtils; import org.apache.logging.log4j.Logger; import org.dspace.app.rest.converter.ConverterService; @@ -48,7 +48,7 @@ * * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.it) */ -@Component(VersionRest.CATEGORY + "." + VersionRest.NAME) +@Component(VersionRest.CATEGORY + "." + VersionRest.PLURAL_NAME) public class VersionRestRepository extends DSpaceRestRepository implements ReloadableEntityObjectRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionsLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionsLinkRepository.java index 2538b51cf16a..f7b0bb902f69 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionsLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionsLinkRepository.java @@ -9,9 +9,9 @@ import java.sql.SQLException; import java.util.List; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; +import jakarta.annotation.Nullable; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.model.VersionHistoryRest; import org.dspace.app.rest.model.VersionRest; import org.dspace.app.rest.projection.Projection; @@ -32,7 +32,7 @@ * This is the Repository that takes care of the retrieval of the {@link Version} objects for a given * {@link VersionHistory} */ -@Component(VersionHistoryRest.CATEGORY + "." + VersionHistoryRest.NAME + "." + VersionHistoryRest.VERSIONS) +@Component(VersionHistoryRest.CATEGORY + "." + VersionHistoryRest.PLURAL_NAME + "." + VersionHistoryRest.VERSIONS) public class VersionsLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ViewEventRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ViewEventRestRepository.java index d49f07d4392a..9c53af48c67f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ViewEventRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ViewEventRestRepository.java @@ -12,10 +12,10 @@ import java.util.Arrays; import java.util.List; import java.util.Locale; -import javax.servlet.ServletInputStream; -import javax.servlet.http.HttpServletRequest; import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.ServletInputStream; +import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; import org.dspace.app.rest.exception.DSpaceBadRequestException; import org.dspace.app.rest.exception.UnprocessableEntityException; @@ -31,7 +31,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -@Component(ViewEventRest.CATEGORY + "." + ViewEventRest.NAME) +@Component(ViewEventRest.CATEGORY + "." + ViewEventRest.PLURAL_NAME) public class ViewEventRestRepository extends AbstractDSpaceRestRepository { @Autowired diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsChildrenLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsChildrenLinkRepository.java index 1788fa893b36..631130be60a5 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsChildrenLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsChildrenLinkRepository.java @@ -9,9 +9,9 @@ import java.util.ArrayList; import java.util.List; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; +import jakarta.annotation.Nullable; +import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; import org.dspace.app.rest.exception.LinkNotFoundException; import org.dspace.app.rest.model.VocabularyEntryDetailsRest; @@ -35,7 +35,8 @@ * * @author Mykhaylo Boychuk (4Science.it) */ -@Component(VocabularyRest.CATEGORY + "." + VocabularyEntryDetailsRest.NAME + "." + VocabularyEntryDetailsRest.CHILDREN) +@Component(VocabularyRest.CATEGORY + "." + VocabularyEntryDetailsRest.PLURAL_NAME + "." + + VocabularyEntryDetailsRest.CHILDREN) public class VocabularyEntryDetailsChildrenLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsParentLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsParentLinkRepository.java index d7a1ff85c363..aa6912e99676 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsParentLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsParentLinkRepository.java @@ -7,10 +7,9 @@ */ package org.dspace.app.rest.repository; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; -import javax.ws.rs.NotFoundException; - +import jakarta.annotation.Nullable; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.ws.rs.NotFoundException; import org.apache.commons.lang3.StringUtils; import org.dspace.app.rest.model.VocabularyEntryDetailsRest; import org.dspace.app.rest.model.VocabularyRest; @@ -30,7 +29,8 @@ * * @author Mykhaylo Boychuk ($science.it) */ -@Component(VocabularyRest.CATEGORY + "." + VocabularyEntryDetailsRest.NAME + "." + VocabularyEntryDetailsRest.PARENT) +@Component(VocabularyRest.CATEGORY + "." + VocabularyEntryDetailsRest.PLURAL_NAME + "." + + VocabularyEntryDetailsRest.PARENT) public class VocabularyEntryDetailsParentLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsRestRepository.java index 3e4d600b1213..cf74dd76f417 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsRestRepository.java @@ -40,7 +40,7 @@ * * @author Andrea Bollini (andrea.bollini at 4science.it) */ -@Component(VocabularyRest.CATEGORY + "." + VocabularyEntryDetailsRest.NAME) +@Component(VocabularyRest.CATEGORY + "." + VocabularyEntryDetailsRest.PLURAL_NAME) public class VocabularyEntryDetailsRestRepository extends DSpaceRestRepository implements InitializingBean { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java index a24bef42c281..3a62a5cbaeab 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java @@ -9,9 +9,9 @@ import java.util.ArrayList; import java.util.List; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; +import jakarta.annotation.Nullable; +import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.dspace.app.rest.exception.UnprocessableEntityException; @@ -38,7 +38,7 @@ * * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it) */ -@Component(VocabularyRest.CATEGORY + "." + VocabularyRest.NAME + "." + VocabularyRest.ENTRIES) +@Component(VocabularyRest.CATEGORY + "." + VocabularyRest.PLURAL_NAME + "." + VocabularyRest.ENTRIES) public class VocabularyEntryLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyRestRepository.java index 783ed418233d..ee80e4320946 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyRestRepository.java @@ -38,7 +38,7 @@ * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it) * @author Andrea Bollini (andrea.bollini at 4science.it) */ -@Component(VocabularyRest.CATEGORY + "." + VocabularyRest.NAME) +@Component(VocabularyRest.CATEGORY + "." + VocabularyRest.PLURAL_NAME) public class VocabularyRestRepository extends DSpaceRestRepository { @Autowired diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowActionRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowActionRestRepository.java index 75c2e5c1405b..92efe9849420 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowActionRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowActionRestRepository.java @@ -24,7 +24,7 @@ * * @author Maria Verdonck (Atmire) on 06/01/2020 */ -@Component(WorkflowActionRest.CATEGORY + "." + WorkflowActionRest.NAME) +@Component(WorkflowActionRest.CATEGORY + "." + WorkflowActionRest.PLURAL_NAME) public class WorkflowActionRestRepository extends DSpaceRestRepository { @Autowired diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowDefinitionRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowDefinitionRestRepository.java index 7aa6d7d28048..8c4836a23d4c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowDefinitionRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowDefinitionRestRepository.java @@ -32,7 +32,7 @@ * * @author Maria Verdonck (Atmire) on 11/12/2019 */ -@Component(WorkflowDefinitionRest.CATEGORY + "." + WorkflowDefinitionRest.NAME) +@Component(WorkflowDefinitionRest.CATEGORY + "." + WorkflowDefinitionRest.PLURAL_NAME) public class WorkflowDefinitionRestRepository extends DSpaceRestRepository { @Autowired diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowItemCollectionLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowItemCollectionLinkRepository.java new file mode 100644 index 000000000000..09c7cc4c0f12 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowItemCollectionLinkRepository.java @@ -0,0 +1,60 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.rest.repository; + +import java.sql.SQLException; +import javax.annotation.Nullable; + +import jakarta.servlet.http.HttpServletRequest; +import org.dspace.app.rest.model.CollectionRest; +import org.dspace.app.rest.model.WorkflowItemRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.core.Context; +import org.dspace.workflow.WorkflowItem; +import org.dspace.xmlworkflow.storedcomponents.service.XmlWorkflowItemService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Pageable; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + +/** + * Link repository for "collection" subresource of a workflow item. + */ +@Component(WorkflowItemRest.CATEGORY + "." + WorkflowItemRest.PLURAL_NAME + "." + WorkflowItemRest.COLLECTION) +public class WorkflowItemCollectionLinkRepository extends AbstractDSpaceRestRepository + implements LinkRestRepository { + + @Autowired + XmlWorkflowItemService wis; + + /** + * Retrieve the item for a workflow collection. + * + * @param request - The current request + * @param id - The workflow item ID for which to retrieve the collection + * @param optionalPageable - optional pageable object + * @param projection - the current projection + * @return the item for the workflow collection + */ + @PreAuthorize("hasPermission(#id, 'WORKFLOWITEM', 'READ')") + public CollectionRest getWorkflowItemCollection(@Nullable HttpServletRequest request, Integer id, + @Nullable Pageable optionalPageable, Projection projection) { + try { + Context context = obtainContext(); + WorkflowItem witem = wis.find(context, id); + if (witem == null) { + throw new ResourceNotFoundException("No such workflow item: " + id); + } + + return converter.toRest(witem.getCollection(), projection); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowItemItemLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowItemItemLinkRepository.java new file mode 100644 index 000000000000..96b09c8d64ac --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowItemItemLinkRepository.java @@ -0,0 +1,60 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.rest.repository; + +import java.sql.SQLException; +import javax.annotation.Nullable; + +import jakarta.servlet.http.HttpServletRequest; +import org.dspace.app.rest.model.ItemRest; +import org.dspace.app.rest.model.WorkflowItemRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.core.Context; +import org.dspace.workflow.WorkflowItem; +import org.dspace.xmlworkflow.storedcomponents.service.XmlWorkflowItemService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Pageable; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + +/** + * Link repository for "item" subresource of a workflow item. + */ +@Component(WorkflowItemRest.CATEGORY + "." + WorkflowItemRest.PLURAL_NAME + "." + WorkflowItemRest.ITEM) +public class WorkflowItemItemLinkRepository extends AbstractDSpaceRestRepository + implements LinkRestRepository { + + @Autowired + XmlWorkflowItemService wis; + + /** + * Retrieve the item for a workflow item. + * + * @param request - The current request + * @param id - The workflow item ID for which to retrieve the item + * @param optionalPageable - optional pageable object + * @param projection - the current projection + * @return the item for the workflow item + */ + @PreAuthorize("hasPermission(#id, 'WORKFLOWITEM', 'READ')") + public ItemRest getWorkflowItemItem(@Nullable HttpServletRequest request, Integer id, + @Nullable Pageable optionalPageable, Projection projection) { + try { + Context context = obtainContext(); + WorkflowItem witem = wis.find(context, id); + if (witem == null) { + throw new ResourceNotFoundException("No such workflow item: " + id); + } + + return converter.toRest(witem.getItem(), projection); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowItemRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowItemRestRepository.java index de39ff69fb9c..f6c879b3efdf 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowItemRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowItemRestRepository.java @@ -13,8 +13,8 @@ import java.sql.SQLException; import java.util.List; import java.util.UUID; -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.rest.Parameter; @@ -67,7 +67,7 @@ * @author Andrea Bollini (andrea.bollini at 4science.it) */ -@Component(WorkflowItemRest.CATEGORY + "." + WorkflowItemRest.NAME) +@Component(WorkflowItemRest.CATEGORY + "." + WorkflowItemRest.PLURAL_NAME) public class WorkflowItemRestRepository extends DSpaceRestRepository { public static final String OPERATION_PATH_SECTIONS = "sections"; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowItemStepLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowItemStepLinkRepository.java index 30aac1579c79..0cbdf50cbcef 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowItemStepLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowItemStepLinkRepository.java @@ -9,9 +9,9 @@ import java.sql.SQLException; import java.util.List; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; +import jakarta.annotation.Nullable; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.model.WorkflowItemRest; import org.dspace.app.rest.model.WorkflowStepRest; import org.dspace.app.rest.projection.Projection; @@ -31,7 +31,7 @@ /** * Link Repository for the Steps subresources of an individual WorkflowItem */ -@Component(WorkflowItemRest.CATEGORY + "." + WorkflowItemRest.NAME + "." + WorkflowItemRest.STEP) +@Component(WorkflowItemRest.CATEGORY + "." + WorkflowItemRest.PLURAL_NAME + "." + WorkflowItemRest.STEP) public class WorkflowItemStepLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { @Autowired diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowItemSubmitterLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowItemSubmitterLinkRepository.java new file mode 100644 index 000000000000..9fe8e29bfa6b --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowItemSubmitterLinkRepository.java @@ -0,0 +1,60 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.rest.repository; + +import java.sql.SQLException; +import javax.annotation.Nullable; + +import jakarta.servlet.http.HttpServletRequest; +import org.dspace.app.rest.model.EPersonRest; +import org.dspace.app.rest.model.WorkflowItemRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.core.Context; +import org.dspace.workflow.WorkflowItem; +import org.dspace.xmlworkflow.storedcomponents.service.XmlWorkflowItemService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Pageable; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + +/** + * Link repository for "submitter" subresource of a workflow item. + */ +@Component(WorkflowItemRest.CATEGORY + "." + WorkflowItemRest.PLURAL_NAME + "." + WorkflowItemRest.SUBMITTER) +public class WorkflowItemSubmitterLinkRepository extends AbstractDSpaceRestRepository + implements LinkRestRepository { + + @Autowired + XmlWorkflowItemService wis; + + /** + * Retrieve the submitter for a workflow item. + * + * @param request - The current request + * @param id - The workflow item ID for which to retrieve the submitter + * @param optionalPageable - optional pageable object + * @param projection - the current projection + * @return the submitter for the workflow item + */ + @PreAuthorize("hasPermission(#id, 'WORKFLOWITEM', 'READ')") + public EPersonRest getWorkflowItemSubmitter(@Nullable HttpServletRequest request, Integer id, + @Nullable Pageable optionalPageable, Projection projection) { + try { + Context context = obtainContext(); + WorkflowItem witem = wis.find(context, id); + if (witem == null) { + throw new ResourceNotFoundException("No such workflow item: " + id); + } + + return converter.toRest(witem.getSubmitter(), projection); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowStepRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowStepRestRepository.java index 798c352b2291..2102bab71050 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowStepRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowStepRestRepository.java @@ -24,7 +24,7 @@ * * @author Maria Verdonck (Atmire) on 10/01/2020 */ -@Component(WorkflowStepRest.CATEGORY + "." + WorkflowStepRest.NAME) +@Component(WorkflowStepRest.CATEGORY + "." + WorkflowStepRest.PLURAL_NAME) public class WorkflowStepRestRepository extends DSpaceRestRepository { @Autowired diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemCollectionLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemCollectionLinkRepository.java new file mode 100644 index 000000000000..a3ad57c31c72 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemCollectionLinkRepository.java @@ -0,0 +1,60 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.rest.repository; + +import java.sql.SQLException; +import javax.annotation.Nullable; + +import jakarta.servlet.http.HttpServletRequest; +import org.dspace.app.rest.model.CollectionRest; +import org.dspace.app.rest.model.WorkspaceItemRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.content.WorkspaceItem; +import org.dspace.content.service.WorkspaceItemService; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Pageable; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + +/** + * Link repository for "collection" subresource of a workspace item. + */ +@Component(WorkspaceItemRest.CATEGORY + "." + WorkspaceItemRest.PLURAL_NAME + "." + WorkspaceItemRest.COLLECTION) +public class WorkspaceItemCollectionLinkRepository extends AbstractDSpaceRestRepository + implements LinkRestRepository { + + @Autowired + WorkspaceItemService wis; + + /** + * Retrieve the collection for a workspace item. + * + * @param request - The current request + * @param id - The workspace item ID for which to retrieve the collection + * @param optionalPageable - optional pageable object + * @param projection - the current projection + * @return the collection for the workspace item + */ + @PreAuthorize("hasPermission(#id, 'WORKSPACEITEM', 'READ')") + public CollectionRest getWorkspaceItemCollection(@Nullable HttpServletRequest request, Integer id, + @Nullable Pageable optionalPageable, Projection projection) { + try { + Context context = obtainContext(); + WorkspaceItem witem = wis.find(context, id); + if (witem == null) { + throw new ResourceNotFoundException("No such workspace item: " + id); + } + + return converter.toRest(witem.getCollection(), projection); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemItemLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemItemLinkRepository.java new file mode 100644 index 000000000000..0eb4e1b27dcb --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemItemLinkRepository.java @@ -0,0 +1,60 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.rest.repository; + +import java.sql.SQLException; +import javax.annotation.Nullable; + +import jakarta.servlet.http.HttpServletRequest; +import org.dspace.app.rest.model.ItemRest; +import org.dspace.app.rest.model.WorkspaceItemRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.content.WorkspaceItem; +import org.dspace.content.service.WorkspaceItemService; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Pageable; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + +/** + * Link repository for "item" subresource of a workspace item. + */ +@Component(WorkspaceItemRest.CATEGORY + "." + WorkspaceItemRest.PLURAL_NAME + "." + WorkspaceItemRest.ITEM) +public class WorkspaceItemItemLinkRepository extends AbstractDSpaceRestRepository + implements LinkRestRepository { + + @Autowired + WorkspaceItemService wis; + + /** + * Retrieve the item for a workspace item. + * + * @param request - The current request + * @param id - The workspace item ID for which to retrieve the item + * @param optionalPageable - optional pageable object + * @param projection - the current projection + * @return the item for the workspace item + */ + @PreAuthorize("hasPermission(#id, 'WORKSPACEITEM', 'READ')") + public ItemRest getWorkspaceItemItem(@Nullable HttpServletRequest request, Integer id, + @Nullable Pageable optionalPageable, Projection projection) { + try { + Context context = obtainContext(); + WorkspaceItem witem = wis.find(context, id); + if (witem == null) { + throw new ResourceNotFoundException("No such workspace item: " + id); + } + + return converter.toRest(witem.getItem(), projection); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemRestRepository.java index 5f4bb0dfe927..278fc2c935c4 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemRestRepository.java @@ -14,8 +14,8 @@ 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.logging.log4j.Logger; import org.dspace.app.rest.Parameter; @@ -73,7 +73,7 @@ * @author Andrea Bollini (andrea.bollini at 4science.it) * @author Pasquale Cavallo (pasquale.cavallo at 4science.it) */ -@Component(WorkspaceItemRest.CATEGORY + "." + WorkspaceItemRest.NAME) +@Component(WorkspaceItemRest.CATEGORY + "." + WorkspaceItemRest.PLURAL_NAME) public class WorkspaceItemRestRepository extends DSpaceRestRepository implements ReloadableEntityObjectRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemSubmitterLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemSubmitterLinkRepository.java new file mode 100644 index 000000000000..707ab7e13d5e --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemSubmitterLinkRepository.java @@ -0,0 +1,60 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.rest.repository; + +import java.sql.SQLException; +import javax.annotation.Nullable; + +import jakarta.servlet.http.HttpServletRequest; +import org.dspace.app.rest.model.EPersonRest; +import org.dspace.app.rest.model.WorkspaceItemRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.content.WorkspaceItem; +import org.dspace.content.service.WorkspaceItemService; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Pageable; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + +/** + * Link repository for "submitter" subresource of a workspace item. + */ +@Component(WorkspaceItemRest.CATEGORY + "." + WorkspaceItemRest.PLURAL_NAME + "." + WorkspaceItemRest.SUBMITTER) +public class WorkspaceItemSubmitterLinkRepository extends AbstractDSpaceRestRepository + implements LinkRestRepository { + + @Autowired + WorkspaceItemService wis; + + /** + * Retrieve the submitter for a workspace item. + * + * @param request - The current request + * @param id - The workspace item ID for which to retrieve the submitter + * @param optionalPageable - optional pageable object + * @param projection - the current projection + * @return the submitter for the workspace item + */ + @PreAuthorize("hasPermission(#id, 'WORKSPACEITEM', 'READ')") + public EPersonRest getWorkspaceItemSubmitter(@Nullable HttpServletRequest request, Integer id, + @Nullable Pageable optionalPageable, Projection projection) { + try { + Context context = obtainContext(); + WorkspaceItem witem = wis.find(context, id); + if (witem == null) { + throw new ResourceNotFoundException("No such workspace item: " + id); + } + + return converter.toRest(witem.getSubmitter(), projection); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemSupervisionOrdersLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemSupervisionOrdersLinkRepository.java index e0d57ae0de52..79d77bf9441d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemSupervisionOrdersLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemSupervisionOrdersLinkRepository.java @@ -8,9 +8,9 @@ package org.dspace.app.rest.repository; import java.sql.SQLException; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; +import jakarta.annotation.Nullable; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.model.SupervisionOrderRest; import org.dspace.app.rest.model.WorkspaceItemRest; import org.dspace.app.rest.projection.Projection; @@ -30,7 +30,8 @@ * * @author Mohamed Eskander (mohamed.eskander at 4science dot it) */ -@Component(WorkspaceItemRest.CATEGORY + "." + WorkspaceItemRest.NAME + "." + WorkspaceItemRest.SUPERVISION_ORDERS) +@Component(WorkspaceItemRest.CATEGORY + "." + WorkspaceItemRest.PLURAL_NAME + "." + + WorkspaceItemRest.SUPERVISION_ORDERS) public class WorkspaceItemSupervisionOrdersLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/handler/ExternalSourceCorrectionTypeUriListHandler.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/handler/ExternalSourceCorrectionTypeUriListHandler.java new file mode 100644 index 000000000000..1d377934afef --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/handler/ExternalSourceCorrectionTypeUriListHandler.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.app.rest.repository.handler; + +import java.sql.SQLException; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import jakarta.servlet.http.HttpServletRequest; +import org.dspace.app.rest.exception.DSpaceBadRequestException; +import org.dspace.authorize.AuthorizeException; +import org.dspace.core.Context; +import org.dspace.correctiontype.CorrectionType; +import org.dspace.correctiontype.service.CorrectionTypeService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * This class extends the {@link ExternalSourceEntryItemUriListHandler} abstract class and implements it specifically + * for the List<{@link CorrectionType}> objects. + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + */ +@Component +public class ExternalSourceCorrectionTypeUriListHandler extends ExternalSourceEntryItemUriListHandler { + + @Autowired + private CorrectionTypeService correctionTypeService; + + @Override + @SuppressWarnings("rawtypes") + public boolean supports(List uriList, String method,Class clazz) { + return clazz != CorrectionType.class ? false : true; + } + + @Override + public CorrectionType handle(Context context, HttpServletRequest request, List uriList) + throws SQLException, AuthorizeException { + return getObjectFromUriList(context, uriList); + } + + @Override + public boolean validate(Context context, HttpServletRequest request, List uriList) + throws AuthorizeException { + return uriList.size() > 1 ? false : true; + } + + + private CorrectionType getObjectFromUriList(Context context, List uriList) { + CorrectionType correctionType = null; + String url = uriList.get(0); + Pattern pattern = Pattern.compile("\\/api\\/config\\/correctiontypes\\/(.*)"); + Matcher matcher = pattern.matcher(url); + if (!matcher.find()) { + throw new DSpaceBadRequestException("The uri: " + url + " doesn't resolve to an correction type"); + } + String id = matcher.group(1); + try { + correctionType = correctionTypeService.findOne(id); + } catch (Exception e) { + throw new RuntimeException(e.getMessage(), e); + } + return correctionType; + } + +} \ No newline at end of file diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/handler/ExternalSourceEntryArchivedItemUriListHandler.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/handler/ExternalSourceEntryArchivedItemUriListHandler.java index 86189933d957..8cbbd99ba65d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/handler/ExternalSourceEntryArchivedItemUriListHandler.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/handler/ExternalSourceEntryArchivedItemUriListHandler.java @@ -9,8 +9,8 @@ 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.authorize.AuthorizeException; import org.dspace.authorize.service.AuthorizeService; @@ -73,7 +73,7 @@ public Item handle(Context context, HttpServletRequest request, List uri WorkspaceItem workspaceItem = super.createWorkspaceItem(context, request, uriList); return installItemService.installItem(context, workspaceItem); } catch (AuthorizeException | SQLException e) { - log.error("An error occured when trying to create item in collection with uuid: " + owningCollectionUuid, + log.error("An error occurred when trying to create item in collection with uuid: " + owningCollectionUuid, e); throw e; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/handler/ExternalSourceEntryItemUriListHandler.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/handler/ExternalSourceEntryItemUriListHandler.java index 3ad9b7a19356..172411f6ac27 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/handler/ExternalSourceEntryItemUriListHandler.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/handler/ExternalSourceEntryItemUriListHandler.java @@ -15,8 +15,8 @@ import java.util.UUID; import java.util.regex.Matcher; import java.util.regex.Pattern; -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.app.rest.model.ExternalSourceRest; @@ -106,7 +106,7 @@ public WorkspaceItem createWorkspaceItem(Context context, HttpServletRequest req Collection collection = collectionService.find(context, UUID.fromString(owningCollectionUuid)); return externalDataService.createWorkspaceItemFromExternalDataObject(context, dataObject, collection); } catch (AuthorizeException | SQLException e) { - log.error("An error occured when trying to create item in collection with uuid: " + owningCollectionUuid, + log.error("An error occurred when trying to create item in collection with uuid: " + owningCollectionUuid, e); throw e; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/handler/ExternalSourceEntryOrcidQueueUriListHandler.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/handler/ExternalSourceEntryOrcidQueueUriListHandler.java index 0965058449be..df66a44b7b37 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/handler/ExternalSourceEntryOrcidQueueUriListHandler.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/handler/ExternalSourceEntryOrcidQueueUriListHandler.java @@ -11,8 +11,8 @@ import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.authorize.AuthorizeException; import org.dspace.core.Context; import org.dspace.orcid.OrcidQueue; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/handler/ExternalSourceEntryPoolTaskUriListHandler.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/handler/ExternalSourceEntryPoolTaskUriListHandler.java index 4ed438f93be5..afe075644721 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/handler/ExternalSourceEntryPoolTaskUriListHandler.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/handler/ExternalSourceEntryPoolTaskUriListHandler.java @@ -11,8 +11,8 @@ import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.authorize.AuthorizeException; import org.dspace.core.Context; import org.dspace.xmlworkflow.storedcomponents.PoolTask; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/handler/ExternalSourceEntryWorkspaceItemUriListHandler.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/handler/ExternalSourceEntryWorkspaceItemUriListHandler.java index 6491ec0fd6aa..9c83ee376077 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/handler/ExternalSourceEntryWorkspaceItemUriListHandler.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/handler/ExternalSourceEntryWorkspaceItemUriListHandler.java @@ -9,8 +9,8 @@ import java.sql.SQLException; import java.util.List; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.authorize.AuthorizeException; import org.dspace.content.WorkspaceItem; import org.dspace.core.Context; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/handler/ExternalSourceItemUriListHandler.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/handler/ExternalSourceItemUriListHandler.java index 201a7ba1633d..16c43336e90d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/handler/ExternalSourceItemUriListHandler.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/handler/ExternalSourceItemUriListHandler.java @@ -11,8 +11,8 @@ import java.util.UUID; import java.util.regex.Matcher; import java.util.regex.Pattern; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.exception.DSpaceBadRequestException; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Item; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/handler/UriListHandler.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/handler/UriListHandler.java index 84d652284539..bf12b6598bac 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/handler/UriListHandler.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/handler/UriListHandler.java @@ -9,8 +9,8 @@ import java.sql.SQLException; import java.util.List; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.authorize.AuthorizeException; import org.dspace.core.Context; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/handler/service/UriListHandlerService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/handler/service/UriListHandlerService.java index 5366428b1cd6..c9603386cc38 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/handler/service/UriListHandlerService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/handler/service/UriListHandlerService.java @@ -9,8 +9,8 @@ import java.sql.SQLException; 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.rest.exception.DSpaceBadRequestException; @@ -42,7 +42,7 @@ public class UriListHandlerService { * @param context The relevant DSpace context * @param request The current active Request * @param uriList The list of Strings representing the UriList to be handled - * @param clazz The class to be hadled + * @param clazz The class to be handled * @param The class to be returned, same as the class parameter above * @return The object that was handled through this method */ diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/PatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/PatchOperation.java index 0842746f329d..9864dae09d28 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/PatchOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/PatchOperation.java @@ -59,7 +59,7 @@ public void checkOperationValue(Object value) { * @return the original or derived boolean value * @throws DSpaceBadRequestException */ - Boolean getBooleanOperationValue(Object value) { + protected Boolean getBooleanOperationValue(Object value) { Boolean bool; if (value instanceof String) { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceDescriptionAddOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceDescriptionAddOperation.java new file mode 100644 index 000000000000..0f973a976043 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceDescriptionAddOperation.java @@ -0,0 +1,77 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.rest.repository.patch.operation.ldn; + +import java.sql.SQLException; + +import org.dspace.app.ldn.NotifyServiceEntity; +import org.dspace.app.ldn.service.NotifyService; +import org.dspace.app.rest.exception.DSpaceBadRequestException; +import org.dspace.app.rest.exception.UnprocessableEntityException; +import org.dspace.app.rest.model.patch.Operation; +import org.dspace.app.rest.repository.patch.operation.PatchOperation; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Implementation for NotifyService Description Add patches. + * + * Example: + * curl -X PATCH http://${dspace.server.url}/api/ldn/ldnservices/<:id-notifyService> -H " + * Content-Type: application/json" -d ' + * [{ + * "op": "add", + * "path": "/description", + * "value": "description value" + * }]' + * + */ +@Component +public class NotifyServiceDescriptionAddOperation extends PatchOperation { + + @Autowired + private NotifyService notifyService; + + private static final String OPERATION_PATH = "/description"; + + @Override + public NotifyServiceEntity perform(Context context, NotifyServiceEntity notifyServiceEntity, Operation operation) + throws SQLException { + checkOperationValue(operation.getValue()); + + Object description = operation.getValue(); + if (description == null | !(description instanceof String)) { + throw new UnprocessableEntityException("The /description value must be a string"); + } + + checkNonExistingDescriptionValue(notifyServiceEntity); + notifyServiceEntity.setDescription((String) description); + notifyService.update(context, notifyServiceEntity); + return notifyServiceEntity; + } + + /** + * Throws PatchBadRequestException if a value is already set in the /description path. + * + * @param notifyServiceEntity the notifyServiceEntity to update + + */ + void checkNonExistingDescriptionValue(NotifyServiceEntity notifyServiceEntity) { + if (notifyServiceEntity.getDescription() != null) { + throw new DSpaceBadRequestException("Attempting to add a value to an already existing path."); + } + } + + @Override + public boolean supports(Object objectToMatch, Operation operation) { + return (objectToMatch instanceof NotifyServiceEntity && + operation.getOp().trim().equalsIgnoreCase(OPERATION_ADD) && + operation.getPath().trim().toLowerCase().equalsIgnoreCase(OPERATION_PATH)); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceDescriptionRemoveOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceDescriptionRemoveOperation.java new file mode 100644 index 000000000000..18e9515b0fb3 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceDescriptionRemoveOperation.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.rest.repository.patch.operation.ldn; + +import java.sql.SQLException; + +import org.dspace.app.ldn.NotifyServiceEntity; +import org.dspace.app.ldn.service.NotifyService; +import org.dspace.app.rest.model.patch.Operation; +import org.dspace.app.rest.repository.patch.operation.PatchOperation; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Implementation for NotifyService Description Remove patches. + * + * Example: + * curl -X PATCH http://${dspace.server.url}/api/ldn/ldnservices/<:id-notifyService> -H " + * Content-Type: application/json" -d ' + * [{ + * "op": "remove", + * "path": "/description" + * }]' + * + */ +@Component +public class NotifyServiceDescriptionRemoveOperation extends PatchOperation { + + @Autowired + private NotifyService notifyService; + + private static final String OPERATION_PATH = "/description"; + + @Override + public NotifyServiceEntity perform(Context context, NotifyServiceEntity notifyServiceEntity, Operation operation) + throws SQLException { + notifyServiceEntity.setDescription(null); + notifyService.update(context, notifyServiceEntity); + return notifyServiceEntity; + } + + @Override + public boolean supports(Object objectToMatch, Operation operation) { + return (objectToMatch instanceof NotifyServiceEntity && + operation.getOp().trim().equalsIgnoreCase(OPERATION_REMOVE) && + operation.getPath().trim().toLowerCase().equalsIgnoreCase(OPERATION_PATH)); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceDescriptionReplaceOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceDescriptionReplaceOperation.java new file mode 100644 index 000000000000..8f7eedc75831 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceDescriptionReplaceOperation.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.rest.repository.patch.operation.ldn; + +import java.sql.SQLException; + +import org.dspace.app.ldn.NotifyServiceEntity; +import org.dspace.app.ldn.service.NotifyService; +import org.dspace.app.rest.exception.DSpaceBadRequestException; +import org.dspace.app.rest.exception.UnprocessableEntityException; +import org.dspace.app.rest.model.patch.Operation; +import org.dspace.app.rest.repository.patch.operation.PatchOperation; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Implementation for NotifyService Description Replace patches. + * + * Example: + * curl -X PATCH http://${dspace.server.url}/api/ldn/ldnservices/<:id-notifyService> -H " + * Content-Type: application/json" -d ' + * [{ + * "op": "replace", + * "path": "/description", + * "value": "description value" + * }]' + * + */ +@Component +public class NotifyServiceDescriptionReplaceOperation extends PatchOperation { + + @Autowired + private NotifyService notifyService; + + private static final String OPERATION_PATH = "/description"; + + @Override + public NotifyServiceEntity perform(Context context, NotifyServiceEntity notifyServiceEntity, Operation operation) + throws SQLException { + + Object description = operation.getValue(); + if (description == null | !(description instanceof String)) { + throw new UnprocessableEntityException("The /description value must be a string"); + } + + checkModelForExistingValue(notifyServiceEntity); + notifyServiceEntity.setDescription((String) description); + notifyService.update(context, notifyServiceEntity); + return notifyServiceEntity; + } + + /** + * Checks whether the description of notifyServiceEntity has an existing value to replace + * @param notifyServiceEntity Object on which patch is being done + */ + private void checkModelForExistingValue(NotifyServiceEntity notifyServiceEntity) { + if (notifyServiceEntity.getDescription() == null) { + throw new DSpaceBadRequestException("Attempting to replace a non-existent value (description)."); + } + } + + @Override + public boolean supports(Object objectToMatch, Operation operation) { + return (objectToMatch instanceof NotifyServiceEntity && + operation.getOp().trim().equalsIgnoreCase(OPERATION_REPLACE) && + operation.getPath().trim().toLowerCase().equalsIgnoreCase(OPERATION_PATH)); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceEnabledReplaceOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceEnabledReplaceOperation.java new file mode 100644 index 000000000000..ccfa6e90200d --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceEnabledReplaceOperation.java @@ -0,0 +1,66 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.rest.repository.patch.operation.ldn; + +import java.sql.SQLException; + +import org.dspace.app.ldn.NotifyServiceEntity; +import org.dspace.app.ldn.service.NotifyService; +import org.dspace.app.rest.exception.DSpaceBadRequestException; +import org.dspace.app.rest.model.patch.Operation; +import org.dspace.app.rest.repository.patch.operation.PatchOperation; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Implementation for NotifyService Enabled Replace patches. + * + * Example: + * curl -X PATCH http://${dspace.server.url}/api/ldn/ldnservices/<:id-notifyService> -H " + * Content-Type: application/json" -d ' + * [{ + * "op": "replace", + * "path": "/enabled" + * }]' + * + */ +@Component +public class NotifyServiceEnabledReplaceOperation extends PatchOperation { + + @Autowired + private NotifyService notifyService; + + @Autowired + private NotifyServicePatchUtils notifyServicePatchUtils; + + private static final String OPERATION_PATH = "/enabled"; + + @Override + public NotifyServiceEntity perform(Context context, NotifyServiceEntity notifyServiceEntity, Operation operation) + throws SQLException { + checkOperationValue(operation.getValue()); + Boolean enabled = getBooleanOperationValue(operation.getValue()); + + if (supports(notifyServiceEntity, operation)) { + notifyServiceEntity.setEnabled(enabled); + notifyService.update(context, notifyServiceEntity); + return notifyServiceEntity; + } else { + throw new DSpaceBadRequestException( + "NotifyServiceEnabledReplaceOperation does not support this operation"); + } + } + + @Override + public boolean supports(Object objectToMatch, Operation operation) { + return (objectToMatch instanceof NotifyServiceEntity && + operation.getOp().trim().equalsIgnoreCase(OPERATION_REPLACE) && + operation.getPath().trim().toLowerCase().equalsIgnoreCase(OPERATION_PATH)); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceInboundPatternAutomaticReplaceOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceInboundPatternAutomaticReplaceOperation.java new file mode 100644 index 000000000000..0f3d8c394d7d --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceInboundPatternAutomaticReplaceOperation.java @@ -0,0 +1,83 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.rest.repository.patch.operation.ldn; + +import static org.dspace.app.rest.repository.patch.operation.ldn.NotifyServicePatchUtils.NOTIFY_SERVICE_INBOUND_PATTERNS; + +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.service.NotifyServiceInboundPatternService; +import org.dspace.app.rest.exception.DSpaceBadRequestException; +import org.dspace.app.rest.model.patch.Operation; +import org.dspace.app.rest.repository.patch.operation.PatchOperation; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Implementation for NotifyService Inbound patterns Automatic Replace patches. + * + * Example: + * curl -X PATCH http://${dspace.server.url}/api/ldn/ldnservices/<:id-notifyService> -H " + * Content-Type: application/json" -d ' + * [{ + * "op": "replace", + * "path": "notifyServiceInboundPatterns[index]/automatic" + * }]' + * + */ +@Component +public class NotifyServiceInboundPatternAutomaticReplaceOperation extends PatchOperation { + + @Autowired + private NotifyServiceInboundPatternService inboundPatternService; + + @Autowired + private NotifyServicePatchUtils notifyServicePatchUtils; + + private static final String OPERATION_PATH = "/automatic"; + + @Override + public NotifyServiceEntity perform(Context context, NotifyServiceEntity notifyServiceEntity, Operation operation) { + checkOperationValue(operation.getValue()); + Boolean automatic = getBooleanOperationValue(operation.getValue()); + if (supports(notifyServiceEntity, operation)) { + try { + int index = notifyServicePatchUtils.extractIndexFromOperation(operation); + + List inboundPatterns = notifyServiceEntity.getInboundPatterns(); + + if (index >= inboundPatterns.size()) { + throw new DSpaceBadRequestException("the provided index[" + index + "] is out of the rang"); + } + + NotifyServiceInboundPattern inboundPattern = inboundPatterns.get(index); + inboundPattern.setAutomatic(automatic); + inboundPatternService.update(context, inboundPattern); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + return notifyServiceEntity; + } else { + throw new DSpaceBadRequestException( + "NotifyServiceInboundPatternAutomaticReplaceOperation does not support this operation"); + } + } + + @Override + public boolean supports(Object objectToMatch, Operation operation) { + String path = operation.getPath().trim(); + return (objectToMatch instanceof NotifyServiceEntity && + operation.getOp().trim().equalsIgnoreCase(OPERATION_REPLACE) && + path.startsWith(NOTIFY_SERVICE_INBOUND_PATTERNS + "[") && + path.endsWith(OPERATION_PATH)); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceInboundPatternConstraintAddOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceInboundPatternConstraintAddOperation.java new file mode 100644 index 000000000000..e82243f9a7ae --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceInboundPatternConstraintAddOperation.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.app.rest.repository.patch.operation.ldn; + +import static org.dspace.app.rest.repository.patch.operation.ldn.NotifyServicePatchUtils.NOTIFY_SERVICE_INBOUND_PATTERNS; + +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.service.NotifyServiceInboundPatternService; +import org.dspace.app.rest.exception.DSpaceBadRequestException; +import org.dspace.app.rest.exception.UnprocessableEntityException; +import org.dspace.app.rest.model.patch.Operation; +import org.dspace.app.rest.repository.patch.operation.PatchOperation; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Implementation for NotifyService Inbound patterns Constraint Add patches. + * + * Example: + * curl -X PATCH http://${dspace.server.url}/api/ldn/ldnservices/<:id-notifyService> -H " + * Content-Type: application/json" -d ' + * [{ + * "op": "add", + * "path": "notifyServiceInboundPatterns[index]/constraint" + * }]' + * + */ +@Component +public class NotifyServiceInboundPatternConstraintAddOperation extends PatchOperation { + + @Autowired + private NotifyServiceInboundPatternService inboundPatternService; + + @Autowired + private NotifyServicePatchUtils notifyServicePatchUtils; + + private static final String OPERATION_PATH = "/constraint"; + + @Override + public NotifyServiceEntity perform(Context context, NotifyServiceEntity notifyServiceEntity, Operation operation) { + checkOperationValue(operation.getValue()); + if (supports(notifyServiceEntity, operation)) { + try { + int index = notifyServicePatchUtils.extractIndexFromOperation(operation); + + List inboundPatterns = notifyServiceEntity.getInboundPatterns(); + + if (index >= inboundPatterns.size()) { + throw new DSpaceBadRequestException("the provided index[" + index + "] is out of the rang"); + } + + NotifyServiceInboundPattern inboundPattern = inboundPatterns.get(index); + Object constraint = operation.getValue(); + if (constraint == null | !(constraint instanceof String)) { + throw new UnprocessableEntityException("The /constraint value must be a string"); + } + + checkNonExistingConstraintValue(inboundPattern); + inboundPattern.setConstraint((String) constraint); + inboundPatternService.update(context, inboundPattern); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + return notifyServiceEntity; + } else { + throw new DSpaceBadRequestException( + "NotifyServiceInboundPatternConstraintAddOperation does not support this operation"); + } + } + + private void checkNonExistingConstraintValue(NotifyServiceInboundPattern inboundPattern) { + if (inboundPattern.getConstraint() != null) { + throw new DSpaceBadRequestException("Attempting to add a value to an already existing path."); + } + } + + @Override + public boolean supports(Object objectToMatch, Operation operation) { + String path = operation.getPath().trim(); + return (objectToMatch instanceof NotifyServiceEntity && + operation.getOp().trim().equalsIgnoreCase(OPERATION_ADD) && + path.startsWith(NOTIFY_SERVICE_INBOUND_PATTERNS + "[") && + path.endsWith(OPERATION_PATH)); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceInboundPatternConstraintRemoveOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceInboundPatternConstraintRemoveOperation.java new file mode 100644 index 000000000000..52d0a6bf730f --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceInboundPatternConstraintRemoveOperation.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.app.rest.repository.patch.operation.ldn; + +import static org.dspace.app.rest.repository.patch.operation.ldn.NotifyServicePatchUtils.NOTIFY_SERVICE_INBOUND_PATTERNS; + +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.service.NotifyServiceInboundPatternService; +import org.dspace.app.rest.exception.DSpaceBadRequestException; +import org.dspace.app.rest.model.patch.Operation; +import org.dspace.app.rest.repository.patch.operation.PatchOperation; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Implementation for NotifyService Inbound patterns Constraint Remove patches. + * + * Example: + * curl -X PATCH http://${dspace.server.url}/api/ldn/ldnservices/<:id-notifyService> -H " + * Content-Type: application/json" -d ' + * [{ + * "op": "remove", + * "path": "notifyServiceInboundPatterns[index]/constraint" + * }]' + * + */ +@Component +public class NotifyServiceInboundPatternConstraintRemoveOperation extends PatchOperation { + + @Autowired + private NotifyServiceInboundPatternService inboundPatternService; + + @Autowired + private NotifyServicePatchUtils notifyServicePatchUtils; + + private static final String OPERATION_PATH = "/constraint"; + + @Override + public NotifyServiceEntity perform(Context context, NotifyServiceEntity notifyServiceEntity, Operation operation) { + if (supports(notifyServiceEntity, operation)) { + try { + int index = notifyServicePatchUtils.extractIndexFromOperation(operation); + + List inboundPatterns = notifyServiceEntity.getInboundPatterns(); + + if (index >= inboundPatterns.size()) { + throw new DSpaceBadRequestException("the provided index[" + index + "] is out of the rang"); + } + + NotifyServiceInboundPattern inboundPattern = inboundPatterns.get(index); + inboundPattern.setConstraint(null); + inboundPatternService.update(context, inboundPattern); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + return notifyServiceEntity; + } else { + throw new DSpaceBadRequestException( + "NotifyServiceInboundPatternConstraintRemoveOperation does not support this operation"); + } + } + + @Override + public boolean supports(Object objectToMatch, Operation operation) { + String path = operation.getPath().trim(); + return (objectToMatch instanceof NotifyServiceEntity && + operation.getOp().trim().equalsIgnoreCase(OPERATION_REMOVE) && + path.startsWith(NOTIFY_SERVICE_INBOUND_PATTERNS + "[") && + path.endsWith(OPERATION_PATH)); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceInboundPatternConstraintReplaceOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceInboundPatternConstraintReplaceOperation.java new file mode 100644 index 000000000000..6faaadbfacde --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceInboundPatternConstraintReplaceOperation.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.app.rest.repository.patch.operation.ldn; + +import static org.dspace.app.rest.repository.patch.operation.ldn.NotifyServicePatchUtils.NOTIFY_SERVICE_INBOUND_PATTERNS; + +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.service.NotifyServiceInboundPatternService; +import org.dspace.app.rest.exception.DSpaceBadRequestException; +import org.dspace.app.rest.exception.UnprocessableEntityException; +import org.dspace.app.rest.model.patch.Operation; +import org.dspace.app.rest.repository.patch.operation.PatchOperation; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Implementation for NotifyService Inbound patterns Constraint Replace patches. + * + * Example: + * curl -X PATCH http://${dspace.server.url}/api/ldn/ldnservices/<:id-notifyService> -H " + * Content-Type: application/json" -d ' + * [{ + * "op": "replace", + * "path": "notifyServiceInboundPatterns[index]/constraint" + * }]' + * + */ +@Component +public class NotifyServiceInboundPatternConstraintReplaceOperation extends PatchOperation { + + @Autowired + private NotifyServiceInboundPatternService inboundPatternService; + + @Autowired + private NotifyServicePatchUtils notifyServicePatchUtils; + + private static final String OPERATION_PATH = "/constraint"; + + @Override + public NotifyServiceEntity perform(Context context, NotifyServiceEntity notifyServiceEntity, Operation operation) { + checkOperationValue(operation.getValue()); + if (supports(notifyServiceEntity, operation)) { + try { + int index = notifyServicePatchUtils.extractIndexFromOperation(operation); + + List inboundPatterns = notifyServiceEntity.getInboundPatterns(); + + if (index >= inboundPatterns.size()) { + throw new DSpaceBadRequestException("the provided index[" + index + "] is out of the rang"); + } + + NotifyServiceInboundPattern inboundPattern = inboundPatterns.get(index); + Object constraint = operation.getValue(); + if (constraint == null | !(constraint instanceof String)) { + throw new UnprocessableEntityException("The /constraint value must be a string"); + } + + checkModelForExistingValue(inboundPattern); + inboundPattern.setConstraint((String) constraint); + inboundPatternService.update(context, inboundPattern); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + return notifyServiceEntity; + } else { + throw new DSpaceBadRequestException( + "NotifyServiceInboundPatternConstraintReplaceOperation does not support this operation"); + } + } + + private void checkModelForExistingValue(NotifyServiceInboundPattern inboundPattern) { + if (inboundPattern.getConstraint() == null) { + throw new DSpaceBadRequestException("Attempting to replace a non-existent value (constraint)."); + } + } + + @Override + public boolean supports(Object objectToMatch, Operation operation) { + String path = operation.getPath().trim(); + return (objectToMatch instanceof NotifyServiceEntity && + operation.getOp().trim().equalsIgnoreCase(OPERATION_REPLACE) && + path.startsWith(NOTIFY_SERVICE_INBOUND_PATTERNS + "[") && + path.endsWith(OPERATION_PATH)); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceInboundPatternPatternAddOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceInboundPatternPatternAddOperation.java new file mode 100644 index 000000000000..17f92057f900 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceInboundPatternPatternAddOperation.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.app.rest.repository.patch.operation.ldn; + +import static org.dspace.app.rest.repository.patch.operation.ldn.NotifyServicePatchUtils.NOTIFY_SERVICE_INBOUND_PATTERNS; + +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.service.NotifyServiceInboundPatternService; +import org.dspace.app.rest.exception.DSpaceBadRequestException; +import org.dspace.app.rest.exception.UnprocessableEntityException; +import org.dspace.app.rest.model.patch.Operation; +import org.dspace.app.rest.repository.patch.operation.PatchOperation; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Implementation for NotifyService Inbound patterns Pattern Add patches. + * + * Example: + * curl -X PATCH http://${dspace.server.url}/api/ldn/ldnservices/<:id-notifyService> -H " + * Content-Type: application/json" -d ' + * [{ + * "op": "add", + * "path": "notifyServiceInboundPatterns[index]/pattern" + * }]' + * + */ +@Component +public class NotifyServiceInboundPatternPatternAddOperation extends PatchOperation { + + @Autowired + private NotifyServiceInboundPatternService inboundPatternService; + + @Autowired + private NotifyServicePatchUtils notifyServicePatchUtils; + + private static final String OPERATION_PATH = "/pattern"; + + @Override + public NotifyServiceEntity perform(Context context, NotifyServiceEntity notifyServiceEntity, Operation operation) { + checkOperationValue(operation.getValue()); + if (supports(notifyServiceEntity, operation)) { + try { + int index = notifyServicePatchUtils.extractIndexFromOperation(operation); + + List inboundPatterns = notifyServiceEntity.getInboundPatterns(); + + if (index >= inboundPatterns.size()) { + throw new DSpaceBadRequestException("the provided index[" + index + "] is out of the rang"); + } + + NotifyServiceInboundPattern inboundPattern = inboundPatterns.get(index); + Object pattern = operation.getValue(); + if (pattern == null | !(pattern instanceof String)) { + throw new UnprocessableEntityException("The /pattern value must be a string"); + } + + checkNonExistingPatternValue(inboundPattern); + inboundPattern.setPattern((String) pattern); + inboundPatternService.update(context, inboundPattern); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + return notifyServiceEntity; + } else { + throw new DSpaceBadRequestException( + "NotifyServiceInboundPatternPatternAddOperation does not support this operation"); + } + } + + private void checkNonExistingPatternValue(NotifyServiceInboundPattern inboundPattern) { + if (inboundPattern.getPattern() != null) { + throw new DSpaceBadRequestException("Attempting to add a value to an already existing path."); + } + } + + @Override + public boolean supports(Object objectToMatch, Operation operation) { + String path = operation.getPath().trim(); + return (objectToMatch instanceof NotifyServiceEntity && + operation.getOp().trim().equalsIgnoreCase(OPERATION_ADD) && + path.startsWith(NOTIFY_SERVICE_INBOUND_PATTERNS + "[") && + path.endsWith(OPERATION_PATH)); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceInboundPatternPatternReplaceOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceInboundPatternPatternReplaceOperation.java new file mode 100644 index 000000000000..5c32cec62b20 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceInboundPatternPatternReplaceOperation.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.app.rest.repository.patch.operation.ldn; + +import static org.dspace.app.rest.repository.patch.operation.ldn.NotifyServicePatchUtils.NOTIFY_SERVICE_INBOUND_PATTERNS; + +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.service.NotifyServiceInboundPatternService; +import org.dspace.app.rest.exception.DSpaceBadRequestException; +import org.dspace.app.rest.exception.UnprocessableEntityException; +import org.dspace.app.rest.model.patch.Operation; +import org.dspace.app.rest.repository.patch.operation.PatchOperation; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Implementation for NotifyService Inbound patterns Pattern Replace patches. + * + * Example: + * curl -X PATCH http://${dspace.server.url}/api/ldn/ldnservices/<:id-notifyService> -H " + * Content-Type: application/json" -d ' + * [{ + * "op": "replace", + * "path": "notifyServiceInboundPatterns[index]/pattern" + * }]' + * + */ +@Component +public class NotifyServiceInboundPatternPatternReplaceOperation extends PatchOperation { + + @Autowired + private NotifyServiceInboundPatternService inboundPatternService; + + @Autowired + private NotifyServicePatchUtils notifyServicePatchUtils; + + private static final String OPERATION_PATH = "/pattern"; + + @Override + public NotifyServiceEntity perform(Context context, NotifyServiceEntity notifyServiceEntity, Operation operation) { + checkOperationValue(operation.getValue()); + if (supports(notifyServiceEntity, operation)) { + try { + int index = notifyServicePatchUtils.extractIndexFromOperation(operation); + + List inboundPatterns = notifyServiceEntity.getInboundPatterns(); + + if (index >= inboundPatterns.size()) { + throw new DSpaceBadRequestException("the provided index[" + index + "] is out of the rang"); + } + + NotifyServiceInboundPattern inboundPattern = inboundPatterns.get(index); + Object pattern = operation.getValue(); + if (pattern == null | !(pattern instanceof String)) { + throw new UnprocessableEntityException("The /pattern value must be a string"); + } + + checkModelForExistingValue(inboundPattern); + inboundPattern.setPattern((String) pattern); + inboundPatternService.update(context, inboundPattern); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + return notifyServiceEntity; + } else { + throw new DSpaceBadRequestException( + "NotifyServiceInboundPatternPatternReplaceOperation does not support this operation"); + } + } + + private void checkModelForExistingValue(NotifyServiceInboundPattern inboundPattern) { + if (inboundPattern.getPattern() == null) { + throw new DSpaceBadRequestException("Attempting to replace a non-existent value (pattern)."); + } + } + + @Override + public boolean supports(Object objectToMatch, Operation operation) { + String path = operation.getPath().trim(); + return (objectToMatch instanceof NotifyServiceEntity && + operation.getOp().trim().equalsIgnoreCase(OPERATION_REPLACE) && + path.startsWith(NOTIFY_SERVICE_INBOUND_PATTERNS + "[") && + path.endsWith(OPERATION_PATH)); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceInboundPatternRemoveOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceInboundPatternRemoveOperation.java new file mode 100644 index 000000000000..45b9dff74d22 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceInboundPatternRemoveOperation.java @@ -0,0 +1,79 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.rest.repository.patch.operation.ldn; + +import static org.dspace.app.rest.repository.patch.operation.ldn.NotifyServicePatchUtils.NOTIFY_SERVICE_INBOUND_PATTERNS; + +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.service.NotifyServiceInboundPatternService; +import org.dspace.app.rest.exception.DSpaceBadRequestException; +import org.dspace.app.rest.model.patch.Operation; +import org.dspace.app.rest.repository.patch.operation.PatchOperation; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Implementation for NotifyService Inbound pattern Remove patches. + * + * Example: + * curl -X PATCH http://${dspace.server.url}/api/ldn/ldnservices/<:id-notifyService> -H " + * Content-Type: application/json" -d ' + * [{ + * "op": "remove", + * "path": "notifyServiceInboundPatterns[index]" + * }]' + * + */ +@Component +public class NotifyServiceInboundPatternRemoveOperation extends PatchOperation { + + @Autowired + private NotifyServiceInboundPatternService inboundPatternService; + + @Autowired + private NotifyServicePatchUtils notifyServicePatchUtils; + + private static final String OPERATION_PATH = NOTIFY_SERVICE_INBOUND_PATTERNS + "["; + + @Override + public NotifyServiceEntity perform(Context context, NotifyServiceEntity notifyServiceEntity, Operation operation) { + if (supports(notifyServiceEntity, operation)) { + try { + int index = notifyServicePatchUtils.extractIndexFromOperation(operation); + + List inboundPatterns = notifyServiceEntity.getInboundPatterns(); + + if (index >= inboundPatterns.size()) { + throw new DSpaceBadRequestException("the provided index[" + index + "] is out of the rang"); + } + + inboundPatternService.delete(context, inboundPatterns.get(index)); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + return notifyServiceEntity; + } else { + throw new DSpaceBadRequestException( + "NotifyServiceInboundPatternRemoveOperation does not support this operation"); + } + } + + @Override + public boolean supports(Object objectToMatch, Operation operation) { + String path = operation.getPath().trim(); + return (objectToMatch instanceof NotifyServiceEntity && + operation.getOp().trim().equalsIgnoreCase(OPERATION_REMOVE) && + path.startsWith(OPERATION_PATH) && + path.endsWith("]")); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceInboundPatternReplaceOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceInboundPatternReplaceOperation.java new file mode 100644 index 000000000000..65e4bd2eedc3 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceInboundPatternReplaceOperation.java @@ -0,0 +1,89 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.rest.repository.patch.operation.ldn; + +import static org.dspace.app.rest.repository.patch.operation.ldn.NotifyServicePatchUtils.NOTIFY_SERVICE_INBOUND_PATTERNS; + +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.service.NotifyServiceInboundPatternService; +import org.dspace.app.rest.exception.DSpaceBadRequestException; +import org.dspace.app.rest.model.patch.Operation; +import org.dspace.app.rest.repository.patch.operation.PatchOperation; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Implementation for NotifyService Inbound patterns Replace One patches. + * + * Example: + * curl -X PATCH http://${dspace.server.url}/api/ldn/ldnservices/<:id-notifyService> -H " + * Content-Type: application/json" -d ' + * [{ + * "op": "replace", + * "path": "notifyServiceInboundPatterns[index]", + * "value": {"pattern":"patternA","constraint":"itemFilterA","automatic":"false"} + * }]' + * + */ +@Component +public class NotifyServiceInboundPatternReplaceOperation extends PatchOperation { + + @Autowired + private NotifyServiceInboundPatternService inboundPatternService; + + @Autowired + private NotifyServicePatchUtils notifyServicePatchUtils; + + private static final String OPERATION_PATH = NOTIFY_SERVICE_INBOUND_PATTERNS + "["; + + @Override + public NotifyServiceEntity perform(Context context, NotifyServiceEntity notifyServiceEntity, Operation operation) { + checkOperationValue(operation.getValue()); + if (supports(notifyServiceEntity, operation)) { + try { + int index = notifyServicePatchUtils.extractIndexFromOperation(operation); + + List inboundPatterns = notifyServiceEntity.getInboundPatterns(); + + if (index >= inboundPatterns.size()) { + throw new DSpaceBadRequestException("the provided index[" + index + "] is out of the rang"); + } + + NotifyServiceInboundPattern patchInboundPattern = + notifyServicePatchUtils.extractNotifyServiceInboundPatternFromOperation(operation); + + NotifyServiceInboundPattern existedInboundPattern = inboundPatterns.get(index); + + existedInboundPattern.setPattern(patchInboundPattern.getPattern()); + existedInboundPattern.setConstraint(patchInboundPattern.getConstraint()); + existedInboundPattern.setAutomatic(patchInboundPattern.isAutomatic()); + inboundPatternService.update(context, existedInboundPattern); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + return notifyServiceEntity; + } else { + throw new DSpaceBadRequestException( + "NotifyServiceInboundPatternReplaceOperation does not support this operation"); + } + } + + @Override + public boolean supports(Object objectToMatch, Operation operation) { + String path = operation.getPath().trim(); + return (objectToMatch instanceof NotifyServiceEntity && + operation.getOp().trim().equalsIgnoreCase(OPERATION_REPLACE) && + path.startsWith(OPERATION_PATH) && + path.endsWith("]")); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceInboundPatternsAddOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceInboundPatternsAddOperation.java new file mode 100644 index 000000000000..a734bb5a5513 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceInboundPatternsAddOperation.java @@ -0,0 +1,87 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.rest.repository.patch.operation.ldn; + +import static org.dspace.app.rest.repository.patch.operation.ldn.NotifyServicePatchUtils.NOTIFY_SERVICE_INBOUND_PATTERNS; + +import java.sql.SQLException; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.app.ldn.NotifyServiceEntity; +import org.dspace.app.ldn.NotifyServiceInboundPattern; +import org.dspace.app.ldn.service.NotifyServiceInboundPatternService; +import org.dspace.app.rest.exception.DSpaceBadRequestException; +import org.dspace.app.rest.model.patch.Operation; +import org.dspace.app.rest.repository.patch.operation.PatchOperation; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Implementation for NotifyService Inbound patterns Add patches. + * + * Example: + * curl -X PATCH http://${dspace.server.url}/api/ldn/ldnservices/<:id-notifyService> -H " + * Content-Type: application/json" -d ' + * [{ + * "op": "add", + * "path": "notifyServiceInboundPatterns/-", + * "value": {"pattern":"patternA","constraint":"itemFilterA","automatic":"false"} + * }]' + * + */ +@Component +public class NotifyServiceInboundPatternsAddOperation extends PatchOperation { + + @Autowired + private NotifyServiceInboundPatternService inboundPatternService; + + @Autowired + private NotifyServicePatchUtils notifyServicePatchUtils; + + private static final String OPERATION_PATH = NOTIFY_SERVICE_INBOUND_PATTERNS + "/-"; + + @Override + public NotifyServiceEntity perform(Context context, NotifyServiceEntity notifyServiceEntity, Operation operation) { + checkOperationValue(operation.getValue()); + if (supports(notifyServiceEntity, operation)) { + try { + NotifyServiceInboundPattern patchInboundPattern = + notifyServicePatchUtils.extractNotifyServiceInboundPatternFromOperation(operation); + + NotifyServiceInboundPattern persistInboundPattern = inboundPatternService.findByServiceAndPattern( + context, notifyServiceEntity, patchInboundPattern.getPattern()); + + if (persistInboundPattern != null && (StringUtils.isNotBlank(persistInboundPattern.getConstraint()) + && persistInboundPattern.getConstraint().equals(patchInboundPattern + .getConstraint()))) { + throw new DSpaceBadRequestException("the provided InboundPattern is already existed"); + } + + NotifyServiceInboundPattern inboundPattern = + inboundPatternService.create(context, notifyServiceEntity); + inboundPattern.setPattern(patchInboundPattern.getPattern()); + inboundPattern.setConstraint(patchInboundPattern.getConstraint()); + inboundPattern.setAutomatic(patchInboundPattern.isAutomatic()); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + return notifyServiceEntity; + } else { + throw new DSpaceBadRequestException( + "NotifyServiceInboundPatternsAddOperation does not support this operation"); + } + } + + @Override + public boolean supports(Object objectToMatch, Operation operation) { + return (objectToMatch instanceof NotifyServiceEntity && + operation.getOp().trim().equalsIgnoreCase(OPERATION_ADD) && + operation.getPath().trim().equalsIgnoreCase(OPERATION_PATH)); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceInboundPatternsRemoveOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceInboundPatternsRemoveOperation.java new file mode 100644 index 000000000000..2ff00dcab58a --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceInboundPatternsRemoveOperation.java @@ -0,0 +1,71 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.rest.repository.patch.operation.ldn; + +import static org.dspace.app.rest.repository.patch.operation.ldn.NotifyServicePatchUtils.NOTIFY_SERVICE_INBOUND_PATTERNS; + +import java.sql.SQLException; + +import org.dspace.app.ldn.NotifyServiceEntity; +import org.dspace.app.ldn.NotifyServiceInboundPattern; +import org.dspace.app.ldn.service.NotifyServiceInboundPatternService; +import org.dspace.app.rest.exception.DSpaceBadRequestException; +import org.dspace.app.rest.model.patch.Operation; +import org.dspace.app.rest.repository.patch.operation.PatchOperation; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Implementation for NotifyService Inbound patterns Remove All patches. + * + * Example: + * curl -X PATCH http://${dspace.server.url}/api/ldn/ldnservices/<:id-notifyService> -H " + * Content-Type: application/json" -d ' + * [{ + * "op": "remove", + * "path": "notifyServiceInboundPatterns" + * }]' + * + */ +@Component +public class NotifyServiceInboundPatternsRemoveOperation extends PatchOperation { + + @Autowired + private NotifyServiceInboundPatternService inboundPatternService; + + @Autowired + private NotifyServicePatchUtils notifyServicePatchUtils; + + private static final String OPERATION_PATH = NOTIFY_SERVICE_INBOUND_PATTERNS; + + @Override + public NotifyServiceEntity perform(Context context, NotifyServiceEntity notifyServiceEntity, Operation operation) { + if (supports(notifyServiceEntity, operation)) { + try { + for (NotifyServiceInboundPattern inboundPattern : notifyServiceEntity.getInboundPatterns()) { + inboundPatternService.delete(context, inboundPattern); + } + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + return notifyServiceEntity; + } else { + throw new DSpaceBadRequestException( + "NotifyServiceInboundPatternsRemoveOperation does not support this operation"); + } + } + + @Override + public boolean supports(Object objectToMatch, Operation operation) { + String path = operation.getPath().trim(); + return (objectToMatch instanceof NotifyServiceEntity && + operation.getOp().trim().equalsIgnoreCase(OPERATION_REMOVE) && + path.startsWith(OPERATION_PATH) && path.endsWith(OPERATION_PATH)); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceInboundPatternsReplaceOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceInboundPatternsReplaceOperation.java new file mode 100644 index 000000000000..2dd98e7d172e --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceInboundPatternsReplaceOperation.java @@ -0,0 +1,88 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.rest.repository.patch.operation.ldn; + +import static org.dspace.app.rest.repository.patch.operation.ldn.NotifyServicePatchUtils.NOTIFY_SERVICE_INBOUND_PATTERNS; + +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.service.NotifyServiceInboundPatternService; +import org.dspace.app.rest.exception.DSpaceBadRequestException; +import org.dspace.app.rest.model.patch.Operation; +import org.dspace.app.rest.repository.patch.operation.PatchOperation; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Implementation for NotifyService Inbound patterns Replace All patches. + * + * Example: + * curl -X PATCH http://${dspace.server.url}/api/ldn/ldnservices/<:id-notifyService> -H " + * Content-Type: application/json" -d ' + * [{ + * "op": "replace", + * "path": "notifyServiceInboundPatterns", + * "value": [{"pattern":"patternA","constraint":"itemFilterA","automatic":"false"}] + * }]' + * + */ +@Component +public class NotifyServiceInboundPatternsReplaceOperation extends PatchOperation { + + @Autowired + private NotifyServiceInboundPatternService inboundPatternService; + + @Autowired + private NotifyServicePatchUtils notifyServicePatchUtils; + + private static final String OPERATION_PATH = NOTIFY_SERVICE_INBOUND_PATTERNS; + + @Override + public NotifyServiceEntity perform(Context context, NotifyServiceEntity notifyServiceEntity, Operation operation) { + checkOperationValue(operation.getValue()); + if (supports(notifyServiceEntity, operation)) { + try { + List patchInboundPatterns = + notifyServicePatchUtils.extractNotifyServiceInboundPatternsFromOperation(operation); + + notifyServiceEntity.getInboundPatterns().forEach(inboundPattern -> { + try { + inboundPatternService.delete(context, inboundPattern); + } catch (SQLException e) { + throw new RuntimeException(e); + } + }); + + for (NotifyServiceInboundPattern patchInboundPattern : patchInboundPatterns) { + NotifyServiceInboundPattern inboundPattern = + inboundPatternService.create(context, notifyServiceEntity); + inboundPattern.setPattern(patchInboundPattern.getPattern()); + inboundPattern.setConstraint(patchInboundPattern.getConstraint()); + inboundPattern.setAutomatic(patchInboundPattern.isAutomatic()); + } + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + return notifyServiceEntity; + } else { + throw new DSpaceBadRequestException( + "NotifyServiceInboundPatternsReplaceOperation does not support this operation"); + } + } + + @Override + public boolean supports(Object objectToMatch, Operation operation) { + return (objectToMatch instanceof NotifyServiceEntity && + operation.getOp().trim().equalsIgnoreCase(OPERATION_REPLACE) && + operation.getPath().trim().equalsIgnoreCase(OPERATION_PATH)); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceLdnUrlReplaceOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceLdnUrlReplaceOperation.java new file mode 100644 index 000000000000..bad2f280e021 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceLdnUrlReplaceOperation.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.rest.repository.patch.operation.ldn; + +import static java.lang.String.format; + +import java.sql.SQLException; + +import org.dspace.app.ldn.NotifyServiceEntity; +import org.dspace.app.ldn.service.NotifyService; +import org.dspace.app.rest.exception.DSpaceBadRequestException; +import org.dspace.app.rest.exception.UnprocessableEntityException; +import org.dspace.app.rest.model.patch.Operation; +import org.dspace.app.rest.repository.patch.operation.PatchOperation; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Implementation for NotifyService ldnUrl Replace patches. + * + * Example: + * curl -X PATCH http://${dspace.server.url}/api/ldn/ldnservices/<:id-notifyService> -H " + * Content-Type: application/json" -d ' + * [{ + * "op": "replace", + * "path": "/ldnurl", + * "value": "ldnurl value" + * }]' + * + */ +@Component +public class NotifyServiceLdnUrlReplaceOperation extends PatchOperation { + + @Autowired + private NotifyService notifyService; + + private static final String OPERATION_PATH = "/ldnurl"; + + @Override + public NotifyServiceEntity perform(Context context, NotifyServiceEntity notifyServiceEntity, Operation operation) + throws SQLException { + checkOperationValue(operation.getValue()); + + Object ldnUrl = operation.getValue(); + if (ldnUrl == null | !(ldnUrl instanceof String)) { + throw new UnprocessableEntityException("The /ldnurl value must be a string"); + } + + if (notifyService.findByLdnUrl(context,(String) ldnUrl) != null) { + throw new UnprocessableEntityException(format("LDN url already in use %s", + (String) ldnUrl)); + } + + checkModelForExistingValue(notifyServiceEntity); + notifyServiceEntity.setLdnUrl((String) ldnUrl); + notifyService.update(context, notifyServiceEntity); + return notifyServiceEntity; + } + + /** + * Checks whether the ldnurl of notifyServiceEntity has an existing value to replace + * @param notifyServiceEntity Object on which patch is being done + */ + private void checkModelForExistingValue(NotifyServiceEntity notifyServiceEntity) { + if (notifyServiceEntity.getLdnUrl() == null) { + throw new DSpaceBadRequestException("Attempting to replace a non-existent value (ldnurl)."); + } + } + + @Override + public boolean supports(Object objectToMatch, Operation operation) { + return (objectToMatch instanceof NotifyServiceEntity && + operation.getOp().trim().equalsIgnoreCase(OPERATION_REPLACE) && + operation.getPath().trim().toLowerCase().equalsIgnoreCase(OPERATION_PATH)); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceLowerIpReplaceOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceLowerIpReplaceOperation.java new file mode 100644 index 000000000000..be605f94d834 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceLowerIpReplaceOperation.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.rest.repository.patch.operation.ldn; + +import java.sql.SQLException; + +import org.dspace.app.ldn.NotifyServiceEntity; +import org.dspace.app.ldn.service.NotifyService; +import org.dspace.app.rest.exception.DSpaceBadRequestException; +import org.dspace.app.rest.exception.UnprocessableEntityException; +import org.dspace.app.rest.model.patch.Operation; +import org.dspace.app.rest.repository.patch.operation.PatchOperation; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Implementation for NotifyService lowerIp Replace patches. + * + * Example: + * curl -X PATCH http://${dspace.server.url}/api/ldn/ldnservices/<:id-notifyService> -H " + * Content-Type: application/json" -d ' + * [{ + * "op": "replace", + * "path": "/lowerIp", + * "value": "lowerIp value" + * }]' + * + */ +@Component +public class NotifyServiceLowerIpReplaceOperation extends PatchOperation { + + @Autowired + private NotifyService notifyService; + + private static final String OPERATION_PATH = "/lowerip"; + + @Override + public NotifyServiceEntity perform(Context context, NotifyServiceEntity notifyServiceEntity, Operation operation) + throws SQLException { + checkOperationValue(operation.getValue()); + + Object lowerIp = operation.getValue(); + if (lowerIp == null | !(lowerIp instanceof String)) { + throw new UnprocessableEntityException("The /lowerIp value must be a string"); + } + + checkModelForExistingValue(notifyServiceEntity); + notifyServiceEntity.setLowerIp((String) lowerIp); + notifyService.update(context, notifyServiceEntity); + return notifyServiceEntity; + } + + /** + * Checks whether the lowerIp of notifyServiceEntity has an existing value to replace + * @param notifyServiceEntity Object on which patch is being done + */ + private void checkModelForExistingValue(NotifyServiceEntity notifyServiceEntity) { + if (notifyServiceEntity.getLowerIp() == null) { + throw new DSpaceBadRequestException("Attempting to replace a non-existent value (lowerIp)."); + } + } + + @Override + public boolean supports(Object objectToMatch, Operation operation) { + return (objectToMatch instanceof NotifyServiceEntity && + operation.getOp().trim().equalsIgnoreCase(OPERATION_REPLACE) && + operation.getPath().trim().toLowerCase().equalsIgnoreCase(OPERATION_PATH)); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceLowerOrUpperRemoveOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceLowerOrUpperRemoveOperation.java new file mode 100644 index 000000000000..a7425a002796 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceLowerOrUpperRemoveOperation.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.rest.repository.patch.operation.ldn; + +import java.sql.SQLException; + +import org.dspace.app.ldn.NotifyServiceEntity; +import org.dspace.app.rest.exception.UnprocessableEntityException; +import org.dspace.app.rest.model.patch.Operation; +import org.dspace.app.rest.repository.patch.operation.PatchOperation; +import org.dspace.core.Context; +import org.springframework.stereotype.Component; + +/** + * Implementation for NotifyService LowerIp Or UpperIp Remove patches. + * + * Example: + * curl -X PATCH http://${dspace.server.url}/api/ldn/ldnservices/<:id-notifyService> -H " + * Content-Type: application/json" -d ' + * [{ + * "op": "remove", + * "path": "/lowerIp" + * }]' + * + */ +@Component +public class NotifyServiceLowerOrUpperRemoveOperation extends PatchOperation { + + @Override + public NotifyServiceEntity perform(Context context, NotifyServiceEntity notifyServiceEntity, Operation operation) + throws SQLException { + throw new UnprocessableEntityException("/lowerIp or /upperIp are mandatory and can't be removed"); + } + + @Override + public boolean supports(Object objectToMatch, Operation operation) { + return (objectToMatch instanceof NotifyServiceEntity && + operation.getOp().trim().equalsIgnoreCase(OPERATION_REMOVE) && + (operation.getPath().trim().toLowerCase().equalsIgnoreCase("/lowerip") || + operation.getPath().trim().toLowerCase().equalsIgnoreCase("/upperip"))); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceNameOrLdnUrlRemoveOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceNameOrLdnUrlRemoveOperation.java new file mode 100644 index 000000000000..e1ff7b83ef5b --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceNameOrLdnUrlRemoveOperation.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.rest.repository.patch.operation.ldn; + +import java.sql.SQLException; + +import org.dspace.app.ldn.NotifyServiceEntity; +import org.dspace.app.rest.exception.UnprocessableEntityException; +import org.dspace.app.rest.model.patch.Operation; +import org.dspace.app.rest.repository.patch.operation.PatchOperation; +import org.dspace.core.Context; +import org.springframework.stereotype.Component; + +/** + * Implementation for NotifyService Name Or LdnUrl Remove patches. + * + * Example: + * curl -X PATCH http://${dspace.server.url}/api/ldn/ldnservices/<:id-notifyService> -H " + * Content-Type: application/json" -d ' + * [{ + * "op": "remove", + * "path": "/name" + * }]' + * + */ +@Component +public class NotifyServiceNameOrLdnUrlRemoveOperation extends PatchOperation { + + @Override + public NotifyServiceEntity perform(Context context, NotifyServiceEntity notifyServiceEntity, Operation operation) + throws SQLException { + throw new UnprocessableEntityException("/name or /ldnurl are mandatory and can't be removed"); + } + + @Override + public boolean supports(Object objectToMatch, Operation operation) { + return (objectToMatch instanceof NotifyServiceEntity && + operation.getOp().trim().equalsIgnoreCase(OPERATION_REMOVE) && + (operation.getPath().trim().toLowerCase().equalsIgnoreCase("/name") || + operation.getPath().trim().toLowerCase().equalsIgnoreCase("/ldnurl"))); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceNameReplaceOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceNameReplaceOperation.java new file mode 100644 index 000000000000..48db23544f63 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceNameReplaceOperation.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.rest.repository.patch.operation.ldn; + +import java.sql.SQLException; + +import org.dspace.app.ldn.NotifyServiceEntity; +import org.dspace.app.ldn.service.NotifyService; +import org.dspace.app.rest.exception.DSpaceBadRequestException; +import org.dspace.app.rest.exception.UnprocessableEntityException; +import org.dspace.app.rest.model.patch.Operation; +import org.dspace.app.rest.repository.patch.operation.PatchOperation; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Implementation for NotifyService Name Replace patches. + * + * Example: + * curl -X PATCH http://${dspace.server.url}/api/ldn/ldnservices/<:id-notifyService> -H " + * Content-Type: application/json" -d ' + * [{ + * "op": "replace", + * "path": "/name", + * "value": "name value" + * }]' + * + */ +@Component +public class NotifyServiceNameReplaceOperation extends PatchOperation { + + @Autowired + private NotifyService notifyService; + + private static final String OPERATION_PATH = "/name"; + + @Override + public NotifyServiceEntity perform(Context context, NotifyServiceEntity notifyServiceEntity, Operation operation) + throws SQLException { + checkOperationValue(operation.getValue()); + + Object name = operation.getValue(); + if (name == null | !(name instanceof String)) { + throw new UnprocessableEntityException("The /name value must be a string"); + } + + checkModelForExistingValue(notifyServiceEntity); + notifyServiceEntity.setName((String) name); + notifyService.update(context, notifyServiceEntity); + return notifyServiceEntity; + } + + /** + * Checks whether the name of notifyServiceEntity has an existing value to replace + * @param notifyServiceEntity Object on which patch is being done + */ + private void checkModelForExistingValue(NotifyServiceEntity notifyServiceEntity) { + if (notifyServiceEntity.getName() == null) { + throw new DSpaceBadRequestException("Attempting to replace a non-existent value (name)."); + } + } + + @Override + public boolean supports(Object objectToMatch, Operation operation) { + return (objectToMatch instanceof NotifyServiceEntity && + operation.getOp().trim().equalsIgnoreCase(OPERATION_REPLACE) && + operation.getPath().trim().toLowerCase().equalsIgnoreCase(OPERATION_PATH)); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServicePatchUtils.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServicePatchUtils.java new file mode 100644 index 000000000000..5735a7bd5f73 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServicePatchUtils.java @@ -0,0 +1,109 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.rest.repository.patch.operation.ldn; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.lang3.StringUtils; +import org.dspace.app.ldn.NotifyServiceInboundPattern; +import org.dspace.app.rest.exception.DSpaceBadRequestException; +import org.dspace.app.rest.model.patch.JsonValueEvaluator; +import org.dspace.app.rest.model.patch.Operation; +import org.springframework.stereotype.Component; + +/** + * Util class for shared methods between the NotifyServiceEntity Operations + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + */ +@Component +public final class NotifyServicePatchUtils { + + public static final String NOTIFY_SERVICE_INBOUND_PATTERNS = "notifyServiceInboundPatterns"; + + private ObjectMapper objectMapper = new ObjectMapper(); + + private NotifyServicePatchUtils() { + } + + /** + * Extract NotifyServiceInboundPattern from Operation by parsing the json + * and mapping it to a NotifyServiceInboundPattern + * + * @param operation Operation whose value is being parsed + * @return NotifyServiceInboundPattern extracted from json in operation value + */ + protected NotifyServiceInboundPattern extractNotifyServiceInboundPatternFromOperation(Operation operation) { + NotifyServiceInboundPattern inboundPattern = null; + try { + if (operation.getValue() != null) { + if (operation.getValue() instanceof JsonValueEvaluator) { + inboundPattern = objectMapper.readValue(((JsonValueEvaluator) operation.getValue()) + .getValueNode().toString(), NotifyServiceInboundPattern.class); + } else if (operation.getValue() instanceof String) { + inboundPattern = objectMapper.readValue((String) operation.getValue(), + NotifyServiceInboundPattern.class); + } + } + } catch (IOException e) { + throw new DSpaceBadRequestException("IOException: trying to map json from operation.value" + + " to NotifyServiceInboundPattern class.", e); + } + if (inboundPattern == null) { + throw new DSpaceBadRequestException("Could not extract NotifyServiceInboundPattern Object from Operation"); + } + return inboundPattern; + } + + /** + * Extract list of NotifyServiceInboundPattern from Operation by parsing the json + * and mapping it to a list of NotifyServiceInboundPattern + * + * @param operation Operation whose value is being parsed + * @return list of NotifyServiceInboundPattern extracted from json in operation value + */ + protected List extractNotifyServiceInboundPatternsFromOperation(Operation operation) { + List inboundPatterns = null; + try { + if (operation.getValue() != null) { + if (operation.getValue() instanceof String) { + inboundPatterns = objectMapper.readValue((String) operation.getValue(), + objectMapper.getTypeFactory().constructCollectionType(ArrayList.class, + NotifyServiceInboundPattern.class)); + } + } + } catch (IOException e) { + throw new DSpaceBadRequestException("IOException: trying to map json from operation.value" + + " to List of NotifyServiceInboundPattern class.", e); + } + if (inboundPatterns == null) { + throw new DSpaceBadRequestException("Could not extract list of NotifyServiceInboundPattern " + + "Objects from Operation"); + } + return inboundPatterns; + } + + protected int extractIndexFromOperation(Operation operation) { + String number = ""; + Pattern pattern = Pattern.compile("\\[(\\d+)\\]"); // Pattern to match [i] + Matcher matcher = pattern.matcher(operation.getPath()); + if (matcher.find()) { + number = matcher.group(1); + } + + if (StringUtils.isEmpty(number)) { + throw new DSpaceBadRequestException("path doesn't contain index"); + } + + return Integer.parseInt(number); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceScoreAddOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceScoreAddOperation.java new file mode 100644 index 000000000000..01c5dd3a66c7 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceScoreAddOperation.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.app.rest.repository.patch.operation.ldn; + +import static java.lang.String.format; + +import java.math.BigDecimal; +import java.sql.SQLException; + +import org.dspace.app.ldn.NotifyServiceEntity; +import org.dspace.app.ldn.service.NotifyService; +import org.dspace.app.rest.exception.DSpaceBadRequestException; +import org.dspace.app.rest.exception.UnprocessableEntityException; +import org.dspace.app.rest.model.patch.Operation; +import org.dspace.app.rest.repository.patch.operation.PatchOperation; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Implementation for NotifyService Score Add patches. + * + * Example: + * curl -X PATCH http://${dspace.server.url}/api/ldn/ldnservices/<:id-notifyService> -H " + * Content-Type: application/json" -d ' + * [{ + * "op": "add", + * "path": "/score", + * "value": "score value" + * }]' + * + */ +@Component +public class NotifyServiceScoreAddOperation extends PatchOperation { + + @Autowired + private NotifyService notifyService; + + private static final String OPERATION_PATH = "/score"; + + @Override + public NotifyServiceEntity perform(Context context, NotifyServiceEntity notifyServiceEntity, Operation operation) + throws SQLException { + checkNonExistingScoreValue(notifyServiceEntity); + checkOperationValue(operation.getValue()); + Object score = operation.getValue(); + if (score == null) { + throw new DSpaceBadRequestException("The /score value must be a decimal number"); + } + + BigDecimal scoreBigDecimal = null; + try { + scoreBigDecimal = new BigDecimal(score.toString()); + } catch (Exception e) { + throw new DSpaceBadRequestException(format("Score out of range [0, 1] %s", score.toString())); + } + if (scoreBigDecimal.compareTo(java.math.BigDecimal.ZERO) == -1 || + scoreBigDecimal.compareTo(java.math.BigDecimal.ONE) == 1) { + throw new UnprocessableEntityException(format("Score out of range [0, 1] %s", + scoreBigDecimal.setScale(4).toPlainString())); + } + notifyServiceEntity.setScore(scoreBigDecimal); + notifyService.update(context, notifyServiceEntity); + return notifyServiceEntity; + } + + /** + * Throws PatchBadRequestException if a value is already set in the /score path. + * + * @param notifyServiceEntity the notifyServiceEntity to update + + */ + void checkNonExistingScoreValue(NotifyServiceEntity notifyServiceEntity) { + if (notifyServiceEntity.getScore() != null) { + throw new DSpaceBadRequestException("Attempting to add a value to an already existing path."); + } + } + + @Override + public boolean supports(Object objectToMatch, Operation operation) { + return (objectToMatch instanceof NotifyServiceEntity && + operation.getOp().trim().equalsIgnoreCase(OPERATION_ADD) && + operation.getPath().trim().toLowerCase().equalsIgnoreCase(OPERATION_PATH)); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceScoreRemoveOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceScoreRemoveOperation.java new file mode 100644 index 000000000000..e26d888e9fc7 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceScoreRemoveOperation.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.rest.repository.patch.operation.ldn; + +import java.sql.SQLException; + +import org.dspace.app.ldn.NotifyServiceEntity; +import org.dspace.app.ldn.service.NotifyService; +import org.dspace.app.rest.model.patch.Operation; +import org.dspace.app.rest.repository.patch.operation.PatchOperation; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Implementation for NotifyService Score Remove patches. + * + * Example: + * curl -X PATCH http://${dspace.server.url}/api/ldn/ldnservices/<:id-notifyService> -H " + * Content-Type: application/json" -d ' + * [{ + * "op": "remove", + * "path": "/score" + * }]' + * + */ +@Component +public class NotifyServiceScoreRemoveOperation extends PatchOperation { + + @Autowired + private NotifyService notifyService; + + private static final String OPERATION_PATH = "/score"; + + @Override + public NotifyServiceEntity perform(Context context, NotifyServiceEntity notifyServiceEntity, Operation operation) + throws SQLException { + notifyServiceEntity.setScore(null); + notifyService.update(context, notifyServiceEntity); + return notifyServiceEntity; + } + + @Override + public boolean supports(Object objectToMatch, Operation operation) { + return (objectToMatch instanceof NotifyServiceEntity && + operation.getOp().trim().equalsIgnoreCase(OPERATION_REMOVE) && + operation.getPath().trim().toLowerCase().equalsIgnoreCase(OPERATION_PATH)); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceScoreReplaceOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceScoreReplaceOperation.java new file mode 100644 index 000000000000..95824c706364 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceScoreReplaceOperation.java @@ -0,0 +1,88 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.rest.repository.patch.operation.ldn; + +import static java.lang.String.format; + +import java.math.BigDecimal; +import java.sql.SQLException; + +import org.dspace.app.ldn.NotifyServiceEntity; +import org.dspace.app.ldn.service.NotifyService; +import org.dspace.app.rest.exception.DSpaceBadRequestException; +import org.dspace.app.rest.exception.UnprocessableEntityException; +import org.dspace.app.rest.model.patch.Operation; +import org.dspace.app.rest.repository.patch.operation.PatchOperation; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Implementation for NotifyService Score Replace patches. + * + * Example: + * curl -X PATCH http://${dspace.server.url}/api/ldn/ldnservices/<:id-notifyService> -H " + * Content-Type: application/json" -d ' + * [{ + * "op": "replace", + * "path": "/score", + * "value": "score value" + * }]' + * + */ +@Component +public class NotifyServiceScoreReplaceOperation extends PatchOperation { + + @Autowired + private NotifyService notifyService; + + private static final String OPERATION_PATH = "/score"; + + @Override + public NotifyServiceEntity perform(Context context, NotifyServiceEntity notifyServiceEntity, Operation operation) + throws SQLException { + checkOperationValue(operation.getValue()); + + Object score = operation.getValue(); + if (score == null) { + throw new DSpaceBadRequestException("The /score value must be a decimal number"); + } + BigDecimal scoreBigDecimal = null; + try { + scoreBigDecimal = new BigDecimal(score.toString()); + } catch (Exception e) { + throw new DSpaceBadRequestException(format("Score out of range [0, 1] %s", score)); + } + if (scoreBigDecimal.compareTo(java.math.BigDecimal.ZERO) == -1 || + scoreBigDecimal.compareTo(java.math.BigDecimal.ONE) == 1) { + throw new UnprocessableEntityException(format("Score out of range [0, 1] %s", score)); + } + + checkModelForExistingValue(notifyServiceEntity); + notifyServiceEntity.setScore(new BigDecimal((String)score)); + notifyService.update(context, notifyServiceEntity); + return notifyServiceEntity; + } + + /** + * Checks whether the description of notifyServiceEntity has an existing value to replace + * @param notifyServiceEntity Object on which patch is being done + */ + private void checkModelForExistingValue(NotifyServiceEntity notifyServiceEntity) { + if (notifyServiceEntity.getDescription() == null) { + throw new DSpaceBadRequestException("Attempting to replace a non-existent value (description)."); + } + } + + @Override + public boolean supports(Object objectToMatch, Operation operation) { + return (objectToMatch instanceof NotifyServiceEntity && + operation.getOp().trim().equalsIgnoreCase(OPERATION_REPLACE) && + operation.getPath().trim().toLowerCase().equalsIgnoreCase(OPERATION_PATH)); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceUpperIpReplaceOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceUpperIpReplaceOperation.java new file mode 100644 index 000000000000..7ee362cf97d0 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceUpperIpReplaceOperation.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.rest.repository.patch.operation.ldn; + +import java.sql.SQLException; + +import org.dspace.app.ldn.NotifyServiceEntity; +import org.dspace.app.ldn.service.NotifyService; +import org.dspace.app.rest.exception.DSpaceBadRequestException; +import org.dspace.app.rest.exception.UnprocessableEntityException; +import org.dspace.app.rest.model.patch.Operation; +import org.dspace.app.rest.repository.patch.operation.PatchOperation; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Implementation for NotifyService upperIp Replace patches. + * + * Example: + * curl -X PATCH http://${dspace.server.url}/api/ldn/ldnservices/<:id-notifyService> -H " + * Content-Type: application/json" -d ' + * [{ + * "op": "replace", + * "path": "/upperIp", + * "value": "upperIp value" + * }]' + * + */ +@Component +public class NotifyServiceUpperIpReplaceOperation extends PatchOperation { + + @Autowired + private NotifyService notifyService; + + private static final String OPERATION_PATH = "/upperip"; + + @Override + public NotifyServiceEntity perform(Context context, NotifyServiceEntity notifyServiceEntity, Operation operation) + throws SQLException { + checkOperationValue(operation.getValue()); + + Object upperIp = operation.getValue(); + if (upperIp == null | !(upperIp instanceof String)) { + throw new UnprocessableEntityException("The /upperIp value must be a string"); + } + + checkModelForExistingValue(notifyServiceEntity); + notifyServiceEntity.setUpperIp((String) upperIp); + notifyService.update(context, notifyServiceEntity); + return notifyServiceEntity; + } + + /** + * Checks whether the upperIp of notifyServiceEntity has an existing value to replace + * @param notifyServiceEntity Object on which patch is being done + */ + private void checkModelForExistingValue(NotifyServiceEntity notifyServiceEntity) { + if (notifyServiceEntity.getUpperIp() == null) { + throw new DSpaceBadRequestException("Attempting to replace a non-existent value (upperIp)."); + } + } + + @Override + public boolean supports(Object objectToMatch, Operation operation) { + return (objectToMatch instanceof NotifyServiceEntity && + operation.getOp().trim().equalsIgnoreCase(OPERATION_REPLACE) && + operation.getPath().trim().toLowerCase().equalsIgnoreCase(OPERATION_PATH)); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceUrlAddOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceUrlAddOperation.java new file mode 100644 index 000000000000..f09740734393 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceUrlAddOperation.java @@ -0,0 +1,77 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.rest.repository.patch.operation.ldn; + +import java.sql.SQLException; + +import org.dspace.app.ldn.NotifyServiceEntity; +import org.dspace.app.ldn.service.NotifyService; +import org.dspace.app.rest.exception.DSpaceBadRequestException; +import org.dspace.app.rest.exception.UnprocessableEntityException; +import org.dspace.app.rest.model.patch.Operation; +import org.dspace.app.rest.repository.patch.operation.PatchOperation; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Implementation for NotifyService URL Add patches. + * + * Example: + * curl -X PATCH http://${dspace.server.url}/api/ldn/ldnservices/<:id-notifyService> -H " + * Content-Type: application/json" -d ' + * [{ + * "op": "add", + * "path": "/url", + * "value": "url value" + * }]' + * + */ +@Component +public class NotifyServiceUrlAddOperation extends PatchOperation { + + @Autowired + private NotifyService notifyService; + + private static final String OPERATION_PATH = "/url"; + + @Override + public NotifyServiceEntity perform(Context context, NotifyServiceEntity notifyServiceEntity, Operation operation) + throws SQLException { + checkOperationValue(operation.getValue()); + + Object url = operation.getValue(); + if (url == null | !(url instanceof String)) { + throw new UnprocessableEntityException("The /url value must be a string"); + } + + checkNonExistingUrlValue(notifyServiceEntity); + notifyServiceEntity.setUrl((String) url); + notifyService.update(context, notifyServiceEntity); + return notifyServiceEntity; + } + + /** + * Throws PatchBadRequestException if a value is already set in the /url path. + * + * @param notifyServiceEntity the notifyServiceEntity to update + + */ + void checkNonExistingUrlValue(NotifyServiceEntity notifyServiceEntity) { + if (notifyServiceEntity.getUrl() != null) { + throw new DSpaceBadRequestException("Attempting to add a value to an already existing path."); + } + } + + @Override + public boolean supports(Object objectToMatch, Operation operation) { + return (objectToMatch instanceof NotifyServiceEntity && + operation.getOp().trim().equalsIgnoreCase(OPERATION_ADD) && + operation.getPath().trim().toLowerCase().equalsIgnoreCase(OPERATION_PATH)); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceUrlRemoveOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceUrlRemoveOperation.java new file mode 100644 index 000000000000..c2e6fa05bedc --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceUrlRemoveOperation.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.rest.repository.patch.operation.ldn; + +import java.sql.SQLException; + +import org.dspace.app.ldn.NotifyServiceEntity; +import org.dspace.app.ldn.service.NotifyService; +import org.dspace.app.rest.model.patch.Operation; +import org.dspace.app.rest.repository.patch.operation.PatchOperation; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Implementation for NotifyService URL Remove patches. + * + * Example: + * curl -X PATCH http://${dspace.server.url}/api/ldn/ldnservices/<:id-notifyService> -H " + * Content-Type: application/json" -d ' + * [{ + * "op": "remove", + * "path": "/url" + * }]' + * + */ +@Component +public class NotifyServiceUrlRemoveOperation extends PatchOperation { + + @Autowired + private NotifyService notifyService; + + private static final String OPERATION_PATH = "/url"; + + @Override + public NotifyServiceEntity perform(Context context, NotifyServiceEntity notifyServiceEntity, Operation operation) + throws SQLException { + notifyServiceEntity.setUrl(null); + notifyService.update(context, notifyServiceEntity); + return notifyServiceEntity; + } + + @Override + public boolean supports(Object objectToMatch, Operation operation) { + return (objectToMatch instanceof NotifyServiceEntity && + operation.getOp().trim().equalsIgnoreCase(OPERATION_REMOVE) && + operation.getPath().trim().toLowerCase().equalsIgnoreCase(OPERATION_PATH)); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceUrlReplaceOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceUrlReplaceOperation.java new file mode 100644 index 000000000000..53a315d079be --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ldn/NotifyServiceUrlReplaceOperation.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.rest.repository.patch.operation.ldn; + +import java.sql.SQLException; + +import org.dspace.app.ldn.NotifyServiceEntity; +import org.dspace.app.ldn.service.NotifyService; +import org.dspace.app.rest.exception.DSpaceBadRequestException; +import org.dspace.app.rest.exception.UnprocessableEntityException; +import org.dspace.app.rest.model.patch.Operation; +import org.dspace.app.rest.repository.patch.operation.PatchOperation; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Implementation for NotifyService URL Replace patches. + * + * Example: + * curl -X PATCH http://${dspace.server.url}/api/ldn/ldnservices/<:id-notifyService> -H " + * Content-Type: application/json" -d ' + * [{ + * "op": "replace", + * "path": "/url", + * "value": "url value" + * }]' + * + */ +@Component +public class NotifyServiceUrlReplaceOperation extends PatchOperation { + + @Autowired + private NotifyService notifyService; + + private static final String OPERATION_PATH = "/url"; + + @Override + public NotifyServiceEntity perform(Context context, NotifyServiceEntity notifyServiceEntity, Operation operation) + throws SQLException { + checkOperationValue(operation.getValue()); + + Object url = operation.getValue(); + if (url == null | !(url instanceof String)) { + throw new UnprocessableEntityException("The /url value must be a string"); + } + + checkModelForExistingValue(notifyServiceEntity); + notifyServiceEntity.setUrl((String) url); + notifyService.update(context, notifyServiceEntity); + return notifyServiceEntity; + } + + /** + * Checks whether the url of notifyServiceEntity has an existing value to replace + * @param notifyServiceEntity Object on which patch is being done + */ + private void checkModelForExistingValue(NotifyServiceEntity notifyServiceEntity) { + if (notifyServiceEntity.getUrl() == null) { + throw new DSpaceBadRequestException("Attempting to replace a non-existent value (url)."); + } + } + + @Override + public boolean supports(Object objectToMatch, Operation operation) { + return (objectToMatch instanceof NotifyServiceEntity && + operation.getOp().trim().equalsIgnoreCase(OPERATION_REPLACE) && + operation.getPath().trim().toLowerCase().equalsIgnoreCase(OPERATION_PATH)); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/scripts/handler/impl/RestDSpaceRunnableHandler.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/scripts/handler/impl/RestDSpaceRunnableHandler.java index 596ab4429093..ee67baa8ab38 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/scripts/handler/impl/RestDSpaceRunnableHandler.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/scripts/handler/impl/RestDSpaceRunnableHandler.java @@ -130,7 +130,7 @@ public void handleCompletion() { @Override public void handleException(Exception e) { - handleException(null, e); + handleException(e.getMessage(), e); } @Override diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/AdminRestPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/AdminRestPermissionEvaluatorPlugin.java index 338eed4a7340..1b251847ba92 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/AdminRestPermissionEvaluatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/AdminRestPermissionEvaluatorPlugin.java @@ -10,6 +10,8 @@ import java.io.Serializable; import java.sql.SQLException; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.app.rest.utils.ContextUtil; import org.dspace.authorize.service.AuthorizeService; import org.dspace.core.Context; @@ -17,8 +19,6 @@ import org.dspace.eperson.service.EPersonService; import org.dspace.services.RequestService; import org.dspace.services.model.Request; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; @@ -34,7 +34,7 @@ @Order(value = Ordered.HIGHEST_PRECEDENCE) public class AdminRestPermissionEvaluatorPlugin extends RestObjectPermissionEvaluatorPlugin { - private static final Logger log = LoggerFactory.getLogger(RestObjectPermissionEvaluatorPlugin.class); + private static final Logger log = LogManager.getLogger(); @Autowired private AuthorizeService authorizeService; @@ -69,7 +69,7 @@ public boolean hasDSpacePermission(Authentication authentication, Serializable t } } catch (SQLException e) { - log.error(e.getMessage(), e); + log.error(e::getMessage, e); } return false; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/AnonymousAdditionalAuthorizationFilter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/AnonymousAdditionalAuthorizationFilter.java index 7f02264b92f3..235ef1a37d3e 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/AnonymousAdditionalAuthorizationFilter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/AnonymousAdditionalAuthorizationFilter.java @@ -10,11 +10,11 @@ import java.io.IOException; import java.sql.SQLException; import java.util.List; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.app.rest.utils.ContextUtil; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/AuthorizeServicePermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/AuthorizeServicePermissionEvaluatorPlugin.java index f0b44187c596..3fdd5158ed1b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/AuthorizeServicePermissionEvaluatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/AuthorizeServicePermissionEvaluatorPlugin.java @@ -11,6 +11,8 @@ import java.sql.SQLException; import java.util.UUID; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.app.rest.utils.ContextUtil; import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.DSpaceObject; @@ -24,21 +26,19 @@ import org.dspace.services.RequestService; import org.dspace.services.model.Request; import org.dspace.util.UUIDUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; import org.springframework.stereotype.Component; /** - * DSpaceObjectPermissionEvaluatorPlugin will check persmissions based on the DSpace {@link AuthorizeService}. + * DSpaceObjectPermissionEvaluatorPlugin will check permissions based on the DSpace {@link AuthorizeService}. * This service will validate if the authenticated user is allowed to perform an action on the given DSpace Object * based on the resource policies attached to that DSpace object. */ @Component public class AuthorizeServicePermissionEvaluatorPlugin extends RestObjectPermissionEvaluatorPlugin { - private static final Logger log = LoggerFactory.getLogger(AuthorizeServicePermissionEvaluatorPlugin.class); + private static final Logger log = LogManager.getLogger(); @Autowired private AuthorizeService authorizeService; @@ -106,7 +106,7 @@ public boolean hasDSpacePermission(Authentication authentication, Serializable t } } catch (SQLException e) { - log.error(e.getMessage(), e); + log.error(e::getMessage, e); } return false; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/BitstreamMetadataReadPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/BitstreamMetadataReadPermissionEvaluatorPlugin.java index b4b08c668b0a..5a596525e4ce 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/BitstreamMetadataReadPermissionEvaluatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/BitstreamMetadataReadPermissionEvaluatorPlugin.java @@ -11,6 +11,8 @@ import java.sql.SQLException; import java.util.UUID; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.app.rest.repository.BitstreamRestRepository; import org.dspace.app.rest.utils.ContextUtil; import org.dspace.app.rest.utils.DSpaceObjectUtils; @@ -24,8 +26,6 @@ import org.dspace.core.Context; import org.dspace.services.RequestService; import org.dspace.services.model.Request; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; import org.springframework.stereotype.Component; @@ -39,7 +39,7 @@ @Component public class BitstreamMetadataReadPermissionEvaluatorPlugin extends RestObjectPermissionEvaluatorPlugin { - private static final Logger log = LoggerFactory.getLogger(BitstreamMetadataReadPermissionEvaluatorPlugin.class); + private static final Logger log = LogManager.getLogger(); @Autowired private RequestService requestService; @@ -66,7 +66,7 @@ public boolean hasPermission(Authentication authentication, Serializable targetI return this.metadataReadPermissionOnBitstream(context, (Bitstream) dso); } } catch (SQLException e) { - log.error(e.getMessage(), e); + log.error(e::getMessage, e); } } return false; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ClaimedTaskRestPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ClaimedTaskRestPermissionEvaluatorPlugin.java index 1741d94ea834..80019b55fdd0 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ClaimedTaskRestPermissionEvaluatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ClaimedTaskRestPermissionEvaluatorPlugin.java @@ -11,6 +11,8 @@ import java.sql.SQLException; import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.app.rest.model.ClaimedTaskRest; import org.dspace.app.rest.utils.ContextUtil; import org.dspace.core.Context; @@ -20,8 +22,6 @@ import org.dspace.services.model.Request; import org.dspace.xmlworkflow.storedcomponents.ClaimedTask; import org.dspace.xmlworkflow.storedcomponents.service.ClaimedTaskService; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; import org.springframework.stereotype.Component; @@ -29,13 +29,13 @@ /** * An authenticated user is allowed to interact with a claimed task only if they own it * claim. - * + * * @author Andrea Bollini (andrea.bollini at 4science.it) */ @Component public class ClaimedTaskRestPermissionEvaluatorPlugin extends RestObjectPermissionEvaluatorPlugin { - private static final Logger log = LoggerFactory.getLogger(ClaimedTaskRestPermissionEvaluatorPlugin.class); + private static final Logger log = LogManager.getLogger(); @Autowired private RequestService requestService; @@ -74,7 +74,7 @@ public boolean hasDSpacePermission(Authentication authentication, Serializable t } } catch (SQLException e) { - log.error(e.getMessage(), e); + log.error(e::getMessage, e); } return false; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/CustomLogoutHandler.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/CustomLogoutHandler.java index b3f4a00d379e..d94e7c82ccbd 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/CustomLogoutHandler.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/CustomLogoutHandler.java @@ -7,13 +7,12 @@ */ package org.dspace.app.rest.security; -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.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.app.rest.utils.ContextUtil; import org.dspace.core.Context; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.logout.LogoutHandler; @@ -28,7 +27,7 @@ @Component public class CustomLogoutHandler implements LogoutHandler { - private static final Logger log = LoggerFactory.getLogger(CustomLogoutHandler.class); + private static final Logger log = LogManager.getLogger(); @Autowired private RestAuthenticationService restAuthenticationService; @@ -40,6 +39,7 @@ public class CustomLogoutHandler implements LogoutHandler { * @param httpServletResponse * @param authentication */ + @Override public void logout(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) { try { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/DSpace401AuthenticationEntryPoint.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/DSpace401AuthenticationEntryPoint.java index b70931336e7a..958a920872db 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/DSpace401AuthenticationEntryPoint.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/DSpace401AuthenticationEntryPoint.java @@ -8,10 +8,10 @@ package org.dspace.app.rest.security; import java.io.IOException; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.AuthenticationEntryPoint; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/DSpaceCsrfAuthenticationStrategy.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/DSpaceCsrfAuthenticationStrategy.java index b08ddab169c3..84442b455823 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/DSpaceCsrfAuthenticationStrategy.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/DSpaceCsrfAuthenticationStrategy.java @@ -7,14 +7,19 @@ */ package org.dspace.app.rest.security; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.session.SessionAuthenticationException; import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy; +import org.springframework.security.web.csrf.CsrfAuthenticationStrategy; import org.springframework.security.web.csrf.CsrfToken; import org.springframework.security.web.csrf.CsrfTokenRepository; +import org.springframework.security.web.csrf.CsrfTokenRequestHandler; +import org.springframework.security.web.csrf.DeferredCsrfToken; +import org.springframework.security.web.csrf.XorCsrfTokenRequestAttributeHandler; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -25,29 +30,41 @@ * successfully or unsuccessfully). This ensures that the Token is not changed on every request (since we are stateless * every request creates a new Authentication object). *

    - * Based on Spring Security's CsrfAuthenticationStrategy: - * https://github.com/spring-projects/spring-security/blob/5.2.x/web/src/main/java/org/springframework/security/web/csrf/CsrfAuthenticationStrategy.java + * This is essentially a customization of Spring Security's CsrfAuthenticationStrategy: + * https://github.com/spring-projects/spring-security/blob/6.2.x/web/src/main/java/org/springframework/security/web/csrf/CsrfAuthenticationStrategy.java */ public class DSpaceCsrfAuthenticationStrategy implements SessionAuthenticationStrategy { - private final CsrfTokenRepository csrfTokenRepository; + private final Log logger = LogFactory.getLog(getClass()); + + private final CsrfTokenRepository tokenRepository; + + private CsrfTokenRequestHandler requestHandler = new XorCsrfTokenRequestAttributeHandler(); /** * Creates a new instance - * @param csrfTokenRepository the {@link CsrfTokenRepository} to use + * @param tokenRepository the {@link CsrfTokenRepository} to use + */ + public DSpaceCsrfAuthenticationStrategy(CsrfTokenRepository tokenRepository) { + Assert.notNull(tokenRepository, "tokenRepository cannot be null"); + this.tokenRepository = tokenRepository; + } + + /** + * Method is copied from {@link CsrfAuthenticationStrategy#setRequestHandler(CsrfTokenRequestHandler)} */ - public DSpaceCsrfAuthenticationStrategy(CsrfTokenRepository csrfTokenRepository) { - Assert.notNull(csrfTokenRepository, "csrfTokenRepository cannot be null"); - this.csrfTokenRepository = csrfTokenRepository; + public void setRequestHandler(CsrfTokenRequestHandler requestHandler) { + Assert.notNull(requestHandler, "requestHandler cannot be null"); + this.requestHandler = requestHandler; } /** * This method is triggered anytime a new Authentication occurs. As DSpace uses Stateless authentication, * this method is triggered on _every request_ after an initial login occurs. This is because the Spring Security - * Authentication object is recreated on every request. + * 'Authentication' object is recreated on every request. *

    * Therefore, for DSpace, we've customized this method to ensure a new CSRF Token is NOT generated each time a new - * Authentication object is created -- doing so causes the CSRF Token to change with every request. Instead, we + * Authentication object is created -- as doing so causes the CSRF Token to change with every request. Instead, we * check to see if the client also passed a CSRF token via a querystring parameter (i.e. "_csrf"). If so, this means * the client has sent the token in a less secure manner & it must then be regenerated. *

    @@ -58,8 +75,10 @@ public void onAuthentication(Authentication authentication, HttpServletRequest request, HttpServletResponse response) throws SessionAuthenticationException { + // Check if token returned in server-side cookie - CsrfToken token = this.csrfTokenRepository.loadToken(request); + CsrfToken token = this.tokenRepository.loadToken(request); + // For DSpace, this will only be null if we are forcing CSRF token regeneration (e.g. on initial login) boolean containsToken = token != null; @@ -70,18 +89,31 @@ public void onAuthentication(Authentication authentication, // If token exists was sent in a parameter, then we need to reset our token // (as sending token in a param is insecure) if (containsParameter) { - // Note: We first set the token to null & then set a new one. This results in 2 cookies sent, - // the first being empty and the second having the new token. - // This behavior is borrowed from Spring Security's CsrfAuthenticationStrategy, see - // https://github.com/spring-projects/spring-security/blob/5.4.x/web/src/main/java/org/springframework/security/web/csrf/CsrfAuthenticationStrategy.java - this.csrfTokenRepository.saveToken(null, request, response); - CsrfToken newToken = this.csrfTokenRepository.generateToken(request); - this.csrfTokenRepository.saveToken(newToken, request, response); - - request.setAttribute(CsrfToken.class.getName(), newToken); - request.setAttribute(newToken.getParameterName(), newToken); + resetCSRFToken(request, response); } } } + /** + * A custom utility method to force Spring Security to reset the CSRF token. This is used by DSpace to reset + * the token whenever the CSRF token is passed insecurely (as a request param, see onAuthentication() above) + * or on logout (see JWTTokenRestAuthenticationServiceImpl) + * @param request current HTTP request + * @param response current HTTP response + * @see org.dspace.app.rest.security.jwt.JWTTokenRestAuthenticationServiceImpl + */ + public void resetCSRFToken(HttpServletRequest request, HttpServletResponse response) { + // Note: We first set the token to null & then set a new one. This results in 2 cookies sent, + // the first being empty and the second having the new token. + // This behavior is borrowed from Spring Security's CsrfAuthenticationStrategy, see + // https://github.com/spring-projects/spring-security/blob/6.2.x/web/src/main/java/org/springframework/security/web/csrf/CsrfAuthenticationStrategy.java + this.tokenRepository.saveToken(null, request, response); + DeferredCsrfToken deferredCsrfToken = this.tokenRepository.loadDeferredToken(request, response); + this.requestHandler.handle(request, response, deferredCsrfToken::get); + // This may look odd, but reading the deferred CSRF token will cause Spring Security to send it back + // in the next request. This ensures our new token is sent back immediately (instead of in a later request) + deferredCsrfToken.get(); + this.logger.debug("Replaced CSRF Token"); + } + } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/DSpaceCsrfTokenRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/DSpaceCsrfTokenRepository.java index 2079727ac889..0bcd2a55237d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/DSpaceCsrfTokenRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/DSpaceCsrfTokenRepository.java @@ -8,12 +8,13 @@ package org.dspace.app.rest.security; import java.util.UUID; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.ws.rs.core.HttpHeaders; +import java.util.function.Consumer; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.springframework.http.ResponseCookie; +import org.springframework.security.web.csrf.CookieCsrfTokenRepository; import org.springframework.security.web.csrf.CsrfToken; import org.springframework.security.web.csrf.CsrfTokenRepository; import org.springframework.security.web.csrf.DefaultCsrfToken; @@ -25,9 +26,10 @@ * This is a custom Spring Security CsrfTokenRepository which supports *cross-domain* CSRF protection (allowing the * client and backend to be on different domains). It's inspired by https://stackoverflow.com/a/33175322 *

    - * This also borrows heavily from Spring Security's CookieCsrfTokenRepository: - * https://github.com/spring-projects/spring-security/blob/5.2.x/web/src/main/java/org/springframework/security/web/csrf/CookieCsrfTokenRepository.java - * + * This is essentially a customization of Spring Security's CookieCsrfTokenRepository: + * https://github.com/spring-projects/spring-security/blob/6.2.x/web/src/main/java/org/springframework/security/web/csrf/CookieCsrfTokenRepository.java + * However, as that class is "final" we aannot override it directly. + *

    * How it works: * * 1. Backend generates XSRF token & stores in a *server-side* cookie named DSPACE-XSRF-COOKIE. By default, this cookie @@ -53,11 +55,19 @@ public class DSpaceCsrfTokenRepository implements CsrfTokenRepository { // This cookie name is changed from the default "XSRF-TOKEN" to ensure it is uniquely named and doesn't conflict // with any other XSRF-TOKEN cookies (e.g. in Angular UI, the XSRF-TOKEN cookie is a *client-side* only cookie) - static final String DEFAULT_CSRF_COOKIE_NAME = "DSPACE-XSRF-COOKIE"; + public static final String DEFAULT_CSRF_COOKIE_NAME = "DSPACE-XSRF-COOKIE"; + + // The HTTP header that is sent back to the client whenever a new CSRF token is created + // (NOTE: This is purposefully different from DEFAULT_CSRF_HEADER_NAME below!) + public static final String DSPACE_CSRF_HEADER_NAME = "DSPACE-XSRF-TOKEN"; - static final String DEFAULT_CSRF_PARAMETER_NAME = "_csrf"; + public static final String DEFAULT_CSRF_PARAMETER_NAME = "_csrf"; - static final String DEFAULT_CSRF_HEADER_NAME = "X-XSRF-TOKEN"; + // The HTTP header that Spring Security expects to receive from the client in order to validate a CSRF token + public static final String DEFAULT_CSRF_HEADER_NAME = "X-XSRF-TOKEN"; + + private static final String CSRF_TOKEN_REMOVED_ATTRIBUTE_NAME = CookieCsrfTokenRepository.class.getName() + .concat(".REMOVED"); private String parameterName = DEFAULT_CSRF_PARAMETER_NAME; @@ -71,73 +81,93 @@ public class DSpaceCsrfTokenRepository implements CsrfTokenRepository { private String cookieDomain; + private Boolean secure; + + private int cookieMaxAge = -1; + + private Consumer cookieCustomizer = (builder) -> { + }; + public DSpaceCsrfTokenRepository() { } + /** + * Method is copied from {@link CookieCsrfTokenRepository#setCookieCustomizer(Consumer)} + */ + public void setCookieCustomizer(Consumer cookieCustomizer) { + Assert.notNull(cookieCustomizer, "cookieCustomizer must not be null"); + this.cookieCustomizer = cookieCustomizer; + } + + /** + * Method is copied from {@link CookieCsrfTokenRepository#generateToken(HttpServletRequest)} + */ @Override public CsrfToken generateToken(HttpServletRequest request) { - return new DefaultCsrfToken(this.headerName, this.parameterName, - createNewToken()); + return new DefaultCsrfToken(this.headerName, this.parameterName, createNewToken()); } /** - * This method has been modified for DSpace. + * This method has been modified for DSpace. It borrows MOST of the logic from + * {@link CookieCsrfTokenRepository#saveToken(CsrfToken, HttpServletRequest, HttpServletResponse)} *

    - * It now uses ResponseCookie to build the cookie, so that the "SameSite" attribute can be applied. + * It applies a "SameSite" attribute to every cookie by default. *

    - * It also sends the token (if not empty) in both the cookie and the custom "DSPACE-XSRF-TOKEN" header + * It also sends the token (if not empty) back in BOTH the cookie and the custom "DSPACE-XSRF-TOKEN" header. + * By default, Spring Security will only send the token back in the cookie. * @param token current token * @param request current request * @param response current response */ @Override - public void saveToken(CsrfToken token, HttpServletRequest request, - HttpServletResponse response) { - String tokenValue = token == null ? "" : token.getToken(); - Cookie cookie = new Cookie(this.cookieName, tokenValue); - cookie.setSecure(request.isSecure()); - if (this.cookiePath != null && !this.cookiePath.isEmpty()) { - cookie.setPath(this.cookiePath); - } else { - cookie.setPath(this.getRequestContext(request)); - } - if (token == null) { - cookie.setMaxAge(0); - } else { - cookie.setMaxAge(-1); - } - cookie.setHttpOnly(cookieHttpOnly); - if (this.cookieDomain != null && !this.cookieDomain.isEmpty()) { - cookie.setDomain(this.cookieDomain); - } + public void saveToken(CsrfToken token, HttpServletRequest request, HttpServletResponse response) { + String tokenValue = (token != null) ? token.getToken() : ""; - // Custom: Turn the above Cookie into a ResponseCookie so that we can set "SameSite" attribute - // If client is on a different domain than the backend, then Cookie MUST use "SameSite=None" and "Secure". - // Most modern browsers will block it otherwise. - // TODO: Make SameSite configurable? "Lax" cookies are more secure, but require client & backend on same domain. - String sameSite = "None"; - if (!cookie.getSecure()) { - sameSite = "Lax"; - } - ResponseCookie responseCookie = ResponseCookie.from(cookie.getName(), cookie.getValue()) - .path(cookie.getPath()).maxAge(cookie.getMaxAge()) - .domain(cookie.getDomain()).httpOnly(cookie.isHttpOnly()) - .secure(cookie.getSecure()).sameSite(sameSite).build(); + ResponseCookie.ResponseCookieBuilder cookieBuilder = + ResponseCookie.from(this.cookieName, tokenValue) + .secure((this.secure != null) ? this.secure : request.isSecure()) + .path(StringUtils.hasLength(this.cookiePath) ? + this.cookiePath : this.getRequestContext(request)) + .maxAge((token != null) ? this.cookieMaxAge : 0) + .httpOnly(this.cookieHttpOnly) + .domain(this.cookieDomain) + // Custom for DSpace: If client is on a different domain than the backend, then Cookie MUST + // use "SameSite=None" and "Secure". Most modern browsers will block it otherwise. + // TODO: Make SameSite configurable? "Lax" cookies are more secure, but require client & + // backend on same domain. + .sameSite(request.isSecure() ? "None" : "Lax");; - // Write the ResponseCookie to the Set-Cookie header - // This cookie is only used by the backend & not needed by client - response.addHeader(HttpHeaders.SET_COOKIE, responseCookie.toString()); + this.cookieCustomizer.accept(cookieBuilder); - // Send custom header to client with token (only if token not empty) - // We send our token via a custom header because client can be on a different domain. + // Custom for DSpace: also send custom header to client with token. + // We send our token via a custom header because client may be on a different domain. // Cookies cannot be reliably sent cross-domain. if (StringUtils.hasLength(tokenValue)) { - response.setHeader("DSPACE-XSRF-TOKEN", tokenValue); + response.setHeader(DSPACE_CSRF_HEADER_NAME, tokenValue); + } + + Cookie cookie = mapToCookie(cookieBuilder.build()); + response.addCookie(cookie); + + // Set request attribute to signal that response has blank cookie value, + // which allows loadToken to return null when token has been removed + if (!StringUtils.hasLength(tokenValue)) { + request.setAttribute(CSRF_TOKEN_REMOVED_ATTRIBUTE_NAME, Boolean.TRUE); + } else { + request.removeAttribute(CSRF_TOKEN_REMOVED_ATTRIBUTE_NAME); } } + /** + * Method is copied from {@link CookieCsrfTokenRepository#loadToken(HttpServletRequest)} + */ @Override public CsrfToken loadToken(HttpServletRequest request) { + // Return null when token has been removed during the current request + // which allows loadDeferredToken to re-generate the token + if (Boolean.TRUE.equals(request.getAttribute(CSRF_TOKEN_REMOVED_ATTRIBUTE_NAME))) { + return null; + } Cookie cookie = WebUtils.getCookie(request, this.cookieName); if (cookie == null) { return null; @@ -146,104 +176,124 @@ public CsrfToken loadToken(HttpServletRequest request) { if (!StringUtils.hasLength(token)) { return null; } - return new DefaultCsrfToken(this.headerName, this.parameterName, token); } - /** - * Sets the name of the HTTP request parameter that should be used to provide a token. - * - * @param parameterName the name of the HTTP request parameter that should be used to - * provide a token + * Method is copied from {@link CookieCsrfTokenRepository#setParameterName(String)} */ public void setParameterName(String parameterName) { - Assert.notNull(parameterName, "parameterName is not null"); + Assert.notNull(parameterName, "parameterName cannot be null"); this.parameterName = parameterName; } /** - * Sets the name of the HTTP header that should be used to provide the token. - * - * @param headerName the name of the HTTP header that should be used to provide the - * token + * Method is copied from {@link CookieCsrfTokenRepository#setHeaderName(String)} */ public void setHeaderName(String headerName) { - Assert.notNull(headerName, "headerName is not null"); + Assert.notNull(headerName, "headerName cannot be null"); this.headerName = headerName; } /** - * Sets the name of the cookie that the expected CSRF token is saved to and read from. - * - * @param cookieName the name of the cookie that the expected CSRF token is saved to - * and read from + * Method is copied from {@link CookieCsrfTokenRepository#setCookieName(String)} */ public void setCookieName(String cookieName) { - Assert.notNull(cookieName, "cookieName is not null"); + Assert.notNull(cookieName, "cookieName cannot be null"); this.cookieName = cookieName; } /** - * Sets the HttpOnly attribute on the cookie containing the CSRF token. - * Defaults to true. - * - * @param cookieHttpOnly true sets the HttpOnly attribute, false does not set it + * Method is copied from {@link CookieCsrfTokenRepository#setCookieHttpOnly(boolean)} + * @deprecated Use {@link #setCookieCustomizer(Consumer)} instead. */ + @Deprecated public void setCookieHttpOnly(boolean cookieHttpOnly) { this.cookieHttpOnly = cookieHttpOnly; } + /** + * Method is copied from {@link CookieCsrfTokenRepository} + */ private String getRequestContext(HttpServletRequest request) { String contextPath = request.getContextPath(); - return contextPath.length() > 0 ? contextPath : "/"; + return (contextPath.length() > 0) ? contextPath : "/"; } /** - * Factory method to conveniently create an instance that has - * {@link #setCookieHttpOnly(boolean)} set to false. - * - * @return an instance of CookieCsrfTokenRepository with - * {@link #setCookieHttpOnly(boolean)} set to false + * Method is copied from {@link CookieCsrfTokenRepository} + * (and only modified to return the DSpaceCsrfTokenRepository instead) */ public static DSpaceCsrfTokenRepository withHttpOnlyFalse() { DSpaceCsrfTokenRepository result = new DSpaceCsrfTokenRepository(); - result.setCookieHttpOnly(false); + result.cookieHttpOnly = false; return result; } + /** + * Method is copied from {@link CookieCsrfTokenRepository} + */ private String createNewToken() { return UUID.randomUUID().toString(); } /** - * Set the path that the Cookie will be created with. This will override the default functionality which uses the - * request context as the path. - * - * @param path the path to use + * Method is copied from {@link CookieCsrfTokenRepository} + */ + private Cookie mapToCookie(ResponseCookie responseCookie) { + Cookie cookie = new Cookie(responseCookie.getName(), responseCookie.getValue()); + cookie.setSecure(responseCookie.isSecure()); + cookie.setPath(responseCookie.getPath()); + cookie.setMaxAge((int) responseCookie.getMaxAge().getSeconds()); + cookie.setHttpOnly(responseCookie.isHttpOnly()); + if (StringUtils.hasLength(responseCookie.getDomain())) { + cookie.setDomain(responseCookie.getDomain()); + } + if (StringUtils.hasText(responseCookie.getSameSite())) { + cookie.setAttribute("SameSite", responseCookie.getSameSite()); + } + return cookie; + } + + /** + * Method is copied from {@link CookieCsrfTokenRepository#setCookiePath(String)} */ public void setCookiePath(String path) { this.cookiePath = path; } /** - * Get the path that the CSRF cookie will be set to. - * - * @return the path to be used. + * Method is copied from {@link CookieCsrfTokenRepository#getCookiePath()} */ public String getCookiePath() { return this.cookiePath; } /** - * Sets the domain of the cookie that the expected CSRF token is saved to and read from. - * - * @since 5.2 - * @param cookieDomain the domain of the cookie that the expected CSRF token is saved to - * and read from + * Method is copied from {@link CookieCsrfTokenRepository#setCookieDomain(String)} + * @deprecated Use {@link #setCookieCustomizer(Consumer)} instead. */ + @Deprecated public void setCookieDomain(String cookieDomain) { this.cookieDomain = cookieDomain; } + /** + * Method is copied from {@link CookieCsrfTokenRepository#setSecure(Boolean)} + * @deprecated Use {@link #setCookieCustomizer(Consumer)} instead. + */ + @Deprecated + public void setSecure(Boolean secure) { + this.secure = secure; + } + + /** + * Method is copied from {@link CookieCsrfTokenRepository#setCookieMaxAge(int)} + * @deprecated Use {@link #setCookieCustomizer(Consumer)} instead. + */ + @Deprecated + public void setCookieMaxAge(int cookieMaxAge) { + Assert.isTrue(cookieMaxAge != 0, "cookieMaxAge cannot be zero"); + this.cookieMaxAge = cookieMaxAge; + } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/DSpaceObjectAdminPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/DSpaceObjectAdminPermissionEvaluatorPlugin.java index 5420a6e7dc0b..dcf0c34aae32 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/DSpaceObjectAdminPermissionEvaluatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/DSpaceObjectAdminPermissionEvaluatorPlugin.java @@ -12,6 +12,8 @@ import java.util.UUID; import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.app.rest.utils.ContextUtil; import org.dspace.app.rest.utils.DSpaceObjectUtils; import org.dspace.authorize.service.AuthorizeService; @@ -19,20 +21,19 @@ import org.dspace.core.Context; import org.dspace.services.RequestService; import org.dspace.services.model.Request; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; import org.springframework.stereotype.Component; + /** - * {@link RestPermissionEvaluatorPlugin} class that evaluate admin permission against a generic DSpace Object - * + * {@link RestPermissionEvaluatorPlugin} class that evaluate admin permission against a generic DSpace Object. + * * @author Mykhaylo Boychuk (4science.it) */ @Component public class DSpaceObjectAdminPermissionEvaluatorPlugin extends RestObjectPermissionEvaluatorPlugin { - private static final Logger log = LoggerFactory.getLogger(DSpaceObjectAdminPermissionEvaluatorPlugin.class); + private static final Logger log = LogManager.getLogger(); public static final String DSPACE_OBJECT = "dspaceObject"; @@ -64,7 +65,7 @@ public boolean hasDSpacePermission(Authentication authentication, Serializable t DSpaceObject dso = dspaceObjectUtil.findDSpaceObject(context, dsoUuid); return authorizeService.isAdmin(context, dso); } catch (SQLException e) { - log.error(e.getMessage(), e); + log.error(e::getMessage, e); } return false; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/DSpacePermissionEvaluator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/DSpacePermissionEvaluator.java index b801830d916f..d6e162b30a46 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/DSpacePermissionEvaluator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/DSpacePermissionEvaluator.java @@ -38,13 +38,13 @@ public class DSpacePermissionEvaluator implements PermissionEvaluator { * expression system. This corresponds to the DSpace action. Not null. * @return true if the permission is granted by one of the plugins, false otherwise */ + @Override public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) { for (RestPermissionEvaluatorPlugin permissionEvaluatorPlugin : permissionEvaluatorPluginList) { if (permissionEvaluatorPlugin.hasPermission(authentication, targetDomainObject, permission)) { return true; } } - return false; } @@ -59,6 +59,7 @@ public boolean hasPermission(Authentication authentication, Object targetDomainO * expression system. This corresponds to the DSpace action. Not null. * @return true if the permission is granted by one of the plugins, false otherwise */ + @Override public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) { for (RestPermissionEvaluatorPlugin permissionEvaluatorPlugin : permissionEvaluatorPluginList) { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/EPersonRestAuthenticationProvider.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/EPersonRestAuthenticationProvider.java index e55734e513de..d66ecd547274 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/EPersonRestAuthenticationProvider.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/EPersonRestAuthenticationProvider.java @@ -15,10 +15,12 @@ import java.util.LinkedList; import java.util.List; import java.util.Objects; -import javax.annotation.PostConstruct; -import javax.servlet.http.HttpServletRequest; +import jakarta.annotation.PostConstruct; +import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.app.rest.login.PostLoggedInAction; import org.dspace.app.rest.utils.ContextUtil; import org.dspace.authenticate.AuthenticationMethod; @@ -28,8 +30,6 @@ import org.dspace.core.LogHelper; import org.dspace.eperson.EPerson; import org.dspace.services.RequestService; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.BadCredentialsException; @@ -40,7 +40,7 @@ import org.springframework.stereotype.Component; /** - * This class is responsible for authenticating a user via REST + * This class is responsible for authenticating a user via REST. * * @author Frederic Van Reet (frederic dot vanreet at atmire dot com) * @author Tom Desair (tom dot desair at atmire dot com) @@ -48,7 +48,7 @@ @Component public class EPersonRestAuthenticationProvider implements AuthenticationProvider { - private static final Logger log = LoggerFactory.getLogger(EPersonRestAuthenticationProvider.class); + private static final Logger log = LogManager.getLogger(); public static final String MANAGE_ACCESS_GROUP = "MANAGE_ACCESS_GROUP"; @@ -144,9 +144,8 @@ private Authentication authenticateNewLogin(Authentication authentication) { } } else { - log.info(LogHelper.getHeader(newContext, "failed_login", "email=" - + name + ", result=" - + authenticateResult)); + log.info(LogHelper.getHeader(newContext, "failed_login", + "email={}, result={}"), name, authenticateResult); throw new BadCredentialsException("Login failed"); } } @@ -155,7 +154,7 @@ private Authentication authenticateNewLogin(Authentication authentication) { try { newContext.complete(); } catch (SQLException e) { - log.error(e.getMessage() + " occurred while trying to close", e); + log.error("{} occurred while trying to close", e.getMessage(), e); } } } @@ -182,7 +181,7 @@ private Authentication createAuthentication(final Context context) { return new DSpaceAuthentication(ePerson, getGrantedAuthorities(context)); } else { - log.info(LogHelper.getHeader(context, "failed_login", "No eperson with an non-blank e-mail address found")); + log.info(LogHelper.getHeader(context, "failed_login", "No eperson with a non-blank e-mail address found")); throw new BadCredentialsException("Login failed"); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/EPersonRestPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/EPersonRestPermissionEvaluatorPlugin.java index ce6783ae1507..c3e1cc68ae96 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/EPersonRestPermissionEvaluatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/EPersonRestPermissionEvaluatorPlugin.java @@ -11,9 +11,11 @@ import java.sql.SQLException; 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.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.app.rest.model.patch.Operation; import org.dspace.app.rest.model.patch.Patch; import org.dspace.app.rest.repository.patch.operation.DSpaceObjectMetadataPatchUtils; @@ -27,8 +29,6 @@ import org.dspace.eperson.EPerson; import org.dspace.services.RequestService; import org.dspace.services.model.Request; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; import org.springframework.stereotype.Component; @@ -40,7 +40,7 @@ @Component public class EPersonRestPermissionEvaluatorPlugin extends RestObjectPermissionEvaluatorPlugin { - private static final Logger log = LoggerFactory.getLogger(EPersonRestPermissionEvaluatorPlugin.class); + private static final Logger log = LogManager.getLogger(); @Autowired AuthorizeService authorizeService; @@ -61,6 +61,9 @@ public boolean hasDSpacePermission(Authentication authentication, Serializable t if (Constants.getTypeID(targetType) != Constants.EPERSON) { return false; } + if (targetId == null) { + return false; + } Request request = requestService.getCurrentRequest(); Context context = ContextUtil.obtainContext(request.getHttpServletRequest()); @@ -84,7 +87,7 @@ public boolean hasDSpacePermission(Authentication authentication, Serializable t return true; } } catch (SQLException e) { - log.error(e.getMessage(), e); + log.error(e::getMessage, e); } @@ -100,7 +103,7 @@ public boolean hasPatchPermission(Authentication authentication, Serializable ta Request currentRequest = requestService.getCurrentRequest(); if (currentRequest != null) { HttpServletRequest httpServletRequest = currentRequest.getHttpServletRequest(); - if (operations.size() > 0 + if (!operations.isEmpty() && StringUtils.equalsIgnoreCase(operations.get(0).getOp(), PatchOperation.OPERATION_ADD) && StringUtils.equalsIgnoreCase(operations.get(0).getPath(), EPersonPasswordAddOperation.OPERATION_PASSWORD_CHANGE) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ExtractorOfAInprogressSubmissionInformations.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ExtractorOfAInprogressSubmissionInformations.java index dd2c8602d891..cbe685f9fe48 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ExtractorOfAInprogressSubmissionInformations.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ExtractorOfAInprogressSubmissionInformations.java @@ -9,9 +9,9 @@ import java.sql.SQLException; import java.util.Objects; 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.StringUtils; import org.dspace.app.rest.model.WorkflowItemRest; import org.dspace.app.rest.model.WorkspaceItemRest; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/GroupRestPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/GroupRestPermissionEvaluatorPlugin.java index d6d3bc82c7cf..ad34cede6bf4 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/GroupRestPermissionEvaluatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/GroupRestPermissionEvaluatorPlugin.java @@ -11,6 +11,8 @@ import java.sql.SQLException; import java.util.UUID; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.app.rest.utils.ContextUtil; import org.dspace.app.util.AuthorizeUtil; import org.dspace.authorize.service.AuthorizeService; @@ -22,8 +24,6 @@ import org.dspace.eperson.service.GroupService; import org.dspace.services.RequestService; import org.dspace.services.model.Request; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; import org.springframework.stereotype.Component; @@ -35,7 +35,7 @@ @Component public class GroupRestPermissionEvaluatorPlugin extends RestObjectPermissionEvaluatorPlugin { - private static final Logger log = LoggerFactory.getLogger(GroupRestPermissionEvaluatorPlugin.class); + private static final Logger log = LogManager.getLogger(); @Autowired private RequestService requestService; @@ -59,6 +59,9 @@ public boolean hasDSpacePermission(Authentication authentication, Serializable t || Constants.getTypeID(targetType) != Constants.GROUP) { return false; } + if (targetId == null) { + return false; + } Request request = requestService.getCurrentRequest(); Context context = ContextUtil.obtainContext(request.getHttpServletRequest()); @@ -87,7 +90,7 @@ public boolean hasDSpacePermission(Authentication authentication, Serializable t } } catch (SQLException e) { - log.error(e.getMessage(), e); + log.error(e::getMessage, e); } return false; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/MethodSecurityConfig.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/MethodSecurityConfig.java index 5ee308c73ed8..90a8f013e259 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/MethodSecurityConfig.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/MethodSecurityConfig.java @@ -8,28 +8,31 @@ package org.dspace.app.rest.security; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.access.PermissionEvaluator; import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler; import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; -import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; -import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; +/** + * This EnableMethodSecurity configuration enables Spring Security annotation checks on all methods + * (e.g. @PreAuthorize, @PostAuthorize annotations, etc.) + */ @Configuration -@EnableGlobalMethodSecurity(prePostEnabled = true) -public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration { - +@EnableMethodSecurity +public class MethodSecurityConfig { @Autowired private PermissionEvaluator dSpacePermissionEvaluator; - @Autowired - private ApplicationContext applicationContext; - - @Override - protected MethodSecurityExpressionHandler createExpressionHandler() { + /** + * Tell Spring to use our custom PermissionEvaluator as part of method security. + * This allows DSpacePermissionEvaluator to be used in @PreAuthorize annotations (and similar). + * @see org.dspace.app.rest.security.DSpacePermissionEvaluator + */ + @Bean + MethodSecurityExpressionHandler methodSecurityExpressionHandler() { DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler(); - expressionHandler.setApplicationContext(applicationContext); expressionHandler.setPermissionEvaluator(dSpacePermissionEvaluator); return expressionHandler; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/OidcLoginFilter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/OidcLoginFilter.java index c84840e77041..28c67314c5ca 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/OidcLoginFilter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/OidcLoginFilter.java @@ -10,26 +10,27 @@ import static org.dspace.authenticate.OidcAuthenticationBean.OIDC_AUTH_ATTRIBUTE; import java.io.IOException; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; /** - * This class will filter openID Connect requests and try and authenticate them. + * This class will filter OpenID Connect (OIDC) requests and try and authenticate them. + * In this case, the actual authentication is performed by OIDC. After authentication succeeds, OIDC will send + * the authentication data to this filter in order for it to be processed by DSpace. * * @author Pasquale Cavallo (pasquale.cavallo at 4science dot it) */ - public class OidcLoginFilter extends StatelessLoginFilter { - public OidcLoginFilter(String url, AuthenticationManager authenticationManager, + public OidcLoginFilter(String url, String httpMethod, AuthenticationManager authenticationManager, RestAuthenticationService restAuthenticationService) { - super(url, authenticationManager, restAuthenticationService); + super(url, httpMethod, authenticationManager, restAuthenticationService); } @Override diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/OrcidLoginFilter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/OrcidLoginFilter.java index 9fdef6b050f7..70496b9dba23 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/OrcidLoginFilter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/OrcidLoginFilter.java @@ -9,11 +9,11 @@ import java.io.IOException; import java.util.ArrayList; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +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; @@ -32,6 +32,8 @@ /** * This class will filter ORCID requests and try and authenticate them. + * In this case, the actual authentication is performed by ORCID. After authentication succeeds, ORCID will send + * the authentication data to this filter in order for it to be processed by DSpace. * * @author Luca Giamminonni (luca.giamminonni at 4science.it) */ @@ -45,9 +47,9 @@ public class OrcidLoginFilter extends StatelessLoginFilter { private OrcidAuthenticationBean orcidAuthentication = new DSpace().getServiceManager() .getServiceByName("orcidAuthentication", OrcidAuthenticationBean.class); - public OrcidLoginFilter(String url, AuthenticationManager authenticationManager, + public OrcidLoginFilter(String url, String httpMethod, AuthenticationManager authenticationManager, RestAuthenticationService restAuthenticationService) { - super(url, authenticationManager, restAuthenticationService); + super(url, httpMethod, authenticationManager, restAuthenticationService); } @Override @@ -57,7 +59,7 @@ public Authentication attemptAuthentication(HttpServletRequest req, HttpServletR if (!OrcidAuthentication.isEnabled()) { throw new ProviderNotFoundException("Orcid login is disabled."); } - + // NOTE: because this authentication is implicit, we pass in an empty DSpaceAuthentication return authenticationManager.authenticate(new DSpaceAuthentication()); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/OrcidQueueAndHistoryRestPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/OrcidQueueAndHistoryRestPermissionEvaluatorPlugin.java index 0139f3b3362a..cadd42f13398 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/OrcidQueueAndHistoryRestPermissionEvaluatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/OrcidQueueAndHistoryRestPermissionEvaluatorPlugin.java @@ -11,6 +11,8 @@ import java.sql.SQLException; import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.app.rest.utils.ContextUtil; import org.dspace.content.Item; import org.dspace.content.service.ItemService; @@ -22,21 +24,19 @@ import org.dspace.orcid.service.OrcidQueueService; import org.dspace.services.RequestService; import org.dspace.services.model.Request; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; import org.springframework.stereotype.Component; /** * Class that evaluate DELETE and READ permissions - * + * * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.it) */ @Component public class OrcidQueueAndHistoryRestPermissionEvaluatorPlugin extends RestObjectPermissionEvaluatorPlugin { - private static final Logger log = LoggerFactory.getLogger(OrcidQueueAndHistoryRestPermissionEvaluatorPlugin.class); + private static final Logger log = LogManager.getLogger(); public static final String ORCID_QUEUE = "ORCID_QUEUE"; public static final String ORCID_HISTORY = "ORCID_HISTORY"; @@ -97,7 +97,7 @@ private boolean hasAccess(Context context, EPerson currentUser, Integer orcidObj .anyMatch(authority -> currentUser.getID().toString().equals(authority)); } catch (SQLException e) { - log.error(e.getMessage(), e); + log.error(e::getMessage, e); } return false; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/OrcidQueueSearchRestPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/OrcidQueueSearchRestPermissionEvaluatorPlugin.java index d8566143c437..2666d98bd889 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/OrcidQueueSearchRestPermissionEvaluatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/OrcidQueueSearchRestPermissionEvaluatorPlugin.java @@ -13,6 +13,8 @@ import java.util.UUID; import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.app.rest.utils.ContextUtil; import org.dspace.content.Item; import org.dspace.content.MetadataValue; @@ -21,8 +23,6 @@ import org.dspace.eperson.EPerson; import org.dspace.services.RequestService; import org.dspace.services.model.Request; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; import org.springframework.stereotype.Component; @@ -36,7 +36,7 @@ @Component public class OrcidQueueSearchRestPermissionEvaluatorPlugin extends RestObjectPermissionEvaluatorPlugin { - private static final Logger log = LoggerFactory.getLogger(OrcidQueueAndHistoryRestPermissionEvaluatorPlugin.class); + private static final Logger log = LogManager.getLogger(); public static final String ORCID_QUEUE_SEARCH = "ORCID_QUEUE_SEARCH"; @@ -75,7 +75,7 @@ public boolean hasDSpacePermission(Authentication authentication, Serializable t return true; } } catch (SQLException e) { - log.error(e.getMessage(), e); + log.error(e::getMessage, e); } return false; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/PoolTaskRestPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/PoolTaskRestPermissionEvaluatorPlugin.java index d145dae44998..3aa556c91fe0 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/PoolTaskRestPermissionEvaluatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/PoolTaskRestPermissionEvaluatorPlugin.java @@ -12,6 +12,8 @@ import java.sql.SQLException; import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.app.rest.model.PoolTaskRest; import org.dspace.app.rest.utils.ContextUtil; import org.dspace.authorize.AuthorizeException; @@ -23,8 +25,6 @@ import org.dspace.xmlworkflow.storedcomponents.PoolTask; import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; import org.dspace.xmlworkflow.storedcomponents.service.PoolTaskService; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; import org.springframework.stereotype.Component; @@ -37,7 +37,7 @@ @Component public class PoolTaskRestPermissionEvaluatorPlugin extends RestObjectPermissionEvaluatorPlugin { - private static final Logger log = LoggerFactory.getLogger(PoolTaskRestPermissionEvaluatorPlugin.class); + private static final Logger log = LogManager.getLogger(); @Autowired private RequestService requestService; @@ -79,7 +79,7 @@ public boolean hasDSpacePermission(Authentication authentication, Serializable t return true; } } catch (SQLException | AuthorizeException | IOException e) { - log.error(e.getMessage(), e); + log.error(e::getMessage, e); } return false; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ProcessRestPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ProcessRestPermissionEvaluatorPlugin.java index 94d9694ec422..b476b3fef98d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ProcessRestPermissionEvaluatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ProcessRestPermissionEvaluatorPlugin.java @@ -11,6 +11,8 @@ import java.sql.SQLException; import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.app.rest.model.ProcessRest; import org.dspace.app.rest.utils.ContextUtil; import org.dspace.authorize.service.AuthorizeService; @@ -19,8 +21,6 @@ import org.dspace.scripts.service.ProcessService; import org.dspace.services.RequestService; import org.dspace.services.model.Request; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; import org.springframework.stereotype.Component; @@ -33,7 +33,7 @@ @Component public class ProcessRestPermissionEvaluatorPlugin extends RestObjectPermissionEvaluatorPlugin { - private static final Logger log = LoggerFactory.getLogger(ProcessRestPermissionEvaluatorPlugin.class); + private static final Logger log = LogManager.getLogger(); @Autowired private RequestService requestService; @@ -66,7 +66,7 @@ public boolean hasDSpacePermission(Authentication authentication, Serializable t return true; } } catch (SQLException e) { - log.error(e.getMessage(), e); + log.error(e::getMessage, e); } return false; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/QAEventRestPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/QAEventRestPermissionEvaluatorPlugin.java new file mode 100644 index 000000000000..6e0c0482db61 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/QAEventRestPermissionEvaluatorPlugin.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.rest.security; + +import java.io.Serializable; +import java.util.Objects; + +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.dspace.app.rest.model.QAEventRest; +import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.content.QAEvent; +import org.dspace.core.Context; +import org.dspace.qaevent.service.QAEventSecurityService; +import org.dspace.qaevent.service.QAEventService; +import org.dspace.services.RequestService; +import org.dspace.services.model.Request; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Component; + +/** + * This class will handle Permissions for the {@link QAEventRest} object and its calls + * + * @author Andrea Bollini (4Science) + */ +@Component +public class QAEventRestPermissionEvaluatorPlugin extends RestObjectPermissionEvaluatorPlugin { + + private static final Logger log = LogManager.getLogger(); + + @Autowired + private QAEventService qaEventService; + + @Autowired + private QAEventSecurityService qaEventSecurityService; + + @Autowired + private RequestService requestService; + + /** + * Responsible for checking whether or not the user has access to the requested QASource + * + * @param targetType the type of Rest Object that should be checked for permission. This class would deal only with + * qaevent + * @param targetId string to extract the sourcename from + */ + @Override + public boolean hasDSpacePermission(Authentication authentication, Serializable targetId, String targetType, + DSpaceRestPermission restPermission) { + if (StringUtils.equalsIgnoreCase(QAEventRest.NAME, targetType)) { + log.debug("Checking permission for targetId {}", targetId); + Request request = requestService.getCurrentRequest(); + Context context = ContextUtil.obtainContext(request.getHttpServletRequest()); + if (Objects.isNull(targetId)) { + return true; + } + QAEvent qaEvent = qaEventService.findEventByEventId(targetId.toString()); + // everyone is expected to be able to see a not existing event (so we can return not found) + if ((qaEvent == null + || qaEventSecurityService.canSeeEvent(context, context.getCurrentUser(), qaEvent))) { + return true; + } + } + return false; + } + +} \ No newline at end of file diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/QASourceRestPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/QASourceRestPermissionEvaluatorPlugin.java new file mode 100644 index 000000000000..8aca72326efc --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/QASourceRestPermissionEvaluatorPlugin.java @@ -0,0 +1,71 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.rest.security; + +import java.io.Serializable; +import java.util.Objects; + +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.dspace.app.rest.model.QASourceRest; +import org.dspace.app.rest.model.QATopicRest; +import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.core.Context; +import org.dspace.qaevent.QATopic; +import org.dspace.qaevent.service.QAEventSecurityService; +import org.dspace.services.RequestService; +import org.dspace.services.model.Request; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Component; + +/** + * This class will handle Permissions for the {@link QASourceRest} object and {@link QATopic} + * + * @author Andrea Bollini (4Science) + */ +@Component +public class QASourceRestPermissionEvaluatorPlugin extends RestObjectPermissionEvaluatorPlugin { + + private static final Logger log = LogManager.getLogger(); + + @Autowired + private QAEventSecurityService qaEventSecurityService; + + @Autowired + private RequestService requestService; + + /** + * Responsible for checking whether or not the user has access to the requested QASource + * + * @param targetType the type of Rest Object that should be checked for permission. This class would deal only with + * qasource and qatopic + * @param targetId string to extract the sourcename from + */ + @Override + public boolean hasDSpacePermission(Authentication authentication, Serializable targetId, String targetType, + DSpaceRestPermission restPermission) { + if (StringUtils.equalsIgnoreCase(QASourceRest.NAME, targetType) + || StringUtils.equalsIgnoreCase(QATopicRest.NAME, targetType)) { + log.debug("Checking permission for targetId {}", targetId); + Request request = requestService.getCurrentRequest(); + Context context = ContextUtil.obtainContext(request.getHttpServletRequest()); + if (Objects.isNull(targetId)) { + return true; + } + // the source name is always the first part of the id both for a source than a topic + // users can see all the topic in source that they can access, eventually they will have no + // events visible to them + String sourceName = targetId.toString().split(":")[0]; + return qaEventSecurityService.canSeeSource(context, context.getCurrentUser(), sourceName); + } + return false; + } + +} \ No newline at end of file diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ReadAuthorizationPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ReadAuthorizationPermissionEvaluatorPlugin.java index 88670c89fef8..8bf0a1452a61 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ReadAuthorizationPermissionEvaluatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ReadAuthorizationPermissionEvaluatorPlugin.java @@ -11,6 +11,8 @@ import java.sql.SQLException; import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.app.rest.authorization.AuthorizationRestUtil; import org.dspace.app.rest.model.AuthorizationRest; import org.dspace.app.rest.utils.ContextUtil; @@ -19,21 +21,19 @@ import org.dspace.eperson.EPerson; import org.dspace.services.RequestService; import org.dspace.services.model.Request; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; import org.springframework.stereotype.Component; /** * {@link RestPermissionEvaluatorPlugin} class that evaluate READ permissions for an Authorization - * + * * @author Andrea Bollini (andrea.bollini at 4science.it) */ @Component public class ReadAuthorizationPermissionEvaluatorPlugin extends RestObjectPermissionEvaluatorPlugin { - private static final Logger log = LoggerFactory.getLogger(ReadAuthorizationPermissionEvaluatorPlugin.class); + private static final Logger log = LogManager.getLogger(); @Autowired AuthorizeService authorizeService; @@ -75,7 +75,7 @@ public boolean hasDSpacePermission(Authentication authentication, Serializable t } } } catch (SQLException e) { - log.error(e.getMessage(), e); + log.error(e::getMessage, e); } return false; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ResearcherProfileRestPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ResearcherProfileRestPermissionEvaluatorPlugin.java index 0e0f60807855..5cad1b33535a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ResearcherProfileRestPermissionEvaluatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ResearcherProfileRestPermissionEvaluatorPlugin.java @@ -13,8 +13,8 @@ import java.io.Serializable; import java.util.UUID; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; import org.dspace.app.rest.model.ResearcherProfileRest; import org.dspace.app.rest.utils.ContextUtil; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ResourcePolicyAdminPermissionEvalutatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ResourcePolicyAdminPermissionEvalutatorPlugin.java index 421d25f9406d..ccf272ecefae 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ResourcePolicyAdminPermissionEvalutatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ResourcePolicyAdminPermissionEvalutatorPlugin.java @@ -11,6 +11,8 @@ import java.sql.SQLException; import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.app.rest.model.ResourcePolicyRest; import org.dspace.app.rest.utils.ContextUtil; import org.dspace.authorize.ResourcePolicy; @@ -20,8 +22,6 @@ import org.dspace.core.Context; import org.dspace.services.RequestService; import org.dspace.services.model.Request; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.rest.webmvc.ResourceNotFoundException; import org.springframework.security.core.Authentication; @@ -36,7 +36,7 @@ @Component public class ResourcePolicyAdminPermissionEvalutatorPlugin extends RestObjectPermissionEvaluatorPlugin { - private static final Logger log = LoggerFactory.getLogger(ResourcePolicyRestPermissionEvaluatorPlugin.class); + private static final Logger log = LogManager.getLogger(); public static final String RESOURCE_POLICY_PATCH = "resourcepolicy"; @@ -74,7 +74,7 @@ public boolean hasDSpacePermission(Authentication authentication, Serializable t DSpaceObject dso = resourcePolicy.getdSpaceObject(); return authorizeService.isAdmin(context, dso); } catch (SQLException e) { - log.error(e.getMessage(), e); + log.error(e::getMessage, e); } return false; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ResourcePolicyRestPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ResourcePolicyRestPermissionEvaluatorPlugin.java index bf7ce3b53f1a..9a34ca68110d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ResourcePolicyRestPermissionEvaluatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ResourcePolicyRestPermissionEvaluatorPlugin.java @@ -11,6 +11,8 @@ import java.sql.SQLException; import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.app.rest.model.ResourcePolicyRest; import org.dspace.app.rest.utils.ContextUtil; import org.dspace.authorize.service.AuthorizeService; @@ -20,21 +22,19 @@ import org.dspace.eperson.service.EPersonService; import org.dspace.services.RequestService; import org.dspace.services.model.Request; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; import org.springframework.stereotype.Component; /** - * {@link RestPermissionEvaluatorPlugin} class that evaluate READ, WRITE and DELETE permissions over a ResourcePolicy - * + * {@link RestPermissionEvaluatorPlugin} class that evaluate READ, WRITE and DELETE permissions over a ResourcePolicy. + * * @author Mykhaylo Boychuk (4science.it) */ @Component public class ResourcePolicyRestPermissionEvaluatorPlugin extends RestObjectPermissionEvaluatorPlugin { - private static final Logger log = LoggerFactory.getLogger(ResourcePolicyRestPermissionEvaluatorPlugin.class); + private static final Logger log = LogManager.getLogger(); @Autowired AuthorizeService authorizeService; @@ -78,7 +78,7 @@ public boolean hasDSpacePermission(Authentication authentication, Serializable t return true; } } catch (SQLException e) { - log.error(e.getMessage(), e); + log.error(e::getMessage, e); } return false; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/RestAuthenticationService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/RestAuthenticationService.java index dd464edfcdee..e021344b15f0 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/RestAuthenticationService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/RestAuthenticationService.java @@ -8,9 +8,9 @@ package org.dspace.app.rest.security; import java.io.IOException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.dspace.app.rest.model.wrapper.AuthenticationToken; import org.dspace.authenticate.service.AuthenticationService; import org.dspace.core.Context; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ShibbolethLoginFilter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ShibbolethLoginFilter.java index e10fc49eccad..886706713238 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ShibbolethLoginFilter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ShibbolethLoginFilter.java @@ -9,11 +9,11 @@ import java.io.IOException; import java.util.ArrayList; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +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; @@ -56,9 +56,9 @@ public class ShibbolethLoginFilter extends StatelessLoginFilter { private ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); - public ShibbolethLoginFilter(String url, AuthenticationManager authenticationManager, + public ShibbolethLoginFilter(String url, String httpMethod, AuthenticationManager authenticationManager, RestAuthenticationService restAuthenticationService) { - super(url, authenticationManager, restAuthenticationService); + super(url, httpMethod, authenticationManager, restAuthenticationService); } @Override diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/StatelessAuthenticationFilter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/StatelessAuthenticationFilter.java index 964d35f42c34..6e151654b524 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/StatelessAuthenticationFilter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/StatelessAuthenticationFilter.java @@ -11,11 +11,13 @@ import java.sql.SQLException; import java.util.List; import java.util.UUID; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.app.rest.utils.ContextUtil; import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.factory.AuthorizeServiceFactory; @@ -28,8 +30,6 @@ import org.dspace.services.RequestService; import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.util.UUIDUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.core.Authentication; @@ -47,21 +47,24 @@ */ public class StatelessAuthenticationFilter extends BasicAuthenticationFilter { - private static final Logger log = LoggerFactory.getLogger(StatelessAuthenticationFilter.class); + private static final Logger log = LogManager.getLogger(); private static final String ON_BEHALF_OF_REQUEST_PARAM = "X-On-Behalf-Of"; - private RestAuthenticationService restAuthenticationService; + private final RestAuthenticationService restAuthenticationService; - private EPersonRestAuthenticationProvider authenticationProvider; + private final EPersonRestAuthenticationProvider authenticationProvider; - private RequestService requestService; + private final RequestService requestService; - private AuthorizeService authorizeService = AuthorizeServiceFactory.getInstance().getAuthorizeService(); + private final AuthorizeService authorizeService + = AuthorizeServiceFactory.getInstance().getAuthorizeService(); - private EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService(); + private final EPersonService ePersonService + = EPersonServiceFactory.getInstance().getEPersonService(); - private ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); + private final ConfigurationService configurationService + = DSpaceServicesFactory.getInstance().getConfigurationService(); public StatelessAuthenticationFilter(AuthenticationManager authenticationManager, RestAuthenticationService restAuthenticationService, @@ -104,7 +107,7 @@ protected void doFilterInternal(HttpServletRequest req, /** * This method returns an Authentication object - * This Authentication object will be attempted to be for the eperson with the uuid in the parameter. Incase + * This Authentication object will be attempted to be for the eperson with the uuid in the parameter. In case * this is able to be done properly, we'll be returning the EPerson Authentication. * If the Authentication object returned is not null, we'll be logged in as this EPerson given through from the * request. @@ -124,7 +127,7 @@ private Authentication getAuthentication(HttpServletRequest request, HttpServlet // parse the token. EPerson eperson = restAuthenticationService.getAuthenticatedEPerson(request, res, context); if (eperson != null) { - log.debug("Found authentication data in request for EPerson {}", eperson.getEmail()); + log.debug("Found authentication data in request for EPerson {}", eperson::getEmail); //Pass the eperson ID to the request service requestService.setCurrentUserId(eperson.getID()); @@ -174,7 +177,7 @@ private Authentication getOnBehalfOfAuthentication(Context context, String onBeh requestService.setCurrentUserId(epersonUuid); context.switchContextUser(onBehalfOfEPerson); log.debug("Found 'on-behalf-of' authentication data in request for EPerson {}", - onBehalfOfEPerson.getEmail()); + onBehalfOfEPerson::getEmail); return new DSpaceAuthentication(onBehalfOfEPerson, authenticationProvider.getGrantedAuthorities(context)); } else { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/StatelessLoginFilter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/StatelessLoginFilter.java index c95fce71c425..cfae6bfcb42b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/StatelessLoginFilter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/StatelessLoginFilter.java @@ -8,13 +8,13 @@ package org.dspace.app.rest.security; import java.io.IOException; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; @@ -23,7 +23,7 @@ /** * This class will filter /api/authn/login requests to try and authenticate them. Keep in mind, this filter runs *after* - * StatelessAuthenticationFilter (which looks for authentication data in the request itself). So, in some scenarios + * {@link StatelessAuthenticationFilter} (which looks for authentication data in the request itself). So, in some scenarios * (e.g. after a Shibboleth login) the StatelessAuthenticationFilter does the actual authentication, and this Filter * just ensures the auth token (JWT) is sent back in an Authorization header. * @@ -31,7 +31,7 @@ * @author Tom Desair (tom dot desair at atmire dot com) */ public class StatelessLoginFilter extends AbstractAuthenticationProcessingFilter { - private static final Logger log = LoggerFactory.getLogger(StatelessLoginFilter.class); + private static final Logger log = LogManager.getLogger(); protected AuthenticationManager authenticationManager; @@ -41,9 +41,20 @@ public class StatelessLoginFilter extends AbstractAuthenticationProcessingFilter public void afterPropertiesSet() { } - public StatelessLoginFilter(String url, AuthenticationManager authenticationManager, + /** + * Initialize a StatelessLoginFilter for the given URL and HTTP method. This login filter will ONLY attempt + * authentication for requests that match this URL and method. The URL & method are defined in the configuration + * in WebSecurityConfiguration. + * @see org.dspace.app.rest.security.WebSecurityConfiguration + * @param url URL path to attempt to authenticate (e.g. "/api/authn/login") + * @param httpMethod HTTP method to attempt to authentication (e.g. "POST") + * @param authenticationManager Spring Security AuthenticationManager to use for authentication + * @param restAuthenticationService DSpace RestAuthenticationService to use for authentication + */ + public StatelessLoginFilter(String url, String httpMethod, AuthenticationManager authenticationManager, RestAuthenticationService restAuthenticationService) { - super(new AntPathRequestMatcher(url)); + // NOTE: attemptAuthentication() below will only be triggered by requests that match both this URL and method + super(new AntPathRequestMatcher(url, httpMethod)); this.authenticationManager = authenticationManager; this.restAuthenticationService = restAuthenticationService; } @@ -97,7 +108,7 @@ protected void successfulAuthentication(HttpServletRequest req, Authentication auth) throws IOException, ServletException { DSpaceAuthentication dSpaceAuthentication = (DSpaceAuthentication) auth; - log.debug("Authentication successful for EPerson {}", dSpaceAuthentication.getName()); + log.debug("Authentication successful for EPerson {}", dSpaceAuthentication::getName); restAuthenticationService.addAuthenticationDataForUser(req, res, dSpaceAuthentication, false); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/SubscriptionRestPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/SubscriptionRestPermissionEvaluatorPlugin.java index c93d966e73cb..dabb5a03b55f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/SubscriptionRestPermissionEvaluatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/SubscriptionRestPermissionEvaluatorPlugin.java @@ -17,6 +17,8 @@ import java.util.Objects; import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.app.rest.utils.ContextUtil; import org.dspace.authorize.service.AuthorizeService; import org.dspace.core.Context; @@ -25,21 +27,19 @@ import org.dspace.eperson.service.SubscribeService; import org.dspace.services.RequestService; import org.dspace.services.model.Request; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; import org.springframework.stereotype.Component; /** * {@link RestPermissionEvaluatorPlugin} class that evaluate READ, WRITE and DELETE permissions over a Subscription - * + * * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.com) */ @Component public class SubscriptionRestPermissionEvaluatorPlugin extends RestObjectPermissionEvaluatorPlugin { - private static final Logger log = LoggerFactory.getLogger(SubscriptionRestPermissionEvaluatorPlugin.class); + private static final Logger log = LogManager.getLogger(); @Autowired private RequestService requestService; @@ -76,7 +76,7 @@ public boolean hasDSpacePermission(Authentication authentication, Serializable t Subscription subscription = subscribeService.findById(context, Integer.parseInt(targetId.toString())); return Objects.nonNull(subscription) ? currentUser.equals(subscription.getEPerson()) : false; } catch (SQLException e) { - log.error(e.getMessage(), e); + log.error(e::getMessage, e); } return false; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/TemplateItemRestPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/TemplateItemRestPermissionEvaluatorPlugin.java index cb977dff3aef..9416844cbc1b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/TemplateItemRestPermissionEvaluatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/TemplateItemRestPermissionEvaluatorPlugin.java @@ -12,6 +12,8 @@ import java.util.UUID; import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.app.rest.model.TemplateItemRest; import org.dspace.app.rest.utils.ContextUtil; import org.dspace.authorize.service.AuthorizeService; @@ -21,8 +23,6 @@ import org.dspace.eperson.EPerson; import org.dspace.services.RequestService; import org.dspace.services.model.Request; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; import org.springframework.stereotype.Component; @@ -35,7 +35,7 @@ @Component public class TemplateItemRestPermissionEvaluatorPlugin extends RestObjectPermissionEvaluatorPlugin { - private static final Logger log = LoggerFactory.getLogger(TemplateItemRestPermissionEvaluatorPlugin.class); + private static final Logger log = LogManager.getLogger(); @Autowired private RequestService requestService; @@ -76,7 +76,7 @@ public boolean hasDSpacePermission(Authentication authentication, Serializable t return true; } } catch (SQLException e) { - log.error(e.getMessage(), e); + log.error(e::getMessage, e); } return false; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/UsageReportRestPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/UsageReportRestPermissionEvaluatorPlugin.java index 06254d561ace..c29abd2f1a71 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/UsageReportRestPermissionEvaluatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/UsageReportRestPermissionEvaluatorPlugin.java @@ -13,6 +13,8 @@ import java.util.UUID; import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.app.rest.model.UsageReportRest; import org.dspace.app.rest.utils.ContextUtil; import org.dspace.app.rest.utils.DSpaceObjectUtils; @@ -22,8 +24,6 @@ import org.dspace.services.ConfigurationService; import org.dspace.services.RequestService; import org.dspace.services.model.Request; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; import org.springframework.stereotype.Component; @@ -36,7 +36,7 @@ @Component public class UsageReportRestPermissionEvaluatorPlugin extends RestObjectPermissionEvaluatorPlugin { - private static final Logger log = LoggerFactory.getLogger(UsageReportRestPermissionEvaluatorPlugin.class); + private static final Logger log = LogManager.getLogger(); @Autowired private ConfigurationService configurationService; @@ -95,7 +95,7 @@ public boolean hasDSpacePermission(Authentication authentication, Serializable t } return authorizeService.authorizeActionBoolean(context, dso, restPermission.getDspaceApiActionId()); } catch (SQLException e) { - log.error(e.getMessage(), e); + log.error(e::getMessage, e); } } return false; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/VersionHistoryRestPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/VersionHistoryRestPermissionEvaluatorPlugin.java index d69f6a21cad3..de5980e3c314 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/VersionHistoryRestPermissionEvaluatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/VersionHistoryRestPermissionEvaluatorPlugin.java @@ -11,6 +11,8 @@ import java.sql.SQLException; import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.app.rest.model.VersionHistoryRest; import org.dspace.app.rest.utils.ContextUtil; import org.dspace.authorize.service.AuthorizeService; @@ -18,8 +20,6 @@ import org.dspace.services.ConfigurationService; import org.dspace.services.RequestService; import org.dspace.services.model.Request; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; import org.springframework.stereotype.Component; @@ -31,7 +31,7 @@ @Component public class VersionHistoryRestPermissionEvaluatorPlugin extends RestObjectPermissionEvaluatorPlugin { - private static final Logger log = LoggerFactory.getLogger(VersionHistoryRestPermissionEvaluatorPlugin.class); + private static final Logger log = LogManager.getLogger(); @Autowired private RequestService requestService; @@ -62,7 +62,7 @@ public boolean hasDSpacePermission(Authentication authentication, Serializable t } return true; } catch (SQLException e) { - log.error(e.getMessage(), e); + log.error(e::getMessage, e); } return false; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/VersionRestPatchPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/VersionRestPatchPermissionEvaluatorPlugin.java index 6cca749cd079..6f1b04b3fd45 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/VersionRestPatchPermissionEvaluatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/VersionRestPatchPermissionEvaluatorPlugin.java @@ -6,11 +6,14 @@ * http://www.dspace.org/license/ */ package org.dspace.app.rest.security; + import java.io.Serializable; import java.sql.SQLException; import java.util.UUID; import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.app.rest.model.VersionRest; import org.dspace.app.rest.utils.ContextUtil; import org.dspace.authorize.service.AuthorizeService; @@ -19,21 +22,19 @@ import org.dspace.services.model.Request; import org.dspace.versioning.Version; import org.dspace.versioning.service.VersioningService; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; import org.springframework.stereotype.Component; /** * This class evaluate ADMIN permissions to patch operation over a Version. - * + * * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.it) */ @Component public class VersionRestPatchPermissionEvaluatorPlugin extends RestObjectPermissionEvaluatorPlugin { - private static final Logger log = LoggerFactory.getLogger(VersionRestPatchPermissionEvaluatorPlugin.class); + private static final Logger log = LogManager.getLogger(); @Autowired private RequestService requestService; @@ -78,7 +79,7 @@ public boolean hasDSpacePermission(Authentication authentication, Serializable t return true; } } catch (SQLException e) { - log.error(e.getMessage(), e); + log.error(e::getMessage, e); } return false; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/VersionRestPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/VersionRestPermissionEvaluatorPlugin.java index 0badd46e2798..f882ce317d72 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/VersionRestPermissionEvaluatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/VersionRestPermissionEvaluatorPlugin.java @@ -12,6 +12,8 @@ import java.util.Objects; import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.app.rest.model.VersionRest; import org.dspace.app.rest.utils.ContextUtil; import org.dspace.authorize.service.AuthorizeService; @@ -21,20 +23,18 @@ import org.dspace.services.model.Request; import org.dspace.versioning.Version; import org.dspace.versioning.service.VersioningService; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; import org.springframework.stereotype.Component; /** * This class acts as a PermissionEvaluator to decide whether a given request to a Versioning endpoint is allowed to - * pass through or not + * pass through or not. */ @Component public class VersionRestPermissionEvaluatorPlugin extends RestObjectPermissionEvaluatorPlugin { - private static final Logger log = LoggerFactory.getLogger(VersionRestPermissionEvaluatorPlugin.class); + private static final Logger log = LogManager.getLogger(); @Autowired private RequestService requestService; @@ -77,7 +77,7 @@ public boolean hasDSpacePermission(Authentication authentication, Serializable t } } catch (SQLException e) { - log.error(e.getMessage(), e); + log.error(e::getMessage, e); } return false; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WebSecurityConfiguration.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WebSecurityConfiguration.java index b29f52939d20..af7116a2bea5 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WebSecurityConfiguration.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WebSecurityConfiguration.java @@ -19,16 +19,17 @@ import org.springframework.context.annotation.Lazy; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; -import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.ProviderManager; +import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.logout.HttpStatusReturningLogoutSuccessHandler; import org.springframework.security.web.authentication.logout.LogoutFilter; -import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy; import org.springframework.security.web.csrf.CsrfTokenRepository; +import org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; /** @@ -40,7 +41,7 @@ @EnableWebSecurity @Configuration @EnableConfigurationProperties(SecurityProperties.class) -public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter { +public class WebSecurityConfiguration { public static final String ADMIN_GRANT = "ADMIN"; public static final String AUTHENTICATED_GRANT = "AUTHENTICATED"; @@ -67,101 +68,105 @@ public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter { @Value("${management.endpoints.web.base-path:/actuator}") private String actuatorBasePath; - @Override - public void configure(WebSecurity webSecurity) throws Exception { - // Define URL patterns which Spring Security will ignore entirely. - webSecurity - .ignoring() - // These /login request types are purposefully unsecured, as they all throw errors. - .antMatchers(HttpMethod.GET, "/api/authn/login") - .antMatchers(HttpMethod.PUT, "/api/authn/login") - .antMatchers(HttpMethod.PATCH, "/api/authn/login") - .antMatchers(HttpMethod.DELETE, "/api/authn/login"); + /** + * Create a Spring Security AuthenticationManager with our custom AuthenticationProvider + * @return AuthenticationManager + */ + @Bean + public AuthenticationManager authenticationManager() { + ProviderManager manager = new ProviderManager(ePersonRestAuthenticationProvider); + return manager; + } + /** + * Bean to customize security on specific endpoints + * @param http HttpSecurity + */ + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + // Get the current AuthenticationManager (defined above) to apply filters below + AuthenticationManager authenticationManager = authenticationManager(); - @Override - protected void configure(HttpSecurity http) throws Exception { // Configure authentication requirements for ${dspace.server.url}/api/ URL only // NOTE: REST API is hardcoded to respond on /api/. Other modules (OAI, SWORD, IIIF, etc) use other root paths. - http.requestMatchers() - .antMatchers("/api/**", "/iiif/**", actuatorBasePath + "/**", "/signposting/**") - .and() - // Enable Spring Security authorization on these paths - .authorizeRequests() - // Allow POST by anyone on the login endpoint - .antMatchers(HttpMethod.POST,"/api/authn/login").permitAll() - // Everyone can call GET on the status endpoint (used to check your authentication status) - .antMatchers(HttpMethod.GET, "/api/authn/status").permitAll() - .antMatchers(HttpMethod.GET, actuatorBasePath + "/info").hasAnyAuthority(ADMIN_GRANT) - .and() + http.securityMatcher("/api/**", "/iiif/**", actuatorBasePath + "/**", "/signposting/**") + .authorizeHttpRequests((requests) -> requests + // Ensure /actuator/info endpoint is restricted to admins + .requestMatchers(new AntPathRequestMatcher(actuatorBasePath + "/info", HttpMethod.GET.name())) + .hasAnyAuthority(ADMIN_GRANT) + // All other requests should be permitted at this layer because we check permissions on each method + // via @PreAuthorize annotations. As this code runs first, we must permitAll() here in order to pass + // the request on to those annotations. + .anyRequest().permitAll()) // Tell Spring to not create Sessions - .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() + .sessionManagement((session) -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // Anonymous requests should have the "ANONYMOUS" security grant - .anonymous().authorities(ANONYMOUS_GRANT).and() + .anonymous((anonymous) -> anonymous.authorities(ANONYMOUS_GRANT)) // Wire up the HttpServletRequest with the current SecurityContext values - .servletApi().and() + .servletApi(Customizer.withDefaults()) // Enable CORS for Spring Security (see CORS settings in Application and ApplicationConfig) - .cors().and() + .cors(Customizer.withDefaults()) // Enable CSRF protection with custom csrfTokenRepository and custom sessionAuthenticationStrategy // (both are defined below as methods). // While we primarily use JWT in headers, CSRF protection is needed because we also support JWT via Cookies - .csrf() + .csrf((csrf) -> csrf .csrfTokenRepository(this.csrfTokenRepository()) - .sessionAuthenticationStrategy(this.sessionAuthenticationStrategy()) - .and() - .exceptionHandling() + .sessionAuthenticationStrategy(this.dSpaceCsrfAuthenticationStrategy()) + // Disable SpringSecurity BREACH protection, as this is not working well with Cookie-based storage. + // When enabled, BREACH protection causes the CSRF token to grow in size until UI errors occur. + // See https://github.com/DSpace/DSpace/issues/9450 + // NOTE: DSpace doesn't need BREACH protection as it's only necessary when sending the token via a + // request attribute (e.g. "_csrf") which the DSpace UI never does. + .csrfTokenRequestHandler(new CsrfTokenRequestAttributeHandler())) + .exceptionHandling((exceptionHandling) -> exceptionHandling // Return 401 on authorization failures with a correct WWWW-Authenticate header .authenticationEntryPoint(new DSpace401AuthenticationEntryPoint(restAuthenticationService)) // Custom handler for AccessDeniedExceptions, including CSRF exceptions .accessDeniedHandler(accessDeniedHandler) - .and() - + ) // Logout configuration - .logout() + .logout((logout) -> logout // On logout, clear the "session" salt .addLogoutHandler(customLogoutHandler) // Configure the logout entry point & require POST .logoutRequestMatcher(new AntPathRequestMatcher("/api/authn/logout", HttpMethod.POST.name())) // When logout is successful, return OK (204) status .logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler(HttpStatus.NO_CONTENT)) - // Everyone can call this endpoint - .permitAll() - .and() - + ) // Add a filter before any request to handle DSpace IP-based authorization/authentication // (e.g. anonymous users may be added to special DSpace groups if they are in a given IP range) - .addFilterBefore(new AnonymousAdditionalAuthorizationFilter(authenticationManager(), authenticationService), + .addFilterBefore(new AnonymousAdditionalAuthorizationFilter(authenticationManager, authenticationService), StatelessAuthenticationFilter.class) - // Add a filter before our login endpoints to do the authentication based on the data in the HTTP request - .addFilterBefore(new StatelessLoginFilter("/api/authn/login", authenticationManager(), - restAuthenticationService), + // Add a filter before our login endpoints to do the authentication based on the data in the HTTP request. + // This login endpoint only responds to POST as it is used for PasswordAuthentication + .addFilterBefore(new StatelessLoginFilter("/api/authn/login", HttpMethod.POST.name(), + authenticationManager, restAuthenticationService), LogoutFilter.class) - // Add a filter before our shibboleth endpoints to do the authentication based on the data in the - // HTTP request - .addFilterBefore(new ShibbolethLoginFilter("/api/authn/shibboleth", authenticationManager(), - restAuthenticationService), + // Add a filter before our shibboleth endpoints to do the authentication based on the data in the HTTP + // request. This endpoint only responds to GET as the actual authentication is performed by Shibboleth, + // which then redirects to this endpoint to forward the authentication data to DSpace. + .addFilterBefore(new ShibbolethLoginFilter("/api/authn/shibboleth", HttpMethod.GET.name(), + authenticationManager, restAuthenticationService), LogoutFilter.class) - //Add a filter before our ORCID endpoints to do the authentication based on the data in the - // HTTP request - .addFilterBefore(new OrcidLoginFilter("/api/authn/orcid", authenticationManager(), - restAuthenticationService), + // Add a filter before our ORCID endpoints to do the authentication based on the data in the HTTP request. + // This endpoint only responds to GET as the actual authentication is performed by ORCID, which then + // redirects to this endpoint to forward the authentication data to DSpace. + .addFilterBefore(new OrcidLoginFilter("/api/authn/orcid", HttpMethod.GET.name(), + authenticationManager, restAuthenticationService), LogoutFilter.class) - //Add a filter before our OIDC endpoints to do the authentication based on the data in the - // HTTP request - .addFilterBefore(new OidcLoginFilter("/api/authn/oidc", authenticationManager(), - restAuthenticationService), + // Add a filter before our OIDC endpoints to do the authentication based on the data in the HTTP request. + // This endpoint only responds to GET as the actual authentication is performed by OIDC, which then + // redirects to this endpoint to forward the authentication data to DSpace. + .addFilterBefore(new OidcLoginFilter("/api/authn/oidc", HttpMethod.GET.name(), + authenticationManager, restAuthenticationService), LogoutFilter.class) // Add a custom Token based authentication filter based on the token previously given to the client // before each URL - .addFilterBefore(new StatelessAuthenticationFilter(authenticationManager(), restAuthenticationService, + .addFilterBefore(new StatelessAuthenticationFilter(authenticationManager, restAuthenticationService, ePersonRestAuthenticationProvider, requestService), StatelessLoginFilter.class); - } - - @Override - protected void configure(AuthenticationManagerBuilder auth) throws Exception { - auth.authenticationProvider(ePersonRestAuthenticationProvider); + return http.build(); } /** @@ -187,8 +192,13 @@ public CsrfTokenRepository csrfTokenRepository() { /** * Returns a custom DSpaceCsrfAuthenticationStrategy, which ensures that (after authenticating) the CSRF token * is only refreshed when it is used (or attempted to be used) by the client. + * + * This is defined as a bean so that it can also be used in other code to reset CSRF Tokens, see + * JWTTokenRestAuthenticationServiceImpl */ - private SessionAuthenticationStrategy sessionAuthenticationStrategy() { + @Lazy + @Bean + public DSpaceCsrfAuthenticationStrategy dSpaceCsrfAuthenticationStrategy() { return new DSpaceCsrfAuthenticationStrategy(csrfTokenRepository()); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WebSecurityExpressionEvaluator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WebSecurityExpressionEvaluator.java index 364e93e297d8..35772e41fd67 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WebSecurityExpressionEvaluator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WebSecurityExpressionEvaluator.java @@ -8,10 +8,10 @@ package org.dspace.app.rest.security; import java.util.List; -import javax.servlet.FilterChain; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.FilterChain; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.springframework.core.GenericTypeResolver; import org.springframework.expression.EvaluationContext; import org.springframework.expression.Expression; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WorkflowRestPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WorkflowRestPermissionEvaluatorPlugin.java index 626290fdc3bb..b59dbdc2f85a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WorkflowRestPermissionEvaluatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WorkflowRestPermissionEvaluatorPlugin.java @@ -12,6 +12,8 @@ import java.sql.SQLException; import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.app.rest.model.WorkflowItemRest; import org.dspace.app.rest.utils.ContextUtil; import org.dspace.authorize.AuthorizeException; @@ -25,8 +27,6 @@ import org.dspace.xmlworkflow.storedcomponents.service.ClaimedTaskService; import org.dspace.xmlworkflow.storedcomponents.service.PoolTaskService; import org.dspace.xmlworkflow.storedcomponents.service.XmlWorkflowItemService; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; import org.springframework.stereotype.Component; @@ -34,13 +34,13 @@ /** * An authenticated user is allowed to interact with workflow item only if they belong to a task that they own or could * claim. - * + * * @author Andrea Bollini (andrea.bollini at 4science.it) */ @Component public class WorkflowRestPermissionEvaluatorPlugin extends RestObjectPermissionEvaluatorPlugin { - private static final Logger log = LoggerFactory.getLogger(WorkflowRestPermissionEvaluatorPlugin.class); + private static final Logger log = LogManager.getLogger(); @Autowired private RequestService requestService; @@ -99,7 +99,7 @@ public boolean hasDSpacePermission(Authentication authentication, Serializable t } } catch (SQLException | AuthorizeException | IOException e) { - log.error(e.getMessage(), e); + log.error(e::getMessage, e); } return false; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WorkspaceItemRestPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WorkspaceItemRestPermissionEvaluatorPlugin.java index c0efbd60f204..ba4387835e24 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WorkspaceItemRestPermissionEvaluatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WorkspaceItemRestPermissionEvaluatorPlugin.java @@ -11,6 +11,8 @@ import java.sql.SQLException; import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.app.rest.model.WorkspaceItemRest; import org.dspace.app.rest.utils.ContextUtil; import org.dspace.authorize.service.AuthorizeService; @@ -21,21 +23,19 @@ import org.dspace.services.RequestService; import org.dspace.services.model.Request; import org.dspace.supervision.service.SupervisionOrderService; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; import org.springframework.stereotype.Component; /** - * {@link RestPermissionEvaluatorPlugin} class that evaluate READ, WRITE and DELETE permissions over a WorkspaceItem - * + * {@link RestPermissionEvaluatorPlugin} class that evaluate READ, WRITE and DELETE permissions over a WorkspaceItem. + * * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.it) */ @Component public class WorkspaceItemRestPermissionEvaluatorPlugin extends RestObjectPermissionEvaluatorPlugin { - private static final Logger log = LoggerFactory.getLogger(WorkspaceItemRestPermissionEvaluatorPlugin.class); + private static final Logger log = LogManager.getLogger(); @Autowired private RequestService requestService; @@ -100,7 +100,7 @@ public boolean hasDSpacePermission(Authentication authentication, Serializable t } } catch (SQLException e) { - log.error(e.getMessage(), e); + log.error(e::getMessage, e); } return false; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/AuthenticationMethodClaimProvider.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/AuthenticationMethodClaimProvider.java index 84b8259dce79..6e1469fa9153 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/AuthenticationMethodClaimProvider.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/AuthenticationMethodClaimProvider.java @@ -9,13 +9,13 @@ import java.sql.SQLException; import java.text.ParseException; -import javax.servlet.http.HttpServletRequest; import com.nimbusds.jwt.JWTClaimsSet; +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.core.Context; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -27,15 +27,17 @@ public class AuthenticationMethodClaimProvider implements JWTClaimProvider { public static final String AUTHENTICATION_METHOD = "authenticationMethod"; - private static final Logger log = LoggerFactory.getLogger(AuthenticationMethodClaimProvider.class); + private static final Logger log = LogManager.getLogger(); @Autowired private AuthenticationService authenticationService; + @Override public String getKey() { return AUTHENTICATION_METHOD; } + @Override public Object getValue(final Context context, final HttpServletRequest request) { if (context.getAuthenticationMethod() != null) { return context.getAuthenticationMethod(); @@ -43,12 +45,13 @@ public Object getValue(final Context context, final HttpServletRequest request) return authenticationService.getAuthenticationMethod(context, request); } + @Override public void parseClaim(final Context context, final HttpServletRequest request, final JWTClaimsSet jwtClaimsSet) throws SQLException { try { context.setAuthenticationMethod(jwtClaimsSet.getStringClaim(AUTHENTICATION_METHOD)); } catch (ParseException e) { - log.error(e.getMessage(), e); + log.error(e::getMessage, e); } } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/EPersonClaimProvider.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/EPersonClaimProvider.java index 39b61427b7bd..9a9142fbd1b1 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/EPersonClaimProvider.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/EPersonClaimProvider.java @@ -9,9 +9,9 @@ import java.sql.SQLException; import java.util.UUID; -import javax.servlet.http.HttpServletRequest; import com.nimbusds.jwt.JWTClaimsSet; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.core.Context; import org.dspace.eperson.EPerson; import org.dspace.eperson.service.EPersonService; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTClaimProvider.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTClaimProvider.java index f8ee6a44de3a..64df3b20eaf6 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTClaimProvider.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTClaimProvider.java @@ -8,9 +8,9 @@ package org.dspace.app.rest.security.jwt; import java.sql.SQLException; -import javax.servlet.http.HttpServletRequest; import com.nimbusds.jwt.JWTClaimsSet; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.core.Context; /** diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenHandler.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenHandler.java index 6beab1aa853d..727267744fb1 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenHandler.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenHandler.java @@ -11,7 +11,6 @@ import java.text.ParseException; import java.util.Date; import java.util.List; -import javax.servlet.http.HttpServletRequest; import com.nimbusds.jose.CompressionAlgorithm; import com.nimbusds.jose.EncryptionMethod; @@ -31,16 +30,17 @@ import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.SignedJWT; import com.nimbusds.jwt.util.DateUtils; +import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.codec.binary.Base64; 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; import org.dspace.eperson.service.EPersonService; 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; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.crypto.keygen.BytesKeyGenerator; @@ -59,7 +59,7 @@ public abstract class JWTTokenHandler { private static final int MAX_CLOCK_SKEW_SECONDS = 60; private static final String AUTHORIZATION_TOKEN_PARAMETER = "authentication-token"; - private static final Logger log = LoggerFactory.getLogger(JWTTokenHandler.class); + private static final Logger log = LogManager.getLogger(); @Autowired private List jwtClaimProviders; @@ -135,7 +135,7 @@ public EPerson parseEPersonFromToken(String token, HttpServletRequest request, C // As long as the JWT is valid, parse all claims and return the EPerson if (isValidToken(request, signedJWT, jwtClaimsSet, ePerson)) { - log.debug("Received valid token for username: " + ePerson.getEmail()); + log.debug("Received valid token for username: {}", ePerson::getEmail); for (JWTClaimProvider jwtClaimProvider : jwtClaimProviders) { jwtClaimProvider.parseClaim(context, request, jwtClaimsSet); @@ -143,7 +143,7 @@ public EPerson parseEPersonFromToken(String token, HttpServletRequest request, C return ePerson; } else { - log.warn(getIpAddress(request) + " tried to use an expired or non-valid token"); + log.warn("{} tried to use an expired or non-valid token", getIpAddress(request)); return null; } } @@ -155,7 +155,8 @@ public EPerson parseEPersonFromToken(String token, HttpServletRequest request, C * @param request current Request * @param previousLoginDate date of last login (before this one) * @return string version of signed JWT - * @throws JOSEException + * @throws JOSEException passed through. + * @throws SQLException passed through. */ public String createTokenForEPerson(Context context, HttpServletRequest request, Date previousLoginDate) throws JOSEException, SQLException { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenRestAuthenticationServiceImpl.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenRestAuthenticationServiceImpl.java index c28729ff83a8..a2928fc96fb1 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenRestAuthenticationServiceImpl.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenRestAuthenticationServiceImpl.java @@ -11,15 +11,18 @@ import java.sql.SQLException; import java.text.ParseException; import java.util.Iterator; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.ws.rs.core.HttpHeaders; import com.nimbusds.jose.JOSEException; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.ws.rs.core.HttpHeaders; import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.app.rest.model.wrapper.AuthenticationToken; import org.dspace.app.rest.security.DSpaceAuthentication; +import org.dspace.app.rest.security.DSpaceCsrfAuthenticationStrategy; import org.dspace.app.rest.security.RestAuthenticationService; import org.dspace.app.rest.utils.ContextUtil; import org.dspace.authenticate.AuthenticationMethod; @@ -27,14 +30,10 @@ import org.dspace.core.Context; import org.dspace.eperson.EPerson; import org.dspace.eperson.service.EPersonService; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.http.ResponseCookie; -import org.springframework.security.web.csrf.CsrfToken; -import org.springframework.security.web.csrf.CsrfTokenRepository; import org.springframework.stereotype.Component; /** @@ -47,7 +46,7 @@ @Component public class JWTTokenRestAuthenticationServiceImpl implements RestAuthenticationService, InitializingBean { - private static final Logger log = LoggerFactory.getLogger(RestAuthenticationService.class); + private static final Logger log = LogManager.getLogger(); private static final String AUTHORIZATION_COOKIE = "Authorization-cookie"; private static final String AUTHORIZATION_HEADER = "Authorization"; private static final String AUTHORIZATION_TYPE = "Bearer"; @@ -67,7 +66,7 @@ public class JWTTokenRestAuthenticationServiceImpl implements RestAuthentication @Lazy @Autowired - private CsrfTokenRepository csrfTokenRepository; + private DSpaceCsrfAuthenticationStrategy dspaceCsrfAuthenticationStrategy; @Override public void afterPropertiesSet() throws Exception { @@ -332,11 +331,8 @@ private boolean hasAuthorizationCookie(HttpServletRequest request) { * @param response current response */ private void resetCSRFToken(HttpServletRequest request, HttpServletResponse response) { - // Remove current CSRF token & generate a new one - // We do this as we want the token to change anytime you login or logout - csrfTokenRepository.saveToken(null, request, response); - CsrfToken newToken = csrfTokenRepository.generateToken(request); - csrfTokenRepository.saveToken(newToken, request, response); + // Use our custom CsrfAuthenticationStrategy class to force reset the CSRF token in Spring Security + dspaceCsrfAuthenticationStrategy.resetCSRFToken(request, response); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/ShortLivedJWTTokenHandler.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/ShortLivedJWTTokenHandler.java index fc4ab39407a4..ac7a73a796ef 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/ShortLivedJWTTokenHandler.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/ShortLivedJWTTokenHandler.java @@ -8,7 +8,6 @@ package org.dspace.app.rest.security.jwt; import java.util.Date; -import javax.servlet.http.HttpServletRequest; import com.nimbusds.jose.JOSEException; import com.nimbusds.jose.JWSVerifier; @@ -16,6 +15,7 @@ import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.SignedJWT; import com.nimbusds.jwt.util.DateUtils; +import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; import org.dspace.core.Context; import org.dspace.eperson.EPerson; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/SpecialGroupClaimProvider.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/SpecialGroupClaimProvider.java index 2ce558133db5..970bd812d3d8 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/SpecialGroupClaimProvider.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/SpecialGroupClaimProvider.java @@ -13,15 +13,15 @@ import java.util.List; import java.util.UUID; import java.util.stream.Collectors; -import javax.servlet.http.HttpServletRequest; import com.nimbusds.jwt.JWTClaimsSet; +import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.collections4.CollectionUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.authenticate.service.AuthenticationService; import org.dspace.core.Context; import org.dspace.eperson.Group; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -34,17 +34,19 @@ @Component public class SpecialGroupClaimProvider implements JWTClaimProvider { - private static final Logger log = LoggerFactory.getLogger(SpecialGroupClaimProvider.class); + private static final Logger log = LogManager.getLogger(); public static final String SPECIAL_GROUPS = "sg"; @Autowired private AuthenticationService authenticationService; + @Override public String getKey() { return SPECIAL_GROUPS; } + @Override public Object getValue(Context context, HttpServletRequest request) { List groups = new ArrayList<>(); try { @@ -57,6 +59,7 @@ public Object getValue(Context context, HttpServletRequest request) { return groupIds; } + @Override public void parseClaim(Context context, HttpServletRequest request, JWTClaimsSet jwtClaimsSet) { try { List groupIds = jwtClaimsSet.getStringListClaim(SPECIAL_GROUPS); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/package-info.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/package-info.java new file mode 100644 index 000000000000..229a6d0c12b5 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/package-info.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/ + */ +/** + * DSpace-specific concepts and behaviors to support Spring Security. + * These may be used by Spring EL expressions in Spring Security annotations. + * + *

    {@code hasPermission} terms are evaluated by + * {@link DSpacePermissionEvaluator}, an implementation of Spring's + * {@link PermissionEvaluator}. It tests access to specific model objects + * (Item, EPerson etc.) using those objects' policies. It is injected with a + * collection of {@link RestPermissionEvaluatorPlugin}s which do the work. + * + *

    {@code hasAuthority} terms are implemented by {@link GrantedAuthority} + * implementations such as {@link EPersonRestAuthenticationProvider}. These + * test for authorization properties of the session itself, such as membership + * in the site administrators group. + * + *

    {@code *PermissionEvaluatorPlugin} classes test permission for specific + * types of objects. They implement {@link RestPermissionEvaluatorPlugin}. + * + *

    Other classes TBD: + *

      + *
    • *Filter
    • + *
    • *Configuration
    • + *
    + */ +package org.dspace.app.rest.security; + +import org.springframework.security.access.PermissionEvaluator; +import org.springframework.security.core.GrantedAuthority; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/controller/LinksetRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/controller/LinksetRestController.java index 2a940d79aba4..b1731fb10bb7 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/controller/LinksetRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/controller/LinksetRestController.java @@ -16,9 +16,9 @@ import java.util.List; import java.util.UUID; import java.util.stream.Collectors; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.dspace.app.rest.converter.ConverterService; import org.dspace.app.rest.signposting.converter.LinksetRestMessageConverter; import org.dspace.app.rest.signposting.model.Linkset; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/LinksetRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/LinksetRest.java index df80cd5c2d50..d1123f50f3de 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/LinksetRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/LinksetRest.java @@ -49,6 +49,11 @@ public String getType() { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @Override public String getCategory() { return CATEGORY; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/TypedLinkRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/TypedLinkRest.java index 3ba09bf1094c..ce6668920a9c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/TypedLinkRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/TypedLinkRest.java @@ -62,6 +62,11 @@ public String getType() { return type; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @Override public String getCategory() { return CATEGORY; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/SignPostingProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/SignPostingProcessor.java index efcfd50ab512..a5cadf4627df 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/SignPostingProcessor.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/SignPostingProcessor.java @@ -8,8 +8,8 @@ package org.dspace.app.rest.signposting.processor; import java.util.List; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.signposting.model.LinksetNode; import org.dspace.content.DSpaceObject; import org.dspace.core.Context; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/bitstream/BitstreamLinksetProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/bitstream/BitstreamLinksetProcessor.java index c65191cb0749..349dde7b6dac 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/bitstream/BitstreamLinksetProcessor.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/bitstream/BitstreamLinksetProcessor.java @@ -8,9 +8,10 @@ package org.dspace.app.rest.signposting.processor.bitstream; import java.util.List; -import javax.servlet.http.HttpServletRequest; -import org.apache.log4j.Logger; +import jakarta.servlet.http.HttpServletRequest; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.app.rest.signposting.model.LinksetNode; import org.dspace.app.rest.signposting.model.LinksetRelationType; import org.dspace.content.Bitstream; @@ -25,7 +26,7 @@ */ public class BitstreamLinksetProcessor extends BitstreamSignpostingProcessor { - private static final Logger log = Logger.getLogger(BitstreamLinksetProcessor.class); + private static final Logger log = LogManager.getLogger(BitstreamLinksetProcessor.class); private final BitstreamService bitstreamService; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/bitstream/BitstreamParentItemProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/bitstream/BitstreamParentItemProcessor.java index 815d7817d4cf..c855f06784f7 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/bitstream/BitstreamParentItemProcessor.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/bitstream/BitstreamParentItemProcessor.java @@ -8,9 +8,10 @@ package org.dspace.app.rest.signposting.processor.bitstream; import java.util.List; -import javax.servlet.http.HttpServletRequest; -import org.apache.log4j.Logger; +import jakarta.servlet.http.HttpServletRequest; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.app.rest.signposting.model.LinksetNode; import org.dspace.app.rest.signposting.model.LinksetRelationType; import org.dspace.content.Bitstream; @@ -28,7 +29,7 @@ */ public class BitstreamParentItemProcessor extends BitstreamSignpostingProcessor { - private static final Logger log = Logger.getLogger(BitstreamParentItemProcessor.class); + private static final Logger log = LogManager.getLogger(BitstreamParentItemProcessor.class); private final BitstreamService bitstreamService; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/bitstream/BitstreamTypeProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/bitstream/BitstreamTypeProcessor.java index 005a8009836d..d0f170b4c55a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/bitstream/BitstreamTypeProcessor.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/bitstream/BitstreamTypeProcessor.java @@ -8,10 +8,11 @@ package org.dspace.app.rest.signposting.processor.bitstream; import java.util.List; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; -import org.apache.log4j.Logger; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.app.rest.signposting.model.LinksetNode; import org.dspace.app.rest.signposting.model.LinksetRelationType; import org.dspace.content.Bitstream; @@ -28,7 +29,7 @@ */ public class BitstreamTypeProcessor extends BitstreamSignpostingProcessor { - private static final Logger log = Logger.getLogger(BitstreamTypeProcessor.class); + private static final Logger log = LogManager.getLogger(BitstreamTypeProcessor.class); @Autowired private SimpleMapConverter mapConverterDSpaceToSchemaOrgUri; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemAuthorProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemAuthorProcessor.java index 1bb215c46864..ebc7c46f4d23 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemAuthorProcessor.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemAuthorProcessor.java @@ -14,9 +14,10 @@ import java.text.MessageFormat; import java.util.List; -import javax.servlet.http.HttpServletRequest; -import org.apache.log4j.Logger; +import jakarta.servlet.http.HttpServletRequest; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.app.rest.signposting.model.LinksetNode; import org.dspace.app.rest.signposting.model.LinksetRelationType; import org.dspace.content.Item; @@ -37,7 +38,7 @@ public class ItemAuthorProcessor extends ItemSignpostingProcessor { /** * log4j category */ - private static final Logger log = Logger.getLogger(ItemAuthorProcessor.class); + private static final Logger log = LogManager.getLogger(ItemAuthorProcessor.class); private final ItemService itemService; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemContentBitstreamsProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemContentBitstreamsProcessor.java index 61bf371adbdf..65a29c2b6c98 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemContentBitstreamsProcessor.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemContentBitstreamsProcessor.java @@ -9,9 +9,10 @@ import java.sql.SQLException; import java.util.List; -import javax.servlet.http.HttpServletRequest; -import org.apache.log4j.Logger; +import jakarta.servlet.http.HttpServletRequest; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.app.rest.signposting.model.LinksetNode; import org.dspace.app.rest.signposting.model.LinksetRelationType; import org.dspace.content.Bitstream; @@ -33,7 +34,7 @@ public class ItemContentBitstreamsProcessor extends ItemSignpostingProcessor { /** * log4j category */ - private static final Logger log = Logger.getLogger(ItemContentBitstreamsProcessor.class); + private static final Logger log = LogManager.getLogger(ItemContentBitstreamsProcessor.class); public ItemContentBitstreamsProcessor(FrontendUrlService frontendUrlService) { super(frontendUrlService); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemDescribedbyProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemDescribedbyProcessor.java index a16770c4d103..a86b98f2e5d0 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemDescribedbyProcessor.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemDescribedbyProcessor.java @@ -8,9 +8,10 @@ package org.dspace.app.rest.signposting.processor.item; import java.util.List; -import javax.servlet.http.HttpServletRequest; -import org.apache.log4j.Logger; +import jakarta.servlet.http.HttpServletRequest; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.app.rest.signposting.model.LinksetNode; import org.dspace.app.rest.signposting.model.LinksetRelationType; import org.dspace.content.Item; @@ -23,7 +24,7 @@ */ public class ItemDescribedbyProcessor extends ItemSignpostingProcessor { - private static final Logger log = Logger.getLogger(ItemDescribedbyProcessor.class); + private static final Logger log = LogManager.getLogger(ItemDescribedbyProcessor.class); private final ConfigurationService configurationService; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemIdentifierProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemIdentifierProcessor.java index c5ebe958d97d..ba4794f37813 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemIdentifierProcessor.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemIdentifierProcessor.java @@ -12,8 +12,8 @@ import java.text.MessageFormat; import java.util.List; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.signposting.model.LinksetNode; import org.dspace.app.rest.signposting.model.LinksetRelationType; import org.dspace.content.Item; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemLicenseProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemLicenseProcessor.java index 1a26fa7695b1..6e26d8f1b225 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemLicenseProcessor.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemLicenseProcessor.java @@ -8,10 +8,11 @@ package org.dspace.app.rest.signposting.processor.item; import java.util.List; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; -import org.apache.log4j.Logger; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.app.rest.signposting.model.LinksetNode; import org.dspace.app.rest.signposting.model.LinksetRelationType; import org.dspace.content.Item; @@ -25,7 +26,7 @@ */ public class ItemLicenseProcessor extends ItemSignpostingProcessor { - private static final Logger log = Logger.getLogger(ItemLicenseProcessor.class); + private static final Logger log = LogManager.getLogger(ItemLicenseProcessor.class); private final CreativeCommonsService creativeCommonsService = LicenseServiceFactory.getInstance().getCreativeCommonsService(); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemLinksetProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemLinksetProcessor.java index 9008a28e29a6..4e48caf9594f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemLinksetProcessor.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemLinksetProcessor.java @@ -8,9 +8,10 @@ package org.dspace.app.rest.signposting.processor.item; import java.util.List; -import javax.servlet.http.HttpServletRequest; -import org.apache.log4j.Logger; +import jakarta.servlet.http.HttpServletRequest; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.app.rest.signposting.model.LinksetNode; import org.dspace.app.rest.signposting.model.LinksetRelationType; import org.dspace.content.Item; @@ -23,7 +24,7 @@ */ public class ItemLinksetProcessor extends ItemSignpostingProcessor { - private static final Logger log = Logger.getLogger(ItemLinksetProcessor.class); + private static final Logger log = LogManager.getLogger(ItemLinksetProcessor.class); private final ConfigurationService configurationService; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemTypeProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemTypeProcessor.java index ddd2da12d59a..f2533a5a9564 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemTypeProcessor.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemTypeProcessor.java @@ -8,10 +8,11 @@ package org.dspace.app.rest.signposting.processor.item; import java.util.List; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; -import org.apache.log4j.Logger; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.app.rest.signposting.model.LinksetNode; import org.dspace.app.rest.signposting.model.LinksetRelationType; import org.dspace.content.Item; @@ -27,7 +28,7 @@ */ public class ItemTypeProcessor extends ItemSignpostingProcessor { - private static final Logger log = Logger.getLogger(ItemTypeProcessor.class); + private static final Logger log = LogManager.getLogger(ItemTypeProcessor.class); private static final String ABOUT_PAGE_URI = "https://schema.org/AboutPage"; @Autowired diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/metadata/MetadataDescribesSignpostingProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/metadata/MetadataDescribesSignpostingProcessor.java index baae16b88389..8bf123dd0478 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/metadata/MetadataDescribesSignpostingProcessor.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/metadata/MetadataDescribesSignpostingProcessor.java @@ -8,8 +8,8 @@ package org.dspace.app.rest.signposting.processor.metadata; import java.util.List; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.signposting.model.LinksetNode; import org.dspace.app.rest.signposting.model.LinksetRelationType; import org.dspace.content.Item; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/service/LinksetService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/service/LinksetService.java index 33d0c10b7415..c6ad757e4e92 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/service/LinksetService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/service/LinksetService.java @@ -8,8 +8,8 @@ package org.dspace.app.rest.signposting.service; import java.util.List; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.signposting.model.LinksetNode; import org.dspace.content.DSpaceObject; import org.dspace.content.Item; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/service/impl/LinksetServiceImpl.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/service/impl/LinksetServiceImpl.java index 399b7bd1e6b0..42b1c8184957 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/service/impl/LinksetServiceImpl.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/service/impl/LinksetServiceImpl.java @@ -11,9 +11,10 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; -import javax.servlet.http.HttpServletRequest; -import org.apache.log4j.Logger; +import jakarta.servlet.http.HttpServletRequest; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.app.rest.security.BitstreamMetadataReadPermissionEvaluatorPlugin; import org.dspace.app.rest.signposting.model.LinksetNode; import org.dspace.app.rest.signposting.processor.bitstream.BitstreamSignpostingProcessor; @@ -37,7 +38,7 @@ @Service public class LinksetServiceImpl implements LinksetService { - private static final Logger log = Logger.getLogger(LinksetServiceImpl.class); + private static final Logger log = LogManager.getLogger(LinksetServiceImpl.class); @Autowired protected ItemService itemService; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/AbstractProcessingStep.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/AbstractProcessingStep.java index 8c03f4ef82f0..bb8c7825e2a2 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/AbstractProcessingStep.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/AbstractProcessingStep.java @@ -7,6 +7,7 @@ */ package org.dspace.app.rest.submit; +import org.dspace.app.rest.submit.factory.impl.NotifySubmissionService; import org.dspace.authorize.factory.AuthorizeServiceFactory; import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.factory.ContentServiceFactory; @@ -19,6 +20,7 @@ import org.dspace.content.service.WorkspaceItemService; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; +import org.dspace.utils.DSpace; /** * Abstract processing class for DSpace Submission Steps. This retrieve several @@ -35,4 +37,6 @@ public abstract class AbstractProcessingStep implements DataProcessingStep { protected MetadataFieldService metadataFieldService = ContentServiceFactory.getInstance().getMetadataFieldService(); protected ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); protected WorkspaceItemService workspaceItemService = ContentServiceFactory.getInstance().getWorkspaceItemService(); + protected NotifySubmissionService coarNotifySubmissionService = new DSpace().getServiceManager() + .getServiceByName("coarNotifySubmissionService", NotifySubmissionService.class); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/DataProcessingStep.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/DataProcessingStep.java index 99af309cdb9a..660d677cdde5 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/DataProcessingStep.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/DataProcessingStep.java @@ -10,8 +10,8 @@ import java.io.Serializable; import java.util.ArrayList; import java.util.List; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.model.ErrorRest; import org.dspace.app.rest.model.patch.Operation; import org.dspace.app.rest.submit.step.validation.Validation; @@ -39,8 +39,10 @@ public interface DataProcessingStep extends RestProcessingStep { public static final String ACCESS_CONDITION_STEP_OPERATION_ENTRY = "discoverable"; public static final String ACCESS_CONDITION_POLICY_STEP_OPERATION_ENTRY = "accessConditions"; public static final String SHOW_IDENTIFIERS_ENTRY = "identifiers"; + public static final String PRIMARY_FLAG_ENTRY = "primary"; public static final String UPLOAD_STEP_METADATA_PATH = "metadata"; + public static final String COARNOTIFY_STEP_PATH = "coarnotify"; /** * Method to expose data in the a dedicated section of the in progress submission. The step needs to return a diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/PatchConfigurationService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/PatchConfigurationService.java index 186ebd65235c..58e1fbe428de 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/PatchConfigurationService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/PatchConfigurationService.java @@ -12,7 +12,7 @@ import org.dspace.app.rest.submit.factory.impl.PatchOperation; /** - * Class to mantain mapping configuration for PATCH operation needed by the Submission process + * Class to maintain mapping configuration for PATCH operation needed by the Submission process * * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it) */ diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/SubmissionService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/SubmissionService.java index e3dbd566a8c9..66df73e7cd70 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/SubmissionService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/SubmissionService.java @@ -10,13 +10,13 @@ import java.io.IOException; import java.sql.SQLException; import java.util.ArrayList; +import java.util.LinkedList; 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.logging.log4j.Logger; -import org.atteo.evo.inflector.English; import org.dspace.app.rest.converter.ConverterService; import org.dspace.app.rest.exception.DSpaceBadRequestException; import org.dspace.app.rest.exception.RESTAuthorizationException; @@ -27,9 +27,11 @@ import org.dspace.app.rest.model.CheckSumRest; import org.dspace.app.rest.model.ErrorRest; import org.dspace.app.rest.model.MetadataValueRest; +import org.dspace.app.rest.model.PotentialDuplicateRest; import org.dspace.app.rest.model.WorkspaceItemRest; import org.dspace.app.rest.model.patch.Operation; import org.dspace.app.rest.model.step.DataCCLicense; +import org.dspace.app.rest.model.step.DataDuplicateDetection; import org.dspace.app.rest.model.step.DataUpload; import org.dspace.app.rest.model.step.UploadBitstreamRest; import org.dspace.app.rest.projection.Projection; @@ -48,11 +50,14 @@ import org.dspace.content.MetadataValue; import org.dspace.content.WorkspaceItem; import org.dspace.content.service.CollectionService; +import org.dspace.content.service.DuplicateDetectionService; import org.dspace.content.service.ItemService; 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.core.Utils; +import org.dspace.discovery.SearchServiceException; import org.dspace.license.service.CreativeCommonsService; import org.dspace.services.ConfigurationService; import org.dspace.services.RequestService; @@ -65,6 +70,7 @@ import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; import org.springframework.data.rest.webmvc.json.patch.PatchException; import org.springframework.jdbc.datasource.init.UncategorizedScriptException; import org.springframework.stereotype.Component; @@ -102,6 +108,8 @@ public class SubmissionService { @Autowired private org.dspace.app.rest.utils.Utils utils; private SubmissionConfigService submissionConfigService; + @Autowired + private DuplicateDetectionService duplicateDetectionService; public SubmissionService() throws SubmissionConfigReaderException { submissionConfigService = SubmissionServiceFactory.getInstance().getSubmissionConfigService(); @@ -219,7 +227,7 @@ public UploadBitstreamRest buildUploadBitstream(ConfigurationService configurati data.setCheckSum(checksum); data.setSizeBytes(source.getSizeBytes()); data.setUrl(configurationService.getProperty("dspace.server.url") + "/api/" + BitstreamRest.CATEGORY + "/" + - English.plural(BitstreamRest.NAME) + "/" + source.getID() + "/content"); + BitstreamRest.PLURAL_NAME + "/" + source.getID() + "/content"); return data; } @@ -241,7 +249,7 @@ public XmlWorkflowItem createWorkflowItem(Context context, String requestUriList if (StringUtils.isBlank(requestUriListString)) { throw new UnprocessableEntityException("Malformed body..." + requestUriListString); } - String regex = "\\/api\\/" + WorkspaceItemRest.CATEGORY + "\\/" + English.plural(WorkspaceItemRest.NAME) + String regex = "\\/api\\/" + WorkspaceItemRest.CATEGORY + "\\/" + WorkspaceItemRest.PLURAL_NAME + "\\/"; String[] split = requestUriListString.split(regex, 2); if (split.length != 2) { @@ -314,6 +322,51 @@ public DataCCLicense getDataCCLicense(InProgressSubmission obj) return result; } + /** + * Prepare section data containing a list of potential duplicates, for use in submission steps. + * This method belongs in SubmissionService and not DuplicateDetectionService because it depends on + * the DataDuplicateDetection class which only appears in the REST project. + * + * @param context DSpace context + * @param obj The in-progress submission object + * @return A DataDuplicateDetection object which implements SectionData for direct use in + * a submission step (see DuplicateDetectionStep) + * @throws SearchServiceException if an error is encountered during Discovery search + */ + public DataDuplicateDetection getDataDuplicateDetection(Context context, InProgressSubmission obj) + throws SearchServiceException { + // Test for a valid object or throw a not found exception + if (obj == null) { + throw new ResourceNotFoundException("Duplicate data step could not find valid in-progress submission obj"); + } + // Initialise an empty section data object + DataDuplicateDetection data = new DataDuplicateDetection(); + + // Get the item for this submission object, throw a not found exception if null + Item item = obj.getItem(); + if (item == null) { + throw new ResourceNotFoundException("Duplicate data step could not find valid item for the" + + " current in-progress submission obj id=" + obj.getID()); + } + // Initialise empty list of PotentialDuplicateRest objects for use in the section data object + List potentialDuplicateRestList = new LinkedList<>(); + + // Get discovery search result for a duplicate detection search based on this item and populate + // the list of REST objects + List potentialDuplicates = duplicateDetectionService.getPotentialDuplicates(context, item); + for (PotentialDuplicate potentialDuplicate : potentialDuplicates) { + // Convert and add the potential duplicate to the list + potentialDuplicateRestList.add(converter.toRest( + potentialDuplicate, utils.obtainProjection())); + } + + // Set the final duplicates list of the section data object + data.setPotentialDuplicates(potentialDuplicateRestList); + + // Return section data + return data; + } + /** * Utility method used by the {@link WorkspaceItemRestRepository} and * {@link WorkflowItemRestRepository} to deal with the upload in an inprogress diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionAddPatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionAddPatchOperation.java index 750c50524d3e..ae8040136f39 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionAddPatchOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionAddPatchOperation.java @@ -12,8 +12,8 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.AccessConditionDTO; import org.dspace.app.rest.model.patch.LateObjectEvaluator; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionDiscoverableReplacePatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionDiscoverableReplacePatchOperation.java index bcc0dce65fac..188a5e8dbe86 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionDiscoverableReplacePatchOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionDiscoverableReplacePatchOperation.java @@ -7,8 +7,8 @@ */ package org.dspace.app.rest.submit.factory.impl; import java.util.Objects; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.lang3.BooleanUtils; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.content.InProgressSubmission; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionRemovePatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionRemovePatchOperation.java index 61ed7e28c9ac..894a8234dc4b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionRemovePatchOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionRemovePatchOperation.java @@ -7,8 +7,8 @@ */ package org.dspace.app.rest.submit.factory.impl; import java.util.List; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.AccessConditionDTO; import org.dspace.authorize.ResourcePolicy; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionReplacePatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionReplacePatchOperation.java index d2529cbca303..ff34559a903b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionReplacePatchOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionReplacePatchOperation.java @@ -14,9 +14,11 @@ import java.util.Date; import java.util.List; import java.util.Objects; -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; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.AccessConditionDTO; import org.dspace.app.rest.model.patch.JsonValueEvaluator; @@ -31,8 +33,6 @@ import org.dspace.submit.model.AccessConditionConfigurationService; import org.dspace.submit.model.AccessConditionOption; import org.dspace.util.TimeHelpers; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; /** @@ -42,7 +42,7 @@ */ public class AccessConditionReplacePatchOperation extends ReplacePatchOperation { - private static final Logger log = LoggerFactory.getLogger(AccessConditionReplacePatchOperation.class); + private static final Logger log = LogManager.getLogger(); @Autowired private ResourcePolicyService resourcePolicyService; @@ -157,7 +157,7 @@ private Date parseDate(String date) { try { return pattern.parse(date); } catch (ParseException e) { - log.error(e.getMessage(), e); + log.error(e::getMessage, e); } } throw new UnprocessableEntityException("Provided format of date:" + date + " is not supported!"); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AddPatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AddPatchOperation.java index 354208c3e536..d903325edaff 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AddPatchOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AddPatchOperation.java @@ -7,8 +7,7 @@ */ package org.dspace.app.rest.submit.factory.impl; -import javax.servlet.http.HttpServletRequest; - +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.model.patch.Operation; import org.dspace.content.InProgressSubmission; import org.dspace.core.Context; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/BitstreamMetadataValueAddPatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/BitstreamMetadataValueAddPatchOperation.java index 38ec37e7d7a0..ad3fd06dbbf7 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/BitstreamMetadataValueAddPatchOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/BitstreamMetadataValueAddPatchOperation.java @@ -8,8 +8,8 @@ package org.dspace.app.rest.submit.factory.impl; import java.util.List; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.model.MetadataValueRest; import org.dspace.app.rest.model.patch.LateObjectEvaluator; import org.dspace.app.rest.utils.BitstreamMetadataValuePathUtils; @@ -48,12 +48,13 @@ void add(Context context, HttpServletRequest currentRequest, InProgressSubmissio throws Exception { //"path": "/sections/upload/files/0/metadata/dc.title/2" //"abspath": "/files/0/metadata/dc.title/2" + String stepId = getStepId(path); String absolutePath = getAbsolutePath(path); String[] split = absolutePath.split("/"); - bitstreamMetadataValuePathUtils.validate(absolutePath); + bitstreamMetadataValuePathUtils.validate(stepId, absolutePath); Item item = source.getItem(); List bundle = itemService.getBundles(item, Constants.CONTENT_BUNDLE_NAME); - ; + for (Bundle bb : bundle) { int idx = 0; for (Bitstream b : bb.getBitstreams()) { @@ -67,10 +68,11 @@ void add(Context context, HttpServletRequest currentRequest, InProgressSubmissio // call with "-" or "index-based" we should receive only single // object member MetadataValueRest object = evaluateSingleObject((LateObjectEvaluator) value); + String mdString = split[3]; // check if is not empty List metadataByMetadataString = - bitstreamService.getMetadataByMetadataString(b,split[3]); - Assert.notEmpty(metadataByMetadataString); + bitstreamService.getMetadataByMetadataString(b,mdString); + Assert.notEmpty(metadataByMetadataString, "No metadata fields match ".concat(mdString)); if (split.length > 4) { String controlChar = split[4]; switch (controlChar) { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/BitstreamMetadataValueMovePatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/BitstreamMetadataValueMovePatchOperation.java index 2726363b4c0e..9db1ab105b9d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/BitstreamMetadataValueMovePatchOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/BitstreamMetadataValueMovePatchOperation.java @@ -8,8 +8,8 @@ package org.dspace.app.rest.submit.factory.impl; import java.util.List; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.utils.BitstreamMetadataValuePathUtils; import org.dspace.content.Bitstream; import org.dspace.content.Bundle; @@ -44,9 +44,10 @@ void move(Context context, HttpServletRequest currentRequest, InProgressSubmissi throws Exception { //"path": "/sections/upload/files/0/metadata/dc.title/2" //"abspath": "/files/0/metadata/dc.title/2" + String stepId = getStepId(path); String absolutePath = getAbsolutePath(path); String[] splitTo = absolutePath.split("/"); - bitstreamMetadataValuePathUtils.validate(absolutePath); + bitstreamMetadataValuePathUtils.validate(stepId, absolutePath); Item item = source.getItem(); List bundle = itemService.getBundles(item, Constants.CONTENT_BUNDLE_NAME); for (Bundle bb : bundle) { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/BitstreamMetadataValueRemovePatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/BitstreamMetadataValueRemovePatchOperation.java index 6bbb7bb0328a..65a962626225 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/BitstreamMetadataValueRemovePatchOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/BitstreamMetadataValueRemovePatchOperation.java @@ -8,8 +8,8 @@ package org.dspace.app.rest.submit.factory.impl; import java.util.List; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.utils.BitstreamMetadataValuePathUtils; import org.dspace.content.Bitstream; import org.dspace.content.Bundle; @@ -44,9 +44,10 @@ void remove(Context context, HttpServletRequest currentRequest, InProgressSubmis Object value) throws Exception { //"path": "/sections/upload/files/0/metadata/dc.title/2" //"abspath": "/files/0/metadata/dc.title/2" + String stepId = getStepId(path); String absolutePath = getAbsolutePath(path); String[] split = absolutePath.split("/"); - bitstreamMetadataValuePathUtils.validate(absolutePath); + bitstreamMetadataValuePathUtils.validate(stepId, absolutePath); Item item = source.getItem(); List bundle = itemService.getBundles(item, Constants.CONTENT_BUNDLE_NAME); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/BitstreamMetadataValueReplacePatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/BitstreamMetadataValueReplacePatchOperation.java index 842771538c8b..eefecf9d565f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/BitstreamMetadataValueReplacePatchOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/BitstreamMetadataValueReplacePatchOperation.java @@ -9,8 +9,8 @@ import java.sql.SQLException; import java.util.List; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.model.MetadataValueRest; import org.dspace.app.rest.model.patch.LateObjectEvaluator; import org.dspace.app.rest.utils.BitstreamMetadataValuePathUtils; @@ -47,9 +47,10 @@ void replace(Context context, HttpServletRequest currentRequest, InProgressSubmi Object value) throws Exception { //"path": "/sections/upload/files/0/metadata/dc.title/2" //"abspath": "/files/0/metadata/dc.title/2" + String stepId = getStepId(path); String absolutePath = getAbsolutePath(path); String[] split = absolutePath.split("/"); - bitstreamMetadataValuePathUtils.validate(absolutePath); + bitstreamMetadataValuePathUtils.validate(stepId, absolutePath); Item item = source.getItem(); List bundle = itemService.getBundles(item, Constants.CONTENT_BUNDLE_NAME); for (Bundle bb : bundle) { @@ -67,7 +68,7 @@ private void replace(Context context, Bitstream b, String[] split, Object value) throws SQLException, IllegalArgumentException, IllegalAccessException { String mdString = split[3]; List metadataByMetadataString = bitstreamService.getMetadataByMetadataString(b, mdString); - Assert.notEmpty(metadataByMetadataString); + Assert.notEmpty(metadataByMetadataString, "No metadata fields match ".concat(mdString)); int index = Integer.parseInt(split[4]); // if split size is one so we have a call to initialize or replace diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/BitstreamMovePatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/BitstreamMovePatchOperation.java index 27d01a8581dc..5b6842b8e708 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/BitstreamMovePatchOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/BitstreamMovePatchOperation.java @@ -7,8 +7,7 @@ */ package org.dspace.app.rest.submit.factory.impl; -import javax.servlet.http.HttpServletRequest; - +import jakarta.servlet.http.HttpServletRequest; import org.dspace.content.InProgressSubmission; import org.dspace.content.service.BundleService; import org.dspace.content.service.ItemService; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/BitstreamRemovePatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/BitstreamRemovePatchOperation.java index 706834930027..ec9d178f14ec 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/BitstreamRemovePatchOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/BitstreamRemovePatchOperation.java @@ -8,8 +8,8 @@ package org.dspace.app.rest.submit.factory.impl; import java.util.List; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.content.Bitstream; import org.dspace.content.Bundle; import org.dspace.content.InProgressSubmission; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/BitstreamResourcePolicyAddPatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/BitstreamResourcePolicyAddPatchOperation.java index 5e6274d78f1e..2d06b66ff748 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/BitstreamResourcePolicyAddPatchOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/BitstreamResourcePolicyAddPatchOperation.java @@ -9,8 +9,8 @@ import java.util.ArrayList; import java.util.List; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.collections.CollectionUtils; import org.dspace.app.rest.model.AccessConditionDTO; import org.dspace.app.rest.model.patch.LateObjectEvaluator; @@ -54,7 +54,7 @@ void add(Context context, HttpServletRequest currentRequest, InProgressSubmissio Item item = source.getItem(); List bundle = itemService.getBundles(item, Constants.CONTENT_BUNDLE_NAME); - ; + UploadConfiguration uploadConfig = uploadConfigurationService.getMap().get(splitPath[2]); for (Bundle bb : bundle) { int idx = 0; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/BitstreamResourcePolicyRemovePatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/BitstreamResourcePolicyRemovePatchOperation.java index e936b0b0c7e9..986d6062bcd6 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/BitstreamResourcePolicyRemovePatchOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/BitstreamResourcePolicyRemovePatchOperation.java @@ -8,8 +8,8 @@ package org.dspace.app.rest.submit.factory.impl; import java.util.List; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.model.AccessConditionDTO; import org.dspace.authorize.ResourcePolicy; import org.dspace.authorize.service.ResourcePolicyService; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/BitstreamResourcePolicyReplacePatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/BitstreamResourcePolicyReplacePatchOperation.java index 19acc7b9f293..565287e9454a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/BitstreamResourcePolicyReplacePatchOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/BitstreamResourcePolicyReplacePatchOperation.java @@ -11,8 +11,8 @@ import java.util.Date; import java.util.Iterator; import java.util.List; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.model.ResourcePolicyRest; import org.dspace.app.rest.model.patch.LateObjectEvaluator; import org.dspace.authorize.ResourcePolicy; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/CCLicenseAddPatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/CCLicenseAddPatchOperation.java index ec4dff9f6c51..dace7c99ed94 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/CCLicenseAddPatchOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/CCLicenseAddPatchOperation.java @@ -7,8 +7,7 @@ */ package org.dspace.app.rest.submit.factory.impl; -import javax.servlet.http.HttpServletRequest; - +import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; import org.dspace.content.InProgressSubmission; import org.dspace.content.Item; @@ -26,7 +25,7 @@ * Example: * curl -X PATCH http://${dspace.server.url}/api/submission/workspaceitems/31599 -H "Content-Type: * application/json" -d '[{ "op": "add", "path": "/sections/cclicense/uri", - * "value":"http://creativecommons.org/licenses/by-nc-sa/3.0/us/"}]' + * "value":"https://creativecommons.org/licenses/by-nc-sa/4.0/us/"}]' * */ public class CCLicenseAddPatchOperation extends AddPatchOperation { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/CCLicenseRemovePatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/CCLicenseRemovePatchOperation.java index b17749fe4e04..57d9b60e3135 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/CCLicenseRemovePatchOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/CCLicenseRemovePatchOperation.java @@ -7,8 +7,7 @@ */ package org.dspace.app.rest.submit.factory.impl; -import javax.servlet.http.HttpServletRequest; - +import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; import org.dspace.content.InProgressSubmission; import org.dspace.content.Item; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/CollectionReplacePatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/CollectionReplacePatchOperation.java index 3cadb443d9cc..a26982c37760 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/CollectionReplacePatchOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/CollectionReplacePatchOperation.java @@ -8,8 +8,8 @@ package org.dspace.app.rest.submit.factory.impl; import java.sql.SQLException; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.util.DCInputsReaderException; import org.dspace.content.Collection; import org.dspace.content.InProgressSubmission; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/ItemMetadataValueAddPatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/ItemMetadataValueAddPatchOperation.java index e749c4e79328..8ab9bb451647 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/ItemMetadataValueAddPatchOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/ItemMetadataValueAddPatchOperation.java @@ -12,8 +12,8 @@ import java.util.List; import java.util.Map; import java.util.Optional; -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.app.rest.exception.UnprocessableEntityException; @@ -103,10 +103,11 @@ void add(Context context, HttpServletRequest currentRequest, InProgressSubmissio // call with "-" or "index-based" we should receive only single // object member MetadataValueRest object = evaluateSingleObject((LateObjectEvaluator) value); + String mdString = split[0]; // check if is not empty List metadataByMetadataString = itemService.getMetadataByMetadataString(source.getItem(), - split[0]); - Assert.notEmpty(metadataByMetadataString); + mdString); + Assert.notEmpty(metadataByMetadataString, "No metadata fields match ".concat(mdString)); if (split.length > 1) { String controlChar = split[1]; switch (controlChar) { @@ -213,7 +214,7 @@ private Integer getRelId(String authority) { private void updateRelationshipPlace(Context context, Item dso, int place, Relationship rs) { try { - if (rs.getLeftItem() == dso) { + if (rs.getLeftItem().equals(dso)) { rs.setLeftPlace(place); } else { rs.setRightPlace(place); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/ItemMetadataValueMovePatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/ItemMetadataValueMovePatchOperation.java index 17712f71482c..d38a5346429b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/ItemMetadataValueMovePatchOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/ItemMetadataValueMovePatchOperation.java @@ -7,8 +7,7 @@ */ package org.dspace.app.rest.submit.factory.impl; -import javax.servlet.http.HttpServletRequest; - +import jakarta.servlet.http.HttpServletRequest; import org.dspace.content.InProgressSubmission; import org.dspace.content.Item; import org.dspace.content.service.ItemService; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/ItemMetadataValueRemovePatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/ItemMetadataValueRemovePatchOperation.java index 95206bc5ca9a..cf62f641b940 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/ItemMetadataValueRemovePatchOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/ItemMetadataValueRemovePatchOperation.java @@ -7,8 +7,7 @@ */ package org.dspace.app.rest.submit.factory.impl; -import javax.servlet.http.HttpServletRequest; - +import jakarta.servlet.http.HttpServletRequest; import org.dspace.content.InProgressSubmission; import org.dspace.content.Item; import org.dspace.content.service.ItemService; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/ItemMetadataValueReplacePatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/ItemMetadataValueReplacePatchOperation.java index 148984cbfd4d..415c5b6712ab 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/ItemMetadataValueReplacePatchOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/ItemMetadataValueReplacePatchOperation.java @@ -8,8 +8,8 @@ package org.dspace.app.rest.submit.factory.impl; import java.util.List; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.model.MetadataValueRest; import org.dspace.app.rest.model.patch.LateObjectEvaluator; import org.dspace.content.InProgressSubmission; @@ -53,10 +53,10 @@ public class ItemMetadataValueReplacePatchOperation extends MetadataValueReplace void replace(Context context, HttpServletRequest currentRequest, InProgressSubmission source, String path, Object value) throws Exception { String[] split = getAbsolutePath(path).split("/"); - + String mdString = split[0]; List metadataByMetadataString = itemService.getMetadataByMetadataString(source.getItem(), - split[0]); - Assert.notEmpty(metadataByMetadataString); + mdString); + Assert.notEmpty(metadataByMetadataString, "No metadata fields match ".concat(mdString)); int index = Integer.parseInt(split[1]); // if split size is one so we have a call to initialize or replace diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/LicenseAddPatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/LicenseAddPatchOperation.java index 6bbd4aae7572..bae40565294f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/LicenseAddPatchOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/LicenseAddPatchOperation.java @@ -7,8 +7,7 @@ */ package org.dspace.app.rest.submit.factory.impl; -import javax.servlet.http.HttpServletRequest; - +import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.lang3.BooleanUtils; import org.dspace.content.InProgressSubmission; import org.dspace.content.Item; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/LicenseRemovePatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/LicenseRemovePatchOperation.java index 39ffdbbd67d9..75c244bf9c6f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/LicenseRemovePatchOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/LicenseRemovePatchOperation.java @@ -7,8 +7,7 @@ */ package org.dspace.app.rest.submit.factory.impl; -import javax.servlet.http.HttpServletRequest; - +import jakarta.servlet.http.HttpServletRequest; import org.dspace.content.InProgressSubmission; import org.dspace.content.Item; import org.dspace.content.service.ItemService; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/LicenseReplacePatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/LicenseReplacePatchOperation.java index bdb3051010a0..699b27ab0e22 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/LicenseReplacePatchOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/LicenseReplacePatchOperation.java @@ -7,8 +7,7 @@ */ package org.dspace.app.rest.submit.factory.impl; -import javax.servlet.http.HttpServletRequest; - +import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.lang3.BooleanUtils; import org.dspace.content.InProgressSubmission; import org.dspace.content.Item; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/MovePatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/MovePatchOperation.java index 750a2bc71ba1..fc6bc2736e60 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/MovePatchOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/MovePatchOperation.java @@ -7,8 +7,7 @@ */ package org.dspace.app.rest.submit.factory.impl; -import javax.servlet.http.HttpServletRequest; - +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.model.patch.MoveOperation; import org.dspace.app.rest.model.patch.Operation; import org.dspace.content.InProgressSubmission; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/NotifyServiceAddPatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/NotifyServiceAddPatchOperation.java new file mode 100644 index 000000000000..ff63fc49fb9c --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/NotifyServiceAddPatchOperation.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.app.rest.submit.factory.impl; + +import java.sql.SQLException; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import jakarta.servlet.http.HttpServletRequest; +import org.dspace.app.ldn.NotifyPatternToTrigger; +import org.dspace.app.ldn.NotifyServiceEntity; +import org.dspace.app.ldn.service.NotifyPatternToTriggerService; +import org.dspace.app.ldn.service.NotifyService; +import org.dspace.app.rest.exception.UnprocessableEntityException; +import org.dspace.app.rest.model.patch.LateObjectEvaluator; +import org.dspace.content.InProgressSubmission; +import org.dspace.content.Item; +import org.dspace.core.Context; +import org.dspace.utils.DSpace; +import org.springframework.beans.factory.annotation.Autowired; + + +/** + * Submission "add" PATCH operation + * + * To add the COAR Notify Service of workspace item. + * + * Example: + * curl -X PATCH http://${dspace.server.url}/api/submission/workspaceitems/31599 -H "Content-Type: + * application/json" -d '[{ "op": "add", "path": "/sections/coarnotify/review/-"}, "value": ["1","2"]' + * + */ +public class NotifyServiceAddPatchOperation extends AddPatchOperation { + + @Autowired + private NotifyPatternToTriggerService notifyPatternToTriggerService; + + @Autowired + private NotifyService notifyService; + + private NotifySubmissionService coarNotifySubmissionService = new DSpace().getServiceManager() + .getServiceByName("coarNotifySubmissionService", NotifySubmissionService.class); + + @Override + protected Class getArrayClassForEvaluation() { + return Integer[].class; + } + + @Override + protected Class getClassForEvaluation() { + return Integer.class; + } + + @Override + void add(Context context, HttpServletRequest currentRequest, InProgressSubmission source, String path, + Object value) throws Exception { + + String pattern = coarNotifySubmissionService.extractPattern(path); + Set servicesIds = new LinkedHashSet<>(evaluateArrayObject((LateObjectEvaluator) value)); + + coarNotifySubmissionService.checkCompatibilityWithPattern(context, pattern, servicesIds); + + List services = + servicesIds.stream() + .map(id -> + findService(context, id)) + .collect(Collectors.toList()); + if (services.isEmpty()) { + createNotifyPattern(context, source.getItem(), null, pattern); + } else { + services.forEach(service -> + createNotifyPattern(context, source.getItem(), service, pattern)); + } + } + + private NotifyServiceEntity findService(Context context, int serviceId) { + try { + NotifyServiceEntity service = notifyService.find(context, serviceId); + if (service == null) { + throw new UnprocessableEntityException("no service found for the provided value: " + serviceId + ""); + } + return service; + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + private void createNotifyPattern(Context context, Item item, NotifyServiceEntity service, String pattern) { + try { + NotifyPatternToTrigger notifyPatternToTrigger = notifyPatternToTriggerService.create(context); + notifyPatternToTrigger.setItem(item); + notifyPatternToTrigger.setNotifyService(service); + notifyPatternToTrigger.setPattern(pattern); + notifyPatternToTriggerService.update(context, notifyPatternToTrigger); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/NotifyServiceRemovePatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/NotifyServiceRemovePatchOperation.java new file mode 100644 index 000000000000..4c8a440a5643 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/NotifyServiceRemovePatchOperation.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.app.rest.submit.factory.impl; + +import java.util.List; + +import jakarta.servlet.http.HttpServletRequest; +import org.dspace.app.ldn.NotifyPatternToTrigger; +import org.dspace.app.ldn.service.NotifyPatternToTriggerService; +import org.dspace.app.rest.exception.UnprocessableEntityException; +import org.dspace.content.InProgressSubmission; +import org.dspace.content.Item; +import org.dspace.core.Context; +import org.dspace.utils.DSpace; +import org.springframework.beans.factory.annotation.Autowired; + + +/** + * Submission "remove" PATCH operation + * + * To remove the COAR Notify Service of workspace item. + * + * Example: + * curl -X PATCH http://${dspace.server.url}/api/submission/workspaceitems/31599 -H "Content-Type: + * application/json" -d '[{ "op": "remove", "path": "/sections/coarnotify/review/0"}]' + * + */ +public class NotifyServiceRemovePatchOperation extends RemovePatchOperation { + + @Autowired + private NotifyPatternToTriggerService notifyPatternToTriggerService; + + private NotifySubmissionService coarNotifySubmissionService = new DSpace().getServiceManager() + .getServiceByName("coarNotifySubmissionService", NotifySubmissionService.class); + + @Override + protected Class getArrayClassForEvaluation() { + return Integer[].class; + } + + @Override + protected Class getClassForEvaluation() { + return Integer.class; + } + + @Override + void remove(Context context, HttpServletRequest currentRequest, InProgressSubmission source, String path, + Object value) throws Exception { + + Item item = source.getItem(); + + String pattern = coarNotifySubmissionService.extractPattern(path); + int index = coarNotifySubmissionService.extractIndex(path); + + List notifyPatterns = + notifyPatternToTriggerService.findByItemAndPattern(context, item, pattern); + + if (index >= notifyPatterns.size()) { + throw new UnprocessableEntityException("the provided index[" + index + "] is out of the rang"); + } + + notifyPatternToTriggerService.delete(context, notifyPatterns.get(index)); + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/NotifyServiceReplacePatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/NotifyServiceReplacePatchOperation.java new file mode 100644 index 000000000000..23946221fb68 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/NotifyServiceReplacePatchOperation.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.rest.submit.factory.impl; + +import java.util.List; +import java.util.Set; + +import jakarta.servlet.http.HttpServletRequest; +import org.dspace.app.ldn.NotifyPatternToTrigger; +import org.dspace.app.ldn.NotifyServiceEntity; +import org.dspace.app.ldn.service.NotifyPatternToTriggerService; +import org.dspace.app.ldn.service.NotifyService; +import org.dspace.app.rest.exception.DSpaceBadRequestException; +import org.dspace.content.InProgressSubmission; +import org.dspace.core.Context; +import org.dspace.utils.DSpace; +import org.springframework.beans.factory.annotation.Autowired; + + +/** + * Submission "replace" PATCH operation + * + * To replace the COAR Notify Service of workspace item. + * + * Example: + * curl -X PATCH http://${dspace.server.url}/api/submission/workspaceitems/31599 -H "Content-Type: + * application/json" -d '[{ "op": "replace", "path": "/sections/coarnotify/review/0"}, "value": "10"]' + * + */ +public class NotifyServiceReplacePatchOperation extends ReplacePatchOperation { + + @Autowired + private NotifyPatternToTriggerService notifyPatternToTriggerService; + + @Autowired + private NotifyService notifyService; + + private NotifySubmissionService coarNotifySubmissionService = new DSpace().getServiceManager() + .getServiceByName("coarNotifySubmissionService", NotifySubmissionService.class); + + @Override + protected Class getArrayClassForEvaluation() { + return Integer[].class; + } + + @Override + protected Class getClassForEvaluation() { + return Integer.class; + } + + @Override + void replace(Context context, HttpServletRequest currentRequest, InProgressSubmission source, String path, + Object value) throws Exception { + + int index = coarNotifySubmissionService.extractIndex(path); + String pattern = coarNotifySubmissionService.extractPattern(path); + + List notifyPatterns = + notifyPatternToTriggerService.findByItemAndPattern(context, source.getItem(), pattern); + + if (index >= notifyPatterns.size()) { + throw new DSpaceBadRequestException("the provided index[" + index + "] is out of the rang"); + } + + NotifyServiceEntity notifyServiceEntity = notifyService.find(context, Integer.parseInt(value.toString())); + if (notifyServiceEntity == null) { + throw new DSpaceBadRequestException("no service found for the provided value: " + value + ""); + } + + coarNotifySubmissionService.checkCompatibilityWithPattern(context, + pattern, Set.of(notifyServiceEntity.getID())); + + NotifyPatternToTrigger notifyPatternToTriggerOld = notifyPatterns.get(index); + notifyPatternToTriggerOld.setNotifyService(notifyServiceEntity); + + notifyPatternToTriggerService.update(context, notifyPatternToTriggerOld); + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/NotifySubmissionService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/NotifySubmissionService.java new file mode 100644 index 000000000000..7cebc3341686 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/NotifySubmissionService.java @@ -0,0 +1,152 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.submit.factory.impl; + +import java.io.IOException; +import java.sql.SQLException; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import org.apache.commons.collections4.CollectionUtils; +import org.dspace.app.ldn.NotifyPatternToTrigger; +import org.dspace.app.ldn.NotifyServiceEntity; +import org.dspace.app.ldn.service.NotifyPatternToTriggerService; +import org.dspace.app.ldn.service.NotifyService; +import org.dspace.app.rest.exception.UnprocessableEntityException; +import org.dspace.app.rest.model.step.DataNotify; +import org.dspace.authorize.AuthorizeException; +import org.dspace.coarnotify.NotifyConfigurationService; +import org.dspace.coarnotify.NotifyPattern; +import org.dspace.content.InProgressSubmission; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Service to manipulate COAR Notify section of in-progress submissions. + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com + */ +@Component +public class NotifySubmissionService { + + @Autowired + private NotifyPatternToTriggerService notifyPatternToTriggerService; + + @Autowired + private NotifyConfigurationService coarNotifyConfigurationService; + + @Autowired + private NotifyService notifyService; + + private NotifySubmissionService() { } + + + /** + * Builds the COAR Notify data of an in-progress submission + * + * @param obj - the in progress submission + * @return an object representing the CC License data + * @throws SQLException + * @throws IOException + * @throws AuthorizeException + */ + public DataNotify getDataCOARNotify(InProgressSubmission obj) throws SQLException { + Context context = new Context(); + + List patternsToTrigger = + notifyPatternToTriggerService.findByItem(context, obj.getItem()); + + Map> data = + patternsToTrigger.stream() + .collect(Collectors.groupingBy( + NotifyPatternToTrigger::getPattern, + Collectors.mapping(patternToTrigger -> + patternToTrigger.getNotifyService().getID(), + Collectors.toList()) + )); + + return new DataNotify(data); + } + + + public int extractIndex(String path) { + Pattern pattern = Pattern.compile("/(\\d+)$"); + Matcher matcher = pattern.matcher(path); + + if (matcher.find()) { + return Integer.parseInt(matcher.group(1)); + } else { + throw new UnprocessableEntityException("Index not found in the path"); + } + } + + /** + * extract pattern from path. see COARNotifyConfigurationService bean + * @param path + * @return the extracted pattern + */ + public String extractPattern(String path) { + Pattern pattern = Pattern.compile("/([^/]+)/([^/]+)/([^/]+)"); + Matcher matcher = pattern.matcher(path); + if (matcher.find()) { + String patternValue = matcher.group(3); + String config = matcher.group(2); + if (!isContainPattern(config, patternValue)) { + throw new UnprocessableEntityException( + "Invalid Pattern (" + patternValue + ") of " + config); + } + return patternValue; + } else { + throw new UnprocessableEntityException("Pattern not found in the path"); + } + } + + private boolean isContainPattern(String config, String pattern) { + List patterns = coarNotifyConfigurationService.getPatterns().get(config); + if (CollectionUtils.isEmpty(patterns)) { + return false; + } + + return patterns.stream() + .map(NotifyPattern::getPattern) + .anyMatch(v -> + v.equals(pattern)); + } + + /** + * check that the provided services ids are compatible + * with the provided inbound pattern + * + * @param context the context + * @param pattern the inbound pattern + * @param servicesIds notify services ids + * @throws SQLException if something goes wrong + */ + public void checkCompatibilityWithPattern(Context context, String pattern, Set servicesIds) + throws SQLException { + + List manualServicesIds = + notifyService.findManualServicesByInboundPattern(context, pattern) + .stream() + .map(NotifyServiceEntity::getID) + .collect(Collectors.toList()); + + for (Integer servicesId : servicesIds) { + if (!manualServicesIds.contains(servicesId)) { + throw new UnprocessableEntityException("notify service with id (" + servicesId + + ") is not compatible with pattern " + pattern); + } + } + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/PatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/PatchOperation.java index 230d931e83f7..f073fd355cc7 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/PatchOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/PatchOperation.java @@ -9,8 +9,8 @@ import java.util.ArrayList; import java.util.List; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.model.patch.LateObjectEvaluator; import org.dspace.app.rest.model.patch.Operation; import org.dspace.content.InProgressSubmission; @@ -60,6 +60,15 @@ public String getAbsolutePath(String fullpath) { return absolutePath; } + public String getStepId(String fullpath) { + String[] path = fullpath.substring(1).split("/", 3); + String stepId = ""; + if (path.length > 1) { + stepId = path[1]; + } + return stepId; + } + protected abstract Class getArrayClassForEvaluation(); protected abstract Class getClassForEvaluation(); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/PrimaryBitstreamAddPatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/PrimaryBitstreamAddPatchOperation.java new file mode 100644 index 000000000000..6704c8c2bbcc --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/PrimaryBitstreamAddPatchOperation.java @@ -0,0 +1,87 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.rest.submit.factory.impl; + +import static org.dspace.core.Constants.CONTENT_BUNDLE_NAME; + +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; + +import jakarta.servlet.http.HttpServletRequest; +import org.dspace.app.rest.exception.UnprocessableEntityException; +import org.dspace.content.Bitstream; +import org.dspace.content.Bundle; +import org.dspace.content.InProgressSubmission; +import org.dspace.content.Item; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Submission "add" operation to set primary bitstream. + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com) + */ +public class PrimaryBitstreamAddPatchOperation extends AddPatchOperation { + + @Autowired + private ItemService itemService; + + @Override + void add(Context context, HttpServletRequest currentRequest, InProgressSubmission source, String path, Object value) + throws Exception { + Item item = source.getItem(); + UUID primaryUUID = parseValue(value); + List bundles = itemService.getBundles(item, CONTENT_BUNDLE_NAME); + Optional currentPrimaryBundle = bundles.stream() + .filter(bundle -> Objects.nonNull(bundle.getPrimaryBitstream())) + .findFirst(); + + Optional primaryBitstreamToAdd = null; + for (Bundle bundle : bundles) { + primaryBitstreamToAdd = bundle.getBitstreams().stream() + .filter(b -> b.getID().equals(primaryUUID)) + .findFirst(); + if (primaryBitstreamToAdd.isPresent()) { + if (currentPrimaryBundle.isPresent()) { + currentPrimaryBundle.get().setPrimaryBitstreamID(null); + } + bundle.setPrimaryBitstreamID(primaryBitstreamToAdd.get()); + break; + } + } + + if (primaryBitstreamToAdd.isEmpty()) { + throw new UnprocessableEntityException("The provided uuid: " + primaryUUID + + " of bitstream to set as primary doesn't match any bitstream!"); + } + } + + private UUID parseValue(Object value) { + UUID primaryBitstreamUUID; + try { + primaryBitstreamUUID = UUID.fromString((String) value); + } catch (Exception e) { + throw new UnprocessableEntityException("The provided value is invalid!", e); + } + return primaryBitstreamUUID; + } + + @Override + protected Class getArrayClassForEvaluation() { + return null; + } + + @Override + protected Class getClassForEvaluation() { + return null; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/PrimaryBitstreamRemovePatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/PrimaryBitstreamRemovePatchOperation.java new file mode 100644 index 000000000000..c6c8f4d6c5ee --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/PrimaryBitstreamRemovePatchOperation.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.app.rest.submit.factory.impl; + +import static org.dspace.core.Constants.CONTENT_BUNDLE_NAME; + +import java.util.List; + +import jakarta.servlet.http.HttpServletRequest; +import org.dspace.content.Bundle; +import org.dspace.content.InProgressSubmission; +import org.dspace.content.Item; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Submission "remove" operation to remove primary bitstream. + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com) + */ +public class PrimaryBitstreamRemovePatchOperation extends RemovePatchOperation { + + @Autowired + private ItemService itemService; + + @Override + void remove(Context context, HttpServletRequest request, InProgressSubmission source, String path, Object value) + throws Exception { + Item item = source.getItem(); + List bundles = itemService.getBundles(item, CONTENT_BUNDLE_NAME); + bundles.forEach(b -> b.setPrimaryBitstreamID(null)); + } + + @Override + protected Class getArrayClassForEvaluation() { + return null; + } + + @Override + protected Class getClassForEvaluation() { + return null; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/PrimaryBitstreamReplacePatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/PrimaryBitstreamReplacePatchOperation.java new file mode 100644 index 000000000000..8ebeb78d341b --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/PrimaryBitstreamReplacePatchOperation.java @@ -0,0 +1,88 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.rest.submit.factory.impl; + +import static org.dspace.core.Constants.CONTENT_BUNDLE_NAME; + +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; + +import jakarta.servlet.http.HttpServletRequest; +import org.dspace.app.rest.exception.UnprocessableEntityException; +import org.dspace.content.Bitstream; +import org.dspace.content.Bundle; +import org.dspace.content.InProgressSubmission; +import org.dspace.content.Item; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Submission "replace" operation to replace primary bitstream. + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com) + */ +public class PrimaryBitstreamReplacePatchOperation extends ReplacePatchOperation { + + private final String EX_MESSAGE = "It is impossible to replace primary bitstrem if it wasn't set!"; + + @Autowired + private ItemService itemService; + + @Override + void replace(Context context, HttpServletRequest request, InProgressSubmission source, String path, Object value) + throws Exception { + Item item = source.getItem(); + UUID primaryUUID = parseValue(value); + List bundles = itemService.getBundles(item, CONTENT_BUNDLE_NAME); + Bundle currentPrimaryBundle = bundles.stream() + .filter(bundle -> Objects.nonNull(bundle.getPrimaryBitstream())) + .findFirst() + .orElseThrow(() -> new UnprocessableEntityException(EX_MESSAGE)); + + Optional primaryBitstream = null; + for (Bundle bundle : bundles) { + primaryBitstream = bundle.getBitstreams().stream() + .filter(b -> b.getID().equals(primaryUUID)) + .findFirst(); + if (primaryBitstream.isPresent()) { + currentPrimaryBundle.setPrimaryBitstreamID(null); + bundle.setPrimaryBitstreamID(primaryBitstream.get()); + break; + } + } + + if (primaryBitstream.isEmpty()) { + throw new UnprocessableEntityException("The provided uuid: " + primaryUUID + + " of bitstream to set as primary doesn't match any bitstream!"); + } + } + + private UUID parseValue(Object value) { + UUID primaryBitstreamUUID; + try { + primaryBitstreamUUID = UUID.fromString((String) value); + } catch (Exception e) { + throw new UnprocessableEntityException("The provided value is invalid!", e); + } + return primaryBitstreamUUID; + } + + @Override + protected Class getArrayClassForEvaluation() { + return null; + } + + @Override + protected Class getClassForEvaluation() { + return null; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/RemovePatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/RemovePatchOperation.java index 0fa50562b3f4..b90fd1875f4e 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/RemovePatchOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/RemovePatchOperation.java @@ -7,8 +7,7 @@ */ package org.dspace.app.rest.submit.factory.impl; -import javax.servlet.http.HttpServletRequest; - +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.model.patch.Operation; import org.dspace.content.InProgressSubmission; import org.dspace.core.Context; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/ReplacePatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/ReplacePatchOperation.java index b5ca61f54359..f24fbde59aaa 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/ReplacePatchOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/ReplacePatchOperation.java @@ -7,8 +7,7 @@ */ package org.dspace.app.rest.submit.factory.impl; -import javax.servlet.http.HttpServletRequest; - +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.model.patch.Operation; import org.dspace.content.InProgressSubmission; import org.dspace.core.Context; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/AccessConditionStep.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/AccessConditionStep.java index 070e4a3cbfeb..29353d0ffdf4 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/AccessConditionStep.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/AccessConditionStep.java @@ -9,8 +9,8 @@ import java.util.ArrayList; import java.util.List; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.lang.StringUtils; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.AccessConditionDTO; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/CCLicenseStep.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/CCLicenseStep.java index 34ae259090e9..357c0c48dd26 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/CCLicenseStep.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/CCLicenseStep.java @@ -7,8 +7,7 @@ */ package org.dspace.app.rest.submit.step; -import javax.servlet.http.HttpServletRequest; - +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.model.patch.Operation; import org.dspace.app.rest.model.step.DataCCLicense; import org.dspace.app.rest.submit.AbstractProcessingStep; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/CollectionStep.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/CollectionStep.java index 09e19f298012..b7b93fe2d3ff 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/CollectionStep.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/CollectionStep.java @@ -8,8 +8,8 @@ package org.dspace.app.rest.submit.step; import java.util.UUID; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.model.patch.Operation; import org.dspace.app.rest.submit.DataProcessingStep; import org.dspace.app.rest.submit.SubmissionService; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/DescribeStep.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/DescribeStep.java index ee81d14ca7a6..5d1f104b92e4 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/DescribeStep.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/DescribeStep.java @@ -9,8 +9,8 @@ import java.util.ArrayList; 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.app.rest.exception.UnprocessableEntityException; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/DuplicateDetectionStep.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/DuplicateDetectionStep.java new file mode 100644 index 000000000000..032a27f83cea --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/DuplicateDetectionStep.java @@ -0,0 +1,112 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.rest.submit.step; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.dspace.app.rest.model.patch.Operation; +import org.dspace.app.rest.model.step.DataDuplicateDetection; +import org.dspace.app.rest.submit.AbstractProcessingStep; +import org.dspace.app.rest.submit.SubmissionService; +import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.app.util.SubmissionStepConfig; +import org.dspace.content.InProgressSubmission; +import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.core.Context; +import org.dspace.handle.service.HandleService; +import org.dspace.services.factory.DSpaceServicesFactory; +import org.dspace.services.model.Request; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Submission processing step to detect potential duplicates of this item and list them so that + * the submitter can choose to cancel or continue with their submission + * + * @author Kim Shepherd + */ +public class DuplicateDetectionStep extends AbstractProcessingStep { + + private static final Logger log = LogManager.getLogger(DuplicateDetectionStep.class); + + @Autowired(required = true) + protected HandleService handleService; + @Autowired(required = true) + protected ContentServiceFactory contentServiceFactory; + + /** + * Override DataProcessing.getData, return a list of potential duplicates + * + * @param submissionService The submission service + * @param obj The workspace or workflow item + * @param config The submission step configuration + * @return A simple DataIdentifiers bean containing doi, handle and list of other identifiers + */ + @Override + public DataDuplicateDetection getData(SubmissionService submissionService, InProgressSubmission obj, + SubmissionStepConfig config) throws Exception { + // Validate in progress submission object and wrapped item + if (obj == null) { + throw new IllegalArgumentException("Null in-progress wrapper object"); + } + if (obj.getItem() == null) { + throw new IllegalArgumentException("Null in-progress item"); + } + // Immediately return an empty if this feature is not configured + if (!configurationService.getBooleanProperty("duplicate.enable", false)) { + log.debug("Duplicate detection is not enabled, returning empty section"); + return new DataDuplicateDetection(); + } + // Validate context + Context context = getContext(); + if (context == null) { + throw new ServletException("Null context"); + } + + // Return the constructed data section + return submissionService.getDataDuplicateDetection(context, obj); + } + + /** + * Utility method to get DSpace context from the HTTP request + * @return DSpace context + */ + private Context getContext() { + Context context; + Request currentRequest = DSpaceServicesFactory.getInstance().getRequestService().getCurrentRequest(); + if (currentRequest != null) { + HttpServletRequest request = currentRequest.getHttpServletRequest(); + context = ContextUtil.obtainContext(request); + } else { + context = new Context(); + } + + return context; + } + + /** + * This step is currently just for displaying identifiers and does not take additional patch operations + * @param context + * the DSpace context + * @param currentRequest + * the http request + * @param source + * the in progress submission + * @param op + * the json patch operation + * @param stepConf + * @throws Exception + */ + @Override + public void doPatchProcessing(Context context, HttpServletRequest currentRequest, InProgressSubmission source, + Operation op, SubmissionStepConfig stepConf) throws Exception { + log.warn("Not implemented"); + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/LicenseStep.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/LicenseStep.java index 053530d862fa..f374dcd7881c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/LicenseStep.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/LicenseStep.java @@ -7,8 +7,7 @@ */ package org.dspace.app.rest.submit.step; -import javax.servlet.http.HttpServletRequest; - +import jakarta.servlet.http.HttpServletRequest; import org.atteo.evo.inflector.English; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.BitstreamRest; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/NotifyStep.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/NotifyStep.java new file mode 100644 index 000000000000..e6297217a458 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/NotifyStep.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.rest.submit.step; + +import jakarta.servlet.http.HttpServletRequest; +import org.dspace.app.rest.model.patch.Operation; +import org.dspace.app.rest.model.step.DataNotify; +import org.dspace.app.rest.submit.AbstractProcessingStep; +import org.dspace.app.rest.submit.SubmissionService; +import org.dspace.app.rest.submit.factory.PatchOperationFactory; +import org.dspace.app.rest.submit.factory.impl.PatchOperation; +import org.dspace.app.util.SubmissionStepConfig; +import org.dspace.content.InProgressSubmission; +import org.dspace.core.Context; + +/** + * COARNotify Step for DSpace Spring Rest. Expose information about + * the COAR Notify services for the in progress submission. + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + */ +public class NotifyStep extends AbstractProcessingStep { + + /** + * Retrieves the COAR Notify services data of the in progress submission + * + * @param submissionService the submission service + * @param obj the in progress submission + * @param config the submission step configuration + * @return the COAR Notify data of the in progress submission + * @throws Exception + */ + @Override + public DataNotify getData(SubmissionService submissionService, InProgressSubmission obj, + SubmissionStepConfig config) throws Exception { + return coarNotifySubmissionService.getDataCOARNotify(obj); + } + + /** + * Processes a patch for the COAR Notify data + * + * @param context the DSpace context + * @param currentRequest the http request + * @param source the in progress submission + * @param op the json patch operation + * @throws Exception + */ + @Override + public void doPatchProcessing(Context context, HttpServletRequest currentRequest, InProgressSubmission source, + Operation op, SubmissionStepConfig stepConf) throws Exception { + + PatchOperation patchOperation = new PatchOperationFactory().instanceOf( + COARNOTIFY_STEP_PATH, op.getOp()); + patchOperation.perform(context, currentRequest, source, op); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/SherpaPolicyStep.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/SherpaPolicyStep.java index d37182904bf0..206b3fab5974 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/SherpaPolicyStep.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/SherpaPolicyStep.java @@ -8,8 +8,8 @@ package org.dspace.app.rest.submit.step; import java.util.Objects; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.model.patch.Operation; import org.dspace.app.rest.model.step.SherpaPolicy; import org.dspace.app.rest.submit.AbstractProcessingStep; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/ShowIdentifiersStep.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/ShowIdentifiersStep.java index e63d38ab2e36..47eac49f499c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/ShowIdentifiersStep.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/ShowIdentifiersStep.java @@ -10,8 +10,8 @@ import java.sql.SQLException; import java.util.Arrays; 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-server-webapp/src/main/java/org/dspace/app/rest/submit/step/UploadStep.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/UploadStep.java index b91916ca3199..e0a9cb17e01c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/UploadStep.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/UploadStep.java @@ -10,8 +10,10 @@ import java.io.BufferedInputStream; import java.io.InputStream; import java.util.List; -import javax.servlet.http.HttpServletRequest; +import java.util.Objects; +import jakarta.servlet.http.HttpServletRequest; +import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.ErrorRest; @@ -46,8 +48,6 @@ public class UploadStep extends AbstractProcessingStep private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(UploadStep.class); - public static final String UPLOAD_STEP_METADATA_SECTION = "bitstream-metadata"; - @Override public DataUpload getData(SubmissionService submissionService, InProgressSubmission obj, SubmissionStepConfig config) throws Exception { @@ -56,8 +56,12 @@ public DataUpload getData(SubmissionService submissionService, InProgressSubmiss List bundles = itemService.getBundles(obj.getItem(), Constants.CONTENT_BUNDLE_NAME); for (Bundle bundle : bundles) { for (Bitstream source : bundle.getBitstreams()) { + Bitstream primaryBitstream = bundle.getPrimaryBitstream(); UploadBitstreamRest b = submissionService.buildUploadBitstream(configurationService, source); result.getFiles().add(b); + if (Objects.nonNull(primaryBitstream)) { + result.setPrimary(primaryBitstream.getID()); + } } } return result; @@ -73,6 +77,8 @@ public void doPatchProcessing(Context context, HttpServletRequest currentRequest instance = UPLOAD_STEP_METADATA_OPERATION_ENTRY; } else if (op.getPath().contains(UPLOAD_STEP_ACCESSCONDITIONS_OPERATION_ENTRY)) { instance = stepConf.getType() + "." + UPLOAD_STEP_ACCESSCONDITIONS_OPERATION_ENTRY; + } else if (op.getPath().contains(PRIMARY_FLAG_ENTRY)) { + instance = PRIMARY_FLAG_ENTRY; } else { instance = UPLOAD_STEP_REMOVE_OPERATION_ENTRY; } @@ -87,9 +93,11 @@ public void doPatchProcessing(Context context, HttpServletRequest currentRequest instance = stepConf.getType() + "." + UPLOAD_STEP_ACCESSCONDITIONS_OPERATION_ENTRY; } else if (op.getPath().contains(UPLOAD_STEP_METADATA_PATH)) { instance = UPLOAD_STEP_METADATA_OPERATION_ENTRY; + } else if (op.getPath().contains(PRIMARY_FLAG_ENTRY)) { + instance = PRIMARY_FLAG_ENTRY; } } - if (instance == null) { + if (StringUtils.isBlank(instance)) { throw new UnprocessableEntityException("The path " + op.getPath() + " is not supported by the operation " + op.getOp()); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/validation/CclicenseValidator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/validation/CclicenseValidator.java new file mode 100644 index 000000000000..2273b377fefa --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/validation/CclicenseValidator.java @@ -0,0 +1,120 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.rest.submit.step.validation; + +import static org.dspace.app.rest.repository.WorkspaceItemRestRepository.OPERATION_PATH_SECTIONS; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +import jakarta.inject.Inject; +import org.dspace.app.rest.model.ErrorRest; +import org.dspace.app.rest.submit.SubmissionService; +import org.dspace.app.util.DCInputsReaderException; +import org.dspace.app.util.SubmissionStepConfig; +import org.dspace.content.InProgressSubmission; +import org.dspace.content.Item; +import org.dspace.content.service.ItemService; +import org.dspace.license.CreativeCommonsServiceImpl; +import org.dspace.services.ConfigurationService; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * This class validates that the Creative Commons License has been granted for the + * in-progress submission. + * + * @author Mattia Vianelli (Mattia.Vianelli@4science.com) + */ +public class CclicenseValidator extends AbstractValidation { + + /** + * Construct a Creative Commons License configuration. + * @param configurationService DSpace configuration provided by the DI container. + */ + @Inject + public CclicenseValidator(ConfigurationService configurationService) { + this.configurationService = configurationService; + } + + private final ConfigurationService configurationService; + + @Autowired + private ItemService itemService; + + @Autowired + private CreativeCommonsServiceImpl creativeCommonsService; + + public static final String ERROR_VALIDATION_CCLICENSEREQUIRED = "error.validation.cclicense.required"; + + private String name; + + /** + * Validate the license of the item. + * @param item The item whose cclicense is to be validated. + * @param config The configuration for the submission step for cclicense. + * @return A list of validation errors. + */ + private List validateLicense(Item item, SubmissionStepConfig config) { + List errors = new ArrayList<>(1); + + String licenseURI = creativeCommonsService.getLicenseURI(item); + if (licenseURI == null || licenseURI.isBlank()) { + addError(errors, ERROR_VALIDATION_CCLICENSEREQUIRED, "/" + OPERATION_PATH_SECTIONS + "/" + config.getId()); + } + + return errors; + } + + public ItemService getItemService() { + return itemService; + } + + public void setItemService(ItemService itemService) { + this.itemService = itemService; + } + + /** + * Check if at least one Creative Commons License is required when submitting a new Item. + * @return true if a Creative Commons License is required setting true for the property cc.license.required. + */ + public Boolean isRequired() { + return configurationService.getBooleanProperty("cc.license.required", false); + } + + @Override + public String getName() { + return name; + } + + /** + * Perform validation on the item and config(ccLicense). + * @param obj The submission to be validated. + * @param config The configuration for the submission step for cclicense. + * @return A list of validation errors. + * @throws SQLException If there is a problem accessing the database. + */ + + @Override + public List validate(SubmissionService submissionService, + InProgressSubmission obj, + SubmissionStepConfig config) + throws DCInputsReaderException, SQLException { + + if (this.isRequired() && obj != null && obj.getItem() != null) { + return validateLicense(obj.getItem(), config); + } else { + return List.of(); + } + } + + public void setName(String name) { + this.name = name; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/validation/NotifyValidation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/validation/NotifyValidation.java new file mode 100644 index 000000000000..04a1567fdac8 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/validation/NotifyValidation.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.app.rest.submit.step.validation; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import org.dspace.app.ldn.NotifyPatternToTrigger; +import org.dspace.app.ldn.NotifyServiceEntity; +import org.dspace.app.ldn.service.NotifyPatternToTriggerService; +import org.dspace.app.rest.model.ErrorRest; +import org.dspace.app.rest.repository.WorkspaceItemRestRepository; +import org.dspace.app.rest.submit.SubmissionService; +import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.app.util.DCInputsReaderException; +import org.dspace.app.util.SubmissionStepConfig; +import org.dspace.coarnotify.NotifyConfigurationService; +import org.dspace.coarnotify.NotifyPattern; +import org.dspace.content.InProgressSubmission; +import org.dspace.content.Item; +import org.dspace.content.logic.LogicalStatement; +import org.dspace.core.Context; +import org.dspace.utils.DSpace; + +/** + * Execute check validation of Coar notify services filters + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + */ +public class NotifyValidation extends AbstractValidation { + + private static final String ERROR_VALIDATION_INVALID_FILTER = "error.validation.coarnotify.invalidfilter"; + + private NotifyConfigurationService coarNotifyConfigurationService; + + private NotifyPatternToTriggerService notifyPatternToTriggerService; + + @Override + public List validate(SubmissionService submissionService, InProgressSubmission obj, + SubmissionStepConfig config) throws DCInputsReaderException, SQLException { + + List errors = new ArrayList<>(); + Context context = ContextUtil.obtainCurrentRequestContext(); + Item item = obj.getItem(); + + List patterns = + coarNotifyConfigurationService.getPatterns().getOrDefault(config.getId(), List.of()) + .stream() + .map(NotifyPattern::getPattern) + .collect(Collectors.toList()); + + patterns.forEach(pattern -> { + List services = findByItemAndPattern(context, item, pattern); + IntStream.range(0, services.size()).forEach(i -> + services.get(i) + .getInboundPatterns() + .stream() + .filter(inboundPattern -> inboundPattern.getPattern().equals(pattern)) + .filter(inboundPattern -> !inboundPattern.isAutomatic() && + !inboundPattern.getConstraint().isEmpty()) + .forEach(inboundPattern -> { + LogicalStatement filter = + new DSpace().getServiceManager() + .getServiceByName(inboundPattern.getConstraint(), LogicalStatement.class); + + if (filter == null || !filter.getResult(context, item)) { + addError(errors, ERROR_VALIDATION_INVALID_FILTER, + "/" + WorkspaceItemRestRepository.OPERATION_PATH_SECTIONS + + "/" + config.getId() + + "/" + inboundPattern.getPattern() + + "/" + i + ); + } + })); + }); + + return errors; + } + + private List findByItemAndPattern(Context context, Item item, String pattern) { + try { + return notifyPatternToTriggerService.findByItemAndPattern(context, item, pattern) + .stream() + .map(NotifyPatternToTrigger::getNotifyService) + .collect(Collectors.toList()); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + public NotifyConfigurationService getCoarNotifyConfigurationService() { + return coarNotifyConfigurationService; + } + + public void setCoarNotifyConfigurationService( + NotifyConfigurationService coarNotifyConfigurationService) { + this.coarNotifyConfigurationService = coarNotifyConfigurationService; + } + + public NotifyPatternToTriggerService getNotifyPatternToTriggerService() { + return notifyPatternToTriggerService; + } + + public void setNotifyPatternToTriggerService( + NotifyPatternToTriggerService notifyPatternToTriggerService) { + this.notifyPatternToTriggerService = notifyPatternToTriggerService; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/ApplicationConfig.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/ApplicationConfig.java index 48538deb13d4..ba43fdf9565c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/ApplicationConfig.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/ApplicationConfig.java @@ -34,7 +34,8 @@ "org.dspace.app.rest.utils", "org.dspace.app.configuration", "org.dspace.iiif", - "org.dspace.app.iiif" + "org.dspace.app.iiif", + "org.dspace.app.ldn" }) public class ApplicationConfig { // Allowed CORS origins ("Access-Control-Allow-Origin" header) @@ -71,6 +72,10 @@ public class ApplicationConfig { @Value("${dspace.ui.url:http://localhost:4000}") private String uiURL; + // LDN enable status + @Value("${ldn.enabled}") + private boolean ldnEnabled; + /** * Return the array of allowed origins (client URLs) for the CORS "Access-Control-Allow-Origin" header * Used by Application class @@ -129,6 +134,14 @@ public boolean getCorsAllowCredentials() { return corsAllowCredentials; } + /** + * Return the ldn.enabled value + * @return true or false + */ + public boolean getLdnEnabled() { + return this.ldnEnabled; + } + /** * Return whether to allow credentials (cookies) on IIIF requests. This is used to set the * CORS "Access-Control-Allow-Credentials" header in Application class. Defaults to false. diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/BitstreamMetadataValuePathUtils.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/BitstreamMetadataValuePathUtils.java index dda661e4910f..c1c05c41f426 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/BitstreamMetadataValuePathUtils.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/BitstreamMetadataValuePathUtils.java @@ -8,10 +8,12 @@ package org.dspace.app.rest.utils; import org.dspace.app.rest.exception.UnprocessableEntityException; -import org.dspace.app.rest.submit.step.UploadStep; import org.dspace.app.util.DCInputSet; import org.dspace.app.util.DCInputsReader; import org.dspace.app.util.DCInputsReaderException; +import org.dspace.submit.model.UploadConfiguration; +import org.dspace.submit.model.UploadConfigurationService; +import org.springframework.beans.factory.annotation.Autowired; /** * Utils class offering methods to validate patch operations for bitstream metadata in the submission @@ -22,6 +24,9 @@ public class BitstreamMetadataValuePathUtils { private DCInputsReader inputReader; + @Autowired + UploadConfigurationService uploadConfigurationService; + BitstreamMetadataValuePathUtils() throws DCInputsReaderException { inputReader = new DCInputsReader(); } @@ -29,20 +34,22 @@ public class BitstreamMetadataValuePathUtils { /** * Method to verify that the path included in the patch operation is supported * by the submission configuration of the upload section - * + * + * @param stepId the name of the upload configuration * @param absolutePath the path in the json patch operation * @throws DCInputsReaderException if an error occurs reading the * submission configuration * @throws UnprocessableEntityException if the path is invalid */ - public void validate(String absolutePath) throws DCInputsReaderException { + public void validate(String stepId, String absolutePath) throws DCInputsReaderException { + UploadConfiguration uploadService = uploadConfigurationService.getMap().get(stepId); + DCInputSet inputConfig = inputReader.getInputsByFormName(uploadService.getMetadata()); String[] split = absolutePath.split("/"); - DCInputSet inputConfig = inputReader.getInputsByFormName(UploadStep.UPLOAD_STEP_METADATA_SECTION); // according to the rest contract the absolute path must be something like files/:idx/metadata/dc.title if (split.length >= 4) { if (!inputConfig.isFieldPresent(split[3])) { throw new UnprocessableEntityException("The field " + split[3] + " is not present in section " - + UploadStep.UPLOAD_STEP_METADATA_SECTION); + + stepId); } } else { throw new UnprocessableEntityException("The path " + absolutePath + " cannot be patched "); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/ContextUtil.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/ContextUtil.java index 73813c01b528..3ba853dbd62e 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/ContextUtil.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/ContextUtil.java @@ -10,10 +10,10 @@ 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-server-webapp/src/main/java/org/dspace/app/rest/utils/DSpaceAPIRequestLoggingFilter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/DSpaceAPIRequestLoggingFilter.java index 6dfebe320354..36888a740376 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/DSpaceAPIRequestLoggingFilter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/DSpaceAPIRequestLoggingFilter.java @@ -8,8 +8,8 @@ package org.dspace.app.rest.utils; import java.util.UUID; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.ThreadContext; import org.dspace.services.ConfigurationService; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/DSpaceConfigurationInitializer.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/DSpaceConfigurationInitializer.java index 56b8ae32dce1..c04ac976e0ac 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/DSpaceConfigurationInitializer.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/DSpaceConfigurationInitializer.java @@ -8,11 +8,9 @@ package org.dspace.app.rest.utils; import org.apache.commons.configuration2.Configuration; -import org.apache.commons.configuration2.spring.ConfigurationPropertySource; +import org.dspace.servicemanager.config.DSpaceConfigurationPropertySource; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ConfigurableApplicationContext; @@ -29,8 +27,6 @@ */ public class DSpaceConfigurationInitializer implements ApplicationContextInitializer { - private static final Logger log = LoggerFactory.getLogger(DSpaceConfigurationInitializer.class); - @Override public void initialize(final ConfigurableApplicationContext applicationContext) { // Load DSpace Configuration service (requires kernel already initialized) @@ -38,8 +34,8 @@ public void initialize(final ConfigurableApplicationContext applicationContext) Configuration configuration = configurationService.getConfiguration(); // Create an Apache Commons Configuration Property Source from our configuration - ConfigurationPropertySource apacheCommonsConfigPropertySource = - new ConfigurationPropertySource(configuration.getClass().getName(), configuration); + DSpaceConfigurationPropertySource apacheCommonsConfigPropertySource = + new DSpaceConfigurationPropertySource(configuration.getClass().getName(), configuration); // Prepend it to the Environment's list of PropertySources // NOTE: This is added *first* in the list so that settings in DSpace's ConfigurationService *override* diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/DSpaceKernelInitializer.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/DSpaceKernelInitializer.java index 73a96259bf21..88a093c0575d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/DSpaceKernelInitializer.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/DSpaceKernelInitializer.java @@ -12,13 +12,13 @@ import javax.naming.InitialContext; import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.kernel.DSpaceKernel; import org.dspace.kernel.DSpaceKernelManager; import org.dspace.servicemanager.DSpaceKernelImpl; import org.dspace.servicemanager.DSpaceKernelInit; import org.dspace.servicemanager.config.DSpaceConfigurationService; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ApplicationListener; import org.springframework.context.ConfigurableApplicationContext; @@ -31,7 +31,7 @@ */ public class DSpaceKernelInitializer implements ApplicationContextInitializer { - private static final Logger log = LoggerFactory.getLogger(DSpaceKernelInitializer.class); + private static final Logger log = LogManager.getLogger(); private transient DSpaceKernel dspaceKernel; @@ -81,6 +81,7 @@ public void initialize(final ConfigurableApplicationContext applicationContext) * 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 Boot's Configuration (application.properties) // This gives us the location of our DSpace configurations, necessary to start the kernel @@ -119,6 +120,7 @@ public DSpaceKernelDestroyer(DSpaceKernel kernel) { this.kernel = kernel; } + @Override public void onApplicationEvent(final ContextClosedEvent event) { if (this.kernel != null) { this.kernel.destroy(); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/HttpHeadersInitializer.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/HttpHeadersInitializer.java index d68c710a3c7a..d1b80c36750b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/HttpHeadersInitializer.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/HttpHeadersInitializer.java @@ -7,21 +7,21 @@ */ package org.dspace.app.rest.utils; +import static jakarta.mail.internet.MimeUtility.encodeText; import static java.util.Objects.isNull; import static java.util.Objects.nonNull; -import static javax.mail.internet.MimeUtility.encodeText; import java.io.IOException; import java.util.Arrays; import java.util.Collections; import java.util.Objects; -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.apache.tomcat.util.http.FastHttpDateFormat; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.http.HttpHeaders; /** @@ -32,7 +32,7 @@ */ public class HttpHeadersInitializer { - protected final Logger log = LoggerFactory.getLogger(this.getClass()); + protected final Logger log = LogManager.getLogger(); private static final String MULTIPART_BOUNDARY = "MULTIPART_BYTERANGES"; private static final String CONTENT_TYPE_MULTITYPE_WITH_BOUNDARY = "multipart/byteranges; boundary=" + diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/RegexUtils.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/RegexUtils.java index 8db5a74eefba..df525f679323 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/RegexUtils.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/RegexUtils.java @@ -13,7 +13,7 @@ */ public class RegexUtils { - private RegexUtils(){} + private RegexUtils() {} /** * Regular expression in the request mapping to accept UUID as identifier @@ -27,6 +27,12 @@ private RegexUtils(){} public static final String REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID = "/{uuid:" + REGEX_UUID + "}"; + /** + * Regular expression in the request mapping to accept LDN identifiers + */ + public static final String REGEX_REQUESTMAPPING_IDENTIFIER_AS_URN_UUID = + "/{id:^urn:uuid:" + REGEX_UUID + "}"; + /** * Regular expression in the request mapping to accept a string as identifier but not the other kind of * identifier (digits or uuid) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/RestRepositoryUtils.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/RestRepositoryUtils.java index 69a9cc11e0ea..12869a563878 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/RestRepositoryUtils.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/RestRepositoryUtils.java @@ -105,7 +105,7 @@ public List listSearchMethods(DSpaceRestRepository repository) { */ public Method getSearchMethod(String searchMethodName, DSpaceRestRepository repository) { Method searchMethod = null; - // DSpaceRestRepository is possibly enhanced with a Spring AOP proxy. Therefor use ClassUtils to determine + // DSpaceRestRepository is possibly enhanced with a Spring AOP proxy. Therefore use ClassUtils to determine // the underlying implementation class. Method[] methods = ClassUtils.getUserClass(repository.getClass()).getMethods(); for (Method method : methods) { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/ScopeResolver.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/ScopeResolver.java index 658a3e996aef..7a425f5e81a8 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/ScopeResolver.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/ScopeResolver.java @@ -35,6 +35,18 @@ public class ScopeResolver { @Autowired CommunityService communityService; + /** + * Returns an IndexableObject corresponding to the community or collection + * of the given scope, or null if the scope is not a valid UUID, or is a + * valid UUID that does not correspond to a community of collection. + * + * @param context the DSpace context + * @param scope a String containing the UUID of the community or collection + * to return. + * @return an IndexableObject corresponding to the community or collection + * of the given scope, or null if the scope is not a valid UUID, or is a + * valid UUID that does not correspond to a community of collection. + */ public IndexableObject resolveScope(Context context, String scope) { IndexableObject scopeObj = null; if (StringUtils.isNotBlank(scope)) { @@ -43,6 +55,16 @@ public IndexableObject resolveScope(Context context, String scope) { scopeObj = new IndexableCommunity(communityService.find(context, uuid)); if (scopeObj.getIndexedObject() == null) { scopeObj = new IndexableCollection(collectionService.find(context, uuid)); + if (scopeObj.getIndexedObject() == null) { + // Can't find the UUID as a community or collection + // so log and return null + log.warn( + "The given scope string " + + StringUtils.trimToEmpty(scope) + + " is not a collection or community UUID." + ); + scopeObj = null; + } } } catch (IllegalArgumentException ex) { log.warn("The given scope string " + StringUtils.trimToEmpty(scope) + " is not a UUID", ex); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/Utils.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/Utils.java index ed6e26ed0fb7..f66d794a8fe0 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/Utils.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/Utils.java @@ -43,10 +43,10 @@ import java.util.Set; import java.util.TreeSet; import java.util.UUID; -import javax.annotation.Nullable; -import javax.servlet.ServletRequest; -import javax.servlet.http.HttpServletRequest; +import jakarta.annotation.Nullable; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -267,8 +267,7 @@ private Serializable getIdentifierForLink(RestAddressableModel data) { */ public DSpaceRestRepository getResourceRepository(String apiCategory, String modelPlural) throws RepositoryNotFoundException { - String model = makeSingular(modelPlural); - return getResourceRepositoryByCategoryAndModel(apiCategory, model); + return getResourceRepositoryByCategoryAndModel(apiCategory, modelPlural); } /** @@ -276,16 +275,16 @@ public DSpaceRestRepository getResourceRepository(String apiCategory, String mod * as returned by the {@link RestAddressableModel#getType()} method * * @param apiCategory - * @param modelSingular + * @param modelPlural * @return the requested repository. * @throws RepositoryNotFoundException if no such repository can be found. */ - public DSpaceRestRepository getResourceRepositoryByCategoryAndModel(String apiCategory, String modelSingular) + public DSpaceRestRepository getResourceRepositoryByCategoryAndModel(String apiCategory, String modelPlural) throws RepositoryNotFoundException { try { - return applicationContext.getBean(apiCategory + "." + modelSingular, DSpaceRestRepository.class); + return applicationContext.getBean(apiCategory + "." + modelPlural, DSpaceRestRepository.class); } catch (NoSuchBeanDefinitionException e) { - throw new RepositoryNotFoundException(apiCategory, modelSingular); + throw new RepositoryNotFoundException(apiCategory, modelPlural); } } @@ -343,11 +342,10 @@ public static String makeSingular(String modelPlural) { * @return */ public LinkRestRepository getLinkResourceRepository(String apiCategory, String modelPlural, String rel) { - String model = makeSingular(modelPlural); try { - return applicationContext.getBean(apiCategory + "." + model + "." + rel, LinkRestRepository.class); + return applicationContext.getBean(apiCategory + "." + modelPlural + "." + rel, LinkRestRepository.class); } catch (NoSuchBeanDefinitionException e) { - throw new RepositoryNotFoundException(apiCategory, model); + throw new RepositoryNotFoundException(apiCategory, modelPlural); } } @@ -742,7 +740,7 @@ void embedRelFromRepository(HALResource resource } Projection projection = resource.getContent().getProjection(); LinkRestRepository linkRepository = getLinkResourceRepository(resource.getContent().getCategory(), - resource.getContent().getType(), rel); + resource.getContent().getTypePlural(), rel); if (linkRepository.isEmbeddableRelation(resource.getContent(), rel)) { Method method = requireMethod(linkRepository.getClass(), linkRest.method()); Object contentId = getContentIdForLinkMethod(resource.getContent(), method); @@ -973,7 +971,7 @@ public Serializable castToPKClass(ReloadableEntityObjectRepository repository, S public Object getDSpaceAPIObjectFromRest(Context context, BaseObjectRest restObj) throws IllegalArgumentException, SQLException { DSpaceRestRepository repository = getResourceRepositoryByCategoryAndModel(restObj.getCategory(), - restObj.getType()); + restObj.getTypePlural()); Serializable pk = castToPKClass((ReloadableEntityObjectRepository) repository, restObj.getId().toString()); return ((ReloadableEntityObjectRepository) repository).findDomainObjectByPk(context, pk); } diff --git a/dspace-server-webapp/src/main/javadoc/overview.html b/dspace-server-webapp/src/main/javadoc/overview.html new file mode 100644 index 000000000000..5c58d2fc742e --- /dev/null +++ b/dspace-server-webapp/src/main/javadoc/overview.html @@ -0,0 +1,29 @@ + + + + + The DSpace Web API + + +

    + The REST back-end of DSpace. A front-end program such as + dspace-angular + can use this to query, fetch and manipulate DSpace objects and related + data. The REST layer sits between front-ends and the DSpace business + logic (chiefly in {@code dspace-api}). +

    + +

    Where To Find It:

    +
      +
    • Endpoint access authorization: {@link org.dspace.app.rest.security}
    • +
    + + diff --git a/dspace-server-webapp/src/main/resources/spring/spring-dspace-addon-validation-services.xml b/dspace-server-webapp/src/main/resources/spring/spring-dspace-addon-validation-services.xml index f39d553c9652..50204bf5c4fb 100644 --- a/dspace-server-webapp/src/main/resources/spring/spring-dspace-addon-validation-services.xml +++ b/dspace-server-webapp/src/main/resources/spring/spring-dspace-addon-validation-services.xml @@ -27,7 +27,19 @@ + + + + + + + + + + + + diff --git a/dspace-server-webapp/src/main/resources/spring/spring-dspace-core-services.xml b/dspace-server-webapp/src/main/resources/spring/spring-dspace-core-services.xml index bb56393d0b45..e865c0d5e748 100644 --- a/dspace-server-webapp/src/main/resources/spring/spring-dspace-core-services.xml +++ b/dspace-server-webapp/src/main/resources/spring/spring-dspace-core-services.xml @@ -20,18 +20,15 @@ - + - + - + @@ -39,90 +36,86 @@ - + - + - + - + - + + + + + + + - + - + - + - + - + - + + + + + + + - + - + - + - + - + @@ -130,6 +123,12 @@ + + + + + + @@ -137,4 +136,7 @@ + + + diff --git a/dspace-server-webapp/src/main/resources/static/index.html b/dspace-server-webapp/src/main/resources/static/index.html index c780286107d8..0b80f806767e 100644 --- a/dspace-server-webapp/src/main/resources/static/index.html +++ b/dspace-server-webapp/src/main/resources/static/index.html @@ -321,7 +321,7 @@

    Embedded Resources

    - +