diff --git a/.codecov.yml b/.codecov.yml index a628d33cbe..326dd3e0b2 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -4,13 +4,6 @@ # Can be validated via instructions at: # https://docs.codecov.io/docs/codecov-yaml#validate-your-repository-yaml -# Tell Codecov not to send a coverage notification until (at least) 2 builds are completed -# Since we run Unit & Integration tests in parallel, this lets Codecov know that coverage -# needs to be merged across those builds -codecov: - notify: - after_n_builds: 2 - # Settings related to code coverage analysis coverage: status: diff --git a/.dockerignore b/.dockerignore index 0e42960dc9..7d3bdc2b4b 100644 --- a/.dockerignore +++ b/.dockerignore @@ -6,6 +6,5 @@ dspace/modules/*/target/ Dockerfile.* dspace/src/main/docker/dspace-postgres-pgcrypto dspace/src/main/docker/dspace-postgres-pgcrypto-curl -dspace/src/main/docker/solr dspace/src/main/docker/README.md dspace/src/main/docker-compose/ diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index b11e3cd531..5b3f4336e6 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] -* Related to [REST Contract](https://github.com/DSpace/Rest7Contract) +* 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). @@ -22,5 +22,7 @@ _This checklist provides a reminder of what we are going to look for when review - [ ] 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). -- [ ] If my PR includes new, third-party 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 the REST API, I've linked to the REST Contract page (or open PR) related to this change. +- [ ] 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. +- [ ] If my PR fixes an issue ticket, I've [linked them together](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue). diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f3ecaeee24..99c9efe019 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,6 +6,9 @@ name: Build # Run this Build for all pushes / PRs to current branch on: [push, pull_request] +permissions: + contents: read # to fetch code (actions/checkout) + jobs: tests: runs-on: ubuntu-latest @@ -42,18 +45,18 @@ jobs: steps: # https://github.com/actions/checkout - name: Checkout codebase - uses: actions/checkout@v2 + uses: actions/checkout@v3 # https://github.com/actions/setup-java - name: Install JDK ${{ matrix.java }} - uses: actions/setup-java@v2 + uses: actions/setup-java@v3 with: java-version: ${{ matrix.java }} distribution: 'temurin' # https://github.com/actions/cache - name: Cache Maven dependencies - uses: actions/cache@v2 + uses: actions/cache@v3 with: # Cache entire ~/.m2/repository path: ~/.m2/repository @@ -71,11 +74,44 @@ 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@v2 + uses: actions/upload-artifact@v3 with: name: ${{ matrix.type }} results path: ${{ matrix.resultsdir }} - # https://github.com/codecov/codecov-action + # 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 + with: + name: ${{ matrix.type }} coverage report + path: 'dspace/target/site/jacoco-aggregate/jacoco.xml' + retention-days: 14 + + # Codecov upload is a separate job in order to allow us to restart this separate from the entire build/test + # job above. This is necessary because Codecov uploads seem to randomly fail at times. + # See https://community.codecov.com/t/upload-issues-unable-to-locate-build-via-github-actions-api/3954 + codecov: + # Must run after 'tests' job above + needs: tests + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + # Download artifacts from previous 'tests' job + - name: Download coverage artifacts + uses: actions/download-artifact@v3 + + # 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. + # + # Retry action: https://github.com/marketplace/actions/retry-action + # Codecov action: https://github.com/codecov/codecov-action - name: Upload coverage to Codecov.io - uses: codecov/codecov-action@v2 + uses: Wandalen/wretry.action@v1.0.36 + with: + action: codecov/codecov-action@v3 + # Try upload 5 times max + attempt_limit: 5 + # Run again in 30 seconds + attempt_delay: 30000 diff --git a/.github/workflows/codescan.yml b/.github/workflows/codescan.yml new file mode 100644 index 0000000000..7580b4ba3d --- /dev/null +++ b/.github/workflows/codescan.yml @@ -0,0 +1,59 @@ +# DSpace CodeQL code scanning configuration for GitHub +# https://docs.github.com/en/code-security/code-scanning +# +# NOTE: Code scanning must be run separate from our default build.yml +# because CodeQL requires a fresh build with all tests *disabled*. +name: "Code Scanning" + +# Run this code scan for all pushes / PRs to main branch. Also run once a week. +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + # Don't run if PR is only updating static documentation + paths-ignore: + - '**/*.md' + - '**/*.txt' + schedule: + - cron: "37 0 * * 1" + +jobs: + analyze: + name: Analyze Code + runs-on: ubuntu-latest + # Limit permissions of this GitHub action. Can only write to security-events + permissions: + actions: read + contents: read + security-events: write + + steps: + # https://github.com/actions/checkout + - name: Checkout repository + uses: actions/checkout@v3 + + # https://github.com/actions/setup-java + - name: Install JDK + uses: actions/setup-java@v3 + with: + java-version: 11 + distribution: 'temurin' + + # Initializes the CodeQL tools for scanning. + # https://github.com/github/codeql-action + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + # Codescan Javascript as well since a few JS files exist in REST API's interface + languages: java, javascript + + # Autobuild attempts to build any compiled languages + # NOTE: Based on testing, this autobuild process works well for DSpace. A custom + # DSpace build w/caching (like in build.yml) was about the same speed as autobuild. + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + # Perform GitHub Code Scanning. + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 7f58a49f9e..971954a5e1 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -12,6 +12,9 @@ on: - 'dspace-**' pull_request: +permissions: + contents: read # to fetch code (actions/checkout) + jobs: docker: # Ensure this job never runs on forked repos. It's only executed for 'dspace/dspace' @@ -40,11 +43,11 @@ jobs: steps: # https://github.com/actions/checkout - name: Checkout codebase - uses: actions/checkout@v2 + uses: actions/checkout@v3 # https://github.com/docker/setup-buildx-action - name: Setup Docker Buildx - uses: docker/setup-buildx-action@v1 + uses: docker/setup-buildx-action@v2 # https://github.com/docker/setup-qemu-action - name: Set up QEMU emulation to build for multiple architectures @@ -54,7 +57,7 @@ jobs: - name: Login to DockerHub # Only login if not a PR, as PRs only trigger a Docker build and not a push if: github.event_name != 'pull_request' - uses: docker/login-action@v1 + uses: docker/login-action@v2 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_ACCESS_TOKEN }} @@ -66,7 +69,7 @@ jobs: # Get Metadata for docker_build_deps step below - name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace-dependencies' image id: meta_build_deps - uses: docker/metadata-action@v3 + uses: docker/metadata-action@v4 with: images: dspace/dspace-dependencies tags: ${{ env.IMAGE_TAGS }} @@ -75,7 +78,7 @@ jobs: # https://github.com/docker/build-push-action - name: Build and push 'dspace-dependencies' image id: docker_build_deps - uses: docker/build-push-action@v2 + uses: docker/build-push-action@v3 with: context: . file: ./Dockerfile.dependencies @@ -93,7 +96,7 @@ jobs: # Get Metadata for docker_build step below - name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace' image id: meta_build - uses: docker/metadata-action@v3 + uses: docker/metadata-action@v4 with: images: dspace/dspace tags: ${{ env.IMAGE_TAGS }} @@ -101,7 +104,7 @@ jobs: - name: Build and push 'dspace' image id: docker_build - uses: docker/build-push-action@v2 + uses: docker/build-push-action@v3 with: context: . file: ./Dockerfile @@ -119,7 +122,7 @@ jobs: # Get Metadata for docker_build_test step below - name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace-test' image id: meta_build_test - uses: docker/metadata-action@v3 + uses: docker/metadata-action@v4 with: images: dspace/dspace tags: ${{ env.IMAGE_TAGS }} @@ -130,7 +133,7 @@ jobs: - name: Build and push 'dspace-test' image id: docker_build_test - uses: docker/build-push-action@v2 + uses: docker/build-push-action@v3 with: context: . file: ./Dockerfile.test @@ -148,7 +151,7 @@ jobs: # Get Metadata for docker_build_test step below - name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace-cli' image id: meta_build_cli - uses: docker/metadata-action@v3 + uses: docker/metadata-action@v4 with: images: dspace/dspace-cli tags: ${{ env.IMAGE_TAGS }} @@ -156,7 +159,7 @@ jobs: - name: Build and push 'dspace-cli' image id: docker_build_cli - uses: docker/build-push-action@v2 + uses: docker/build-push-action@v3 with: context: . file: ./Dockerfile.cli @@ -167,3 +170,86 @@ jobs: # Use tags / labels provided by 'docker/metadata-action' above tags: ${{ steps.meta_build_cli.outputs.tags }} labels: ${{ steps.meta_build_cli.outputs.labels }} + + ########################################### + # Build/Push the 'dspace/dspace-solr' image + ########################################### + # Get Metadata for docker_build_solr step below + - name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace-solr' image + id: meta_build_solr + uses: docker/metadata-action@v4 + with: + images: dspace/dspace-solr + tags: ${{ env.IMAGE_TAGS }} + flavor: ${{ env.TAGS_FLAVOR }} + + - name: Build and push 'dspace-solr' image + id: docker_build_solr + uses: docker/build-push-action@v3 + with: + context: . + file: ./dspace/src/main/docker/dspace-solr/Dockerfile + platforms: ${{ env.PLATFORMS }} + # For pull requests, we run the Docker build (to ensure no PR changes break the build), + # but we ONLY do an image push to DockerHub if it's NOT a PR + push: ${{ github.event_name != 'pull_request' }} + # Use tags / labels provided by 'docker/metadata-action' above + tags: ${{ steps.meta_build_solr.outputs.tags }} + labels: ${{ steps.meta_build_solr.outputs.labels }} + + ########################################################### + # Build/Push the 'dspace/dspace-postgres-pgcrypto' image + ########################################################### + # Get Metadata for docker_build_postgres step below + - name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace-postgres-pgcrypto' image + id: meta_build_postgres + uses: docker/metadata-action@v4 + with: + images: dspace/dspace-postgres-pgcrypto + tags: ${{ env.IMAGE_TAGS }} + flavor: ${{ env.TAGS_FLAVOR }} + + - name: Build and push 'dspace-postgres-pgcrypto' image + id: docker_build_postgres + uses: docker/build-push-action@v3 + with: + # Must build out of subdirectory to have access to install script for pgcrypto + context: ./dspace/src/main/docker/dspace-postgres-pgcrypto/ + dockerfile: Dockerfile + platforms: ${{ env.PLATFORMS }} + # For pull requests, we run the Docker build (to ensure no PR changes break the build), + # but we ONLY do an image push to DockerHub if it's NOT a PR + push: ${{ github.event_name != 'pull_request' }} + # Use tags / labels provided by 'docker/metadata-action' above + tags: ${{ steps.meta_build_postgres.outputs.tags }} + labels: ${{ steps.meta_build_postgres.outputs.labels }} + + ########################################################### + # Build/Push the 'dspace/dspace-postgres-pgcrypto' image ('-loadsql' tag) + ########################################################### + # Get Metadata for docker_build_postgres_loadsql step below + - name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace-postgres-pgcrypto-loadsql' image + id: meta_build_postgres_loadsql + uses: docker/metadata-action@v4 + with: + images: dspace/dspace-postgres-pgcrypto + tags: ${{ env.IMAGE_TAGS }} + # Suffix all tags with "-loadsql". Otherwise, it uses the same + # tagging logic as the primary 'dspace/dspace-postgres-pgcrypto' image above. + flavor: ${{ env.TAGS_FLAVOR }} + suffix=-loadsql + + - name: Build and push 'dspace-postgres-pgcrypto-loadsql' image + id: docker_build_postgres_loadsql + uses: docker/build-push-action@v3 + with: + # Must build out of subdirectory to have access to install script for pgcrypto + context: ./dspace/src/main/docker/dspace-postgres-pgcrypto-curl/ + dockerfile: Dockerfile + platforms: ${{ env.PLATFORMS }} + # For pull requests, we run the Docker build (to ensure no PR changes break the build), + # but we ONLY do an image push to DockerHub if it's NOT a PR + push: ${{ github.event_name != 'pull_request' }} + # Use tags / labels provided by 'docker/metadata-action' above + tags: ${{ steps.meta_build_postgres_loadsql.outputs.tags }} + labels: ${{ steps.meta_build_postgres_loadsql.outputs.labels }} \ No newline at end of file diff --git a/.github/workflows/issue_opened.yml b/.github/workflows/issue_opened.yml index 3ccdd22a0d..b4436dca3a 100644 --- a/.github/workflows/issue_opened.yml +++ b/.github/workflows/issue_opened.yml @@ -5,25 +5,22 @@ on: issues: types: [opened] +permissions: {} jobs: automation: runs-on: ubuntu-latest steps: # Add the new issue to a project board, if it needs triage - # See https://github.com/marketplace/actions/create-project-card-action - - name: Add issue to project board + # See https://github.com/actions/add-to-project + - name: Add issue to triage board # 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: technote-space/create-project-card-action@v1 + uses: actions/add-to-project@v0.5.0 # Note, the authentication token below is an ORG level Secret. - # It must be created/recreated manually via a personal access token with "public_repo" and "admin:org" permissions + # 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 # This is necessary because the "DSpace Backlog" project is an org level project (i.e. not repo specific) with: - GITHUB_TOKEN: ${{ secrets.ORG_PROJECT_TOKEN }} - PROJECT: DSpace Backlog - COLUMN: Triage - CHECK_ORG_PROJECT: true - # Ignore errors. - continue-on-error: true + github-token: ${{ secrets.TRIAGE_PROJECT_TOKEN }} + project-url: https://github.com/orgs/DSpace/projects/24 diff --git a/.github/workflows/label_merge_conflicts.yml b/.github/workflows/label_merge_conflicts.yml index dcbab18f1b..cc0c7099f4 100644 --- a/.github/workflows/label_merge_conflicts.yml +++ b/.github/workflows/label_merge_conflicts.yml @@ -5,21 +5,32 @@ name: Check for merge conflicts # NOTE: This means merge conflicts are only checked for when a PR is merged to main. on: push: - branches: - - main + branches: [ main ] + # So that the `conflict_label_name` is removed if conflicts are resolved, + # we allow this to run for `pull_request_target` so that github secrets are available. + pull_request_target: + types: [ synchronize ] + +permissions: {} jobs: triage: + # Ensure this job never runs on forked repos. It's only executed for 'dspace/dspace' + if: github.repository == 'dspace/dspace' runs-on: ubuntu-latest + permissions: + pull-requests: write steps: - # See: https://github.com/mschilde/auto-label-merge-conflicts/ + # See: https://github.com/prince-chrismc/label-merge-conflicts-action - name: Auto-label PRs with merge conflicts - uses: mschilde/auto-label-merge-conflicts@v2.0 + uses: prince-chrismc/label-merge-conflicts-action@v3 # Add "merge conflict" label if a merge conflict is detected. Remove it when resolved. # Note, the authentication token is created automatically # See: https://docs.github.com/en/actions/configuring-and-managing-workflows/authenticating-with-the-github_token with: - CONFLICT_LABEL_NAME: 'merge conflict' - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # Ignore errors - continue-on-error: true + conflict_label_name: 'merge conflict' + github_token: ${{ secrets.GITHUB_TOKEN }} + conflict_comment: | + Hi @${author}, + Conflicts have been detected against the base branch. + Please [resolve these conflicts](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/addressing-merge-conflicts/about-merge-conflicts) as soon as you can. Thanks! diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000..45a6af9ce5 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,45 @@ +# How to Contribute + +DSpace is a community built and supported project. We do not have a centralized development or support team, but have a dedicated group of volunteers who help us improve the software, documentation, resources, etc. + +* [Contribute new code via a Pull Request](#contribute-new-code-via-a-pull-request) +* [Contribute documentation](#contribute-documentation) +* [Help others on mailing lists or Slack](#help-others-on-mailing-lists-or-slack) +* [Join a working or interest group](#join-a-working-or-interest-group) + +## 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). + +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). +- [ ] 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). + +Additional details on the code contribution process can be found in our [Code Contribution Guidelines](https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines) + +## Contribute documentation + +DSpace Documentation is a collaborative effort in a shared Wiki. The latest documentation is at https://wiki.lyrasis.org/display/DSDOC7x + +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. + +## 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). + +## Join a working or interest group + +Most of the work in building/improving DSpace comes via [Working Groups](https://wiki.lyrasis.org/display/DSPACE/DSpace+Working+Groups) or [Interest Groups](https://wiki.lyrasis.org/display/DSPACE/DSpace+Interest+Groups). + +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 diff --git a/Dockerfile b/Dockerfile index 444a1bcf0b..f1ff6adf5a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -31,7 +31,7 @@ ARG TARGET_DIR=dspace-installer COPY --from=build /install /dspace-src WORKDIR /dspace-src # Create the initial install deployment using ANT -ENV ANT_VERSION 1.10.12 +ENV ANT_VERSION 1.10.13 ENV ANT_HOME /tmp/ant-$ANT_VERSION ENV PATH $ANT_HOME/bin:$PATH # Need wget to install ant diff --git a/Dockerfile.cli b/Dockerfile.cli index 76e559fc83..62e83b79ef 100644 --- a/Dockerfile.cli +++ b/Dockerfile.cli @@ -30,12 +30,12 @@ ARG TARGET_DIR=dspace-installer COPY --from=build /install /dspace-src WORKDIR /dspace-src # Create the initial install deployment using ANT -ENV ANT_VERSION 1.10.12 +ENV ANT_VERSION 1.10.13 ENV ANT_HOME /tmp/ant-$ANT_VERSION ENV PATH $ANT_HOME/bin:$PATH -# Need wget to install ant +# Need wget to install ant, and unzip for managing AIPs RUN apt-get update \ - && apt-get install -y --no-install-recommends wget \ + && apt-get install -y --no-install-recommends wget unzip \ && apt-get purge -y --auto-remove \ && rm -rf /var/lib/apt/lists/* # Download and install 'ant' diff --git a/Dockerfile.test b/Dockerfile.test index 5e70c5e539..4e9b2b5b43 100644 --- a/Dockerfile.test +++ b/Dockerfile.test @@ -58,9 +58,11 @@ COPY --from=ant_build /dspace $DSPACE_INSTALL # 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 +EXPOSE 8080 8009 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/) diff --git a/LICENSES_THIRD_PARTY b/LICENSES_THIRD_PARTY index 92d0b71a70..b96ea77648 100644 --- a/LICENSES_THIRD_PARTY +++ b/LICENSES_THIRD_PARTY @@ -30,9 +30,9 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * 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.12.6 - http://github.com/FasterXML/jackson) - * Jackson-core (com.fasterxml.jackson.core:jackson-core:2.12.6 - https://github.com/FasterXML/jackson-core) - * jackson-databind (com.fasterxml.jackson.core:jackson-databind:2.12.6.1 - http://github.com/FasterXML/jackson) + * 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) * 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.12.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) @@ -151,7 +151,7 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * 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.4.0 - https://commons.apache.org/proper/commons-bcel) + * 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) @@ -159,12 +159,12 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * 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.8.0 - https://commons.apache.org/dbcp/) + * Apache Commons DBCP (org.apache.commons:commons-dbcp2:2.9.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 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.9.0 - https://commons.apache.org/proper/commons-pool/) - * Apache Commons Text (org.apache.commons:commons-text:1.9 - https://commons.apache.org/proper/commons-text) + * Apache Commons Pool (org.apache.commons:commons-pool2:2.11.1 - https://commons.apache.org/proper/commons-pool/) + * Apache Commons Text (org.apache.commons:commons-text:1.10.0 - https://commons.apache.org/proper/commons-text) * 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) @@ -218,10 +218,10 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * Lucene Spatial Extras (org.apache.lucene:lucene-spatial-extras:8.11.1 - https://lucene.apache.org/lucene-parent/lucene-spatial-extras) * Lucene Spatial 3D (org.apache.lucene:lucene-spatial3d:8.11.1 - https://lucene.apache.org/lucene-parent/lucene-spatial3d) * Lucene Suggest (org.apache.lucene:lucene-suggest:8.11.1 - https://lucene.apache.org/lucene-parent/lucene-suggest) - * Apache FontBox (org.apache.pdfbox:fontbox:2.0.24 - http://pdfbox.apache.org/) + * Apache FontBox (org.apache.pdfbox:fontbox:2.0.27 - http://pdfbox.apache.org/) * PDFBox JBIG2 ImageIO plugin (org.apache.pdfbox:jbig2-imageio:3.0.3 - https://www.apache.org/jbig2-imageio/) * Apache JempBox (org.apache.pdfbox:jempbox:1.8.16 - http://www.apache.org/pdfbox-parent/jempbox/) - * Apache PDFBox (org.apache.pdfbox:pdfbox:2.0.24 - https://www.apache.org/pdfbox-parent/pdfbox/) + * Apache PDFBox (org.apache.pdfbox:pdfbox:2.0.27 - https://www.apache.org/pdfbox-parent/pdfbox/) * Apache PDFBox Debugger (org.apache.pdfbox:pdfbox-debugger:2.0.25 - https://www.apache.org/pdfbox-parent/pdfbox-debugger/) * Apache PDFBox tools (org.apache.pdfbox:pdfbox-tools:2.0.25 - https://www.apache.org/pdfbox-parent/pdfbox-tools/) * Apache XmpBox (org.apache.pdfbox:xmpbox:2.0.25 - https://www.apache.org/pdfbox-parent/xmpbox/) @@ -426,7 +426,7 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * 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-util (org.ow2.asm:asm-util:7.1 - http://asm.ow2.org/) - * PostgreSQL JDBC Driver (org.postgresql:postgresql:42.4.1 - https://jdbc.postgresql.org) + * PostgreSQL JDBC Driver (org.postgresql:postgresql:42.4.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/) @@ -589,7 +589,7 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * 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.25.2 - https://www.webjars.org) + * core-js (org.webjars.npm:core-js:3.28.0 - 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: diff --git a/README.md b/README.md index 37a46a70c9..af9158eff3 100644 --- a/README.md +++ b/README.md @@ -48,18 +48,7 @@ See [Running DSpace 7 with Docker Compose](dspace/src/main/docker-compose/README ## Contributing -DSpace is a community built and supported project. We do not have a centralized development or support team, -but have a dedicated group of volunteers who help us improve the software, documentation, resources, etc. - -We welcome contributions of any type. Here's a few basic guides that provide suggestions for contributing to DSpace: -* [How to Contribute to DSpace](https://wiki.lyrasis.org/display/DSPACE/How+to+Contribute+to+DSpace): How to contribute in general (via code, documentation, bug reports, expertise, etc) -* [Code Contribution Guidelines](https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines): How to give back code or contribute features, bug fixes, etc. -* [DSpace Community Advisory Team (DCAT)](https://wiki.lyrasis.org/display/cmtygp/DSpace+Community+Advisory+Team): If you are not a developer, we also have an interest group specifically for repository managers. The DCAT group meets virtually, once a month, and sends open invitations to join their meetings via the [DCAT mailing list](https://groups.google.com/d/forum/DSpaceCommunityAdvisoryTeam). - -We also encourage GitHub Pull Requests (PRs) at any time. Please see our [Development with Git](https://wiki.lyrasis.org/display/DSPACE/Development+with+Git) guide for more info. - -In addition, a listing of all known contributors to DSpace software can be -found online at: https://wiki.lyrasis.org/display/DSPACE/DSpaceContributors +See [Contributing documentation](CONTRIBUTING.md) ## Getting Help diff --git a/checkstyle.xml b/checkstyle.xml index 815edaec7b..e0fa808d83 100644 --- a/checkstyle.xml +++ b/checkstyle.xml @@ -92,9 +92,7 @@ For more information on CheckStyle configurations below, see: http://checkstyle. - - - + diff --git a/docker-compose.yml b/docker-compose.yml index f790257bdb..36ba6af2c9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -41,6 +41,8 @@ services: target: 8080 - published: 8009 target: 8009 + - published: 8000 + target: 8000 stdin_open: true tty: true volumes: @@ -60,13 +62,17 @@ services: while (! /dev/null 2>&1; do sleep 1; done; /dspace/bin/dspace database migrate catalina.sh run - # DSpace database container + # DSpace PostgreSQL database container dspacedb: container_name: dspacedb + # Uses a custom Postgres image with pgcrypto installed + image: "${DOCKER_OWNER:-dspace}/dspace-postgres-pgcrypto:${DSPACE_VER:-dspace-7_x}" + build: + # Must build out of subdirectory to have access to install script for pgcrypto + context: ./dspace/src/main/docker/dspace-postgres-pgcrypto/ environment: PGDATA: /pgdata - # Uses a custom Postgres image with pgcrypto installed - image: dspace/dspace-postgres-pgcrypto + POSTGRES_PASSWORD: dspace networks: dspacenet: ports: @@ -75,12 +81,17 @@ services: stdin_open: true tty: true volumes: + # Keep Postgres data directory between reboots - pgdata:/pgdata # DSpace Solr container dspacesolr: container_name: dspacesolr - # Uses official Solr image at https://hub.docker.com/_/solr/ - image: solr:8.11-slim + image: "${DOCKER_OWNER:-dspace}/dspace-solr:${DSPACE_VER:-dspace-7_x}" + build: + context: . + dockerfile: ./dspace/src/main/docker/dspace-solr/Dockerfile + args: + SOLR_VERSION: "${SOLR_VER:-8.11}" networks: dspacenet: ports: @@ -90,30 +101,25 @@ services: tty: true working_dir: /var/solr/data volumes: - # Mount our local Solr core configs so that they are available as Solr configsets on container - - ./dspace/solr/authority:/opt/solr/server/solr/configsets/authority - - ./dspace/solr/oai:/opt/solr/server/solr/configsets/oai - - ./dspace/solr/search:/opt/solr/server/solr/configsets/search - - ./dspace/solr/statistics:/opt/solr/server/solr/configsets/statistics # Keep Solr data directory between reboots - solr_data:/var/solr/data - # Initialize all DSpace Solr cores using the mounted local configsets (see above), then start Solr + # Initialize all DSpace Solr cores then start Solr: # * First, run precreate-core to create the core (if it doesn't yet exist). If exists already, this is a no-op - # * Second, copy updated configs from mounted configsets to this core. If it already existed, this updates core - # to the latest configs. If it's a newly created core, this is a no-op. + # * Second, copy configsets to this core: + # Updates to Solr configs require the container to be rebuilt/restarted: `docker compose -p d7 up -d --build dspacesolr` entrypoint: - /bin/bash - '-c' - | init-var-solr precreate-core authority /opt/solr/server/solr/configsets/authority - cp -r -u /opt/solr/server/solr/configsets/authority/* authority + cp -r /opt/solr/server/solr/configsets/authority/* authority precreate-core oai /opt/solr/server/solr/configsets/oai - cp -r -u /opt/solr/server/solr/configsets/oai/* oai + cp -r /opt/solr/server/solr/configsets/oai/* oai precreate-core search /opt/solr/server/solr/configsets/search - cp -r -u /opt/solr/server/solr/configsets/search/* search + cp -r /opt/solr/server/solr/configsets/search/* search precreate-core statistics /opt/solr/server/solr/configsets/statistics - cp -r -u /opt/solr/server/solr/configsets/statistics/* statistics + cp -r /opt/solr/server/solr/configsets/statistics/* statistics exec solr -f volumes: assetstore: diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 79094ddbb8..c1811a9d63 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -12,7 +12,7 @@ org.dspace dspace-parent - 7.4 + 7.6-SNAPSHOT .. @@ -589,13 +589,6 @@ solr-core test ${solr.client.version} - - - - org.apache.commons - commons-text - - org.apache.lucene @@ -783,7 +776,7 @@ org.json json - 20180130 + 20230227 @@ -813,10 +806,11 @@ test - + org.apache.bcel bcel - 6.4.0 + 6.6.0 + test @@ -846,11 +840,6 @@ - - org.apache.commons - commons-text - 1.9 - io.netty netty-buffer diff --git a/dspace-api/src/main/java/org/dspace/alerts/AllowSessionsEnum.java b/dspace-api/src/main/java/org/dspace/alerts/AllowSessionsEnum.java new file mode 100644 index 0000000000..a200cab878 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/alerts/AllowSessionsEnum.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.alerts; + +/** + * Enum representing the options for allowing sessions: + * ALLOW_ALL_SESSIONS - Will allow all users to log in and continue their sessions + * ALLOW_CURRENT_SESSIONS_ONLY - Will prevent non admin users from logging in, however logged-in users + * will remain logged in + * ALLOW_ADMIN_SESSIONS_ONLY - Only admin users can log in, non admin sessions will be interrupted + * + * NOTE: This functionality can be stored in the database, but no support is present right now to interrupt and prevent + * sessions. + */ +public enum AllowSessionsEnum { + ALLOW_ALL_SESSIONS("all"), + ALLOW_CURRENT_SESSIONS_ONLY("current"), + ALLOW_ADMIN_SESSIONS_ONLY("admin"); + + private String allowSessionsType; + + AllowSessionsEnum(String allowSessionsType) { + this.allowSessionsType = allowSessionsType; + } + + public String getValue() { + return allowSessionsType; + } + + public static AllowSessionsEnum fromString(String alertAllowSessionType) { + if (alertAllowSessionType == null) { + return AllowSessionsEnum.ALLOW_ALL_SESSIONS; + } + + switch (alertAllowSessionType) { + case "all": + return AllowSessionsEnum.ALLOW_ALL_SESSIONS; + case "current": + return AllowSessionsEnum.ALLOW_CURRENT_SESSIONS_ONLY; + case "admin" : + return AllowSessionsEnum.ALLOW_ADMIN_SESSIONS_ONLY; + default: + throw new IllegalArgumentException("No corresponding enum value for provided string: " + + alertAllowSessionType); + } + } + + +} diff --git a/dspace-api/src/main/java/org/dspace/alerts/SystemWideAlert.java b/dspace-api/src/main/java/org/dspace/alerts/SystemWideAlert.java new file mode 100644 index 0000000000..f56cbdcce9 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/alerts/SystemWideAlert.java @@ -0,0 +1,179 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +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 org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.dspace.core.ReloadableEntity; +import org.hibernate.annotations.CacheConcurrencyStrategy; + +/** + * Database object representing system-wide alerts + */ +@Entity +@Cacheable +@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE, include = "non-lazy") +@Table(name = "systemwidealert") +public class SystemWideAlert implements ReloadableEntity { + + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "alert_id_seq") + @SequenceGenerator(name = "alert_id_seq", sequenceName = "alert_id_seq", allocationSize = 1) + @Column(name = "alert_id", unique = true, nullable = false) + private Integer alertId; + + @Column(name = "message", nullable = false) + private String message; + + @Column(name = "allow_sessions") + private String allowSessions; + + @Column(name = "countdown_to") + @Temporal(TemporalType.TIMESTAMP) + private Date countdownTo; + + @Column(name = "active") + private boolean active; + + protected SystemWideAlert() { + } + + /** + * This method returns the ID that the system-wide alert holds within the database + * + * @return The ID that the system-wide alert holds within the database + */ + @Override + public Integer getID() { + return alertId; + } + + /** + * Set the ID for the system-wide alert + * + * @param alertID The ID to set + */ + public void setID(final Integer alertID) { + this.alertId = alertID; + } + + /** + * Retrieve the message of the system-wide alert + * + * @return the message of the system-wide alert + */ + public String getMessage() { + return message; + } + + /** + * Set the message of the system-wide alert + * + * @param message The message to set + */ + public void setMessage(final String message) { + this.message = message; + } + + /** + * Retrieve what kind of sessions are allowed while the system-wide alert is active + * + * @return what kind of sessions are allowed while the system-wide alert is active + */ + public AllowSessionsEnum getAllowSessions() { + return AllowSessionsEnum.fromString(allowSessions); + } + + /** + * Set what kind of sessions are allowed while the system-wide alert is active + * + * @param allowSessions Integer representing what kind of sessions are allowed + */ + public void setAllowSessions(AllowSessionsEnum allowSessions) { + this.allowSessions = allowSessions.getValue(); + } + + /** + * Retrieve the date to which will be count down when the system-wide alert is active + * + * @return the date to which will be count down when the system-wide alert is active + */ + public Date getCountdownTo() { + return countdownTo; + } + + /** + * Set the date to which will be count down when the system-wide alert is active + * + * @param countdownTo The date to which will be count down + */ + public void setCountdownTo(final Date countdownTo) { + this.countdownTo = countdownTo; + } + + /** + * Retrieve whether the system-wide alert is active + * + * @return whether the system-wide alert is active + */ + public boolean isActive() { + return active; + } + + /** + * Set whether the system-wide alert is active + * + * @param active Whether the system-wide alert is active + */ + public void setActive(final boolean active) { + this.active = active; + } + + /** + * Return true if other is the same SystemWideAlert + * as this object, false otherwise + * + * @param other object to compare to + * @return true if object passed in represents the same + * system-wide alert as this object + */ + @Override + public boolean equals(Object other) { + return (other instanceof SystemWideAlert && + new EqualsBuilder().append(this.getID(), ((SystemWideAlert) other).getID()) + .append(this.getMessage(), ((SystemWideAlert) other).getMessage()) + .append(this.getAllowSessions(), ((SystemWideAlert) other).getAllowSessions()) + .append(this.getCountdownTo(), ((SystemWideAlert) other).getCountdownTo()) + .append(this.isActive(), ((SystemWideAlert) other).isActive()) + .isEquals()); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(this.getID()) + .append(this.getMessage()) + .append(this.getAllowSessions()) + .append(this.getCountdownTo()) + .append(this.isActive()) + .toHashCode(); + } + +} diff --git a/dspace-api/src/main/java/org/dspace/alerts/SystemWideAlertServiceImpl.java b/dspace-api/src/main/java/org/dspace/alerts/SystemWideAlertServiceImpl.java new file mode 100644 index 0000000000..9ddf6c97d1 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/alerts/SystemWideAlertServiceImpl.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.alerts; + +import java.io.IOException; +import java.sql.SQLException; +import java.util.Date; +import java.util.List; + +import org.apache.logging.log4j.Logger; +import org.dspace.alerts.dao.SystemWideAlertDAO; +import org.dspace.alerts.service.SystemWideAlertService; +import org.dspace.authorize.AuthorizeException; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.core.Context; +import org.dspace.core.LogHelper; +import org.dspace.eperson.EPerson; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * The implementation for the {@link SystemWideAlertService} class + */ +public class SystemWideAlertServiceImpl implements SystemWideAlertService { + + private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(SystemWideAlertService.class); + + + @Autowired + private SystemWideAlertDAO systemWideAlertDAO; + + @Autowired + private AuthorizeService authorizeService; + + @Override + public SystemWideAlert create(final Context context, final String message, + final AllowSessionsEnum allowSessionsType, + final Date countdownTo, final boolean active) throws SQLException, + AuthorizeException { + if (!authorizeService.isAdmin(context)) { + throw new AuthorizeException( + "Only administrators can create a system-wide alert"); + } + SystemWideAlert systemWideAlert = new SystemWideAlert(); + systemWideAlert.setMessage(message); + systemWideAlert.setAllowSessions(allowSessionsType); + systemWideAlert.setCountdownTo(countdownTo); + systemWideAlert.setActive(active); + + SystemWideAlert createdAlert = systemWideAlertDAO.create(context, systemWideAlert); + log.info(LogHelper.getHeader(context, "system_wide_alert_create", + "System Wide Alert has been created with message: '" + message + "' and ID " + + createdAlert.getID() + " and allowSessionsType " + allowSessionsType + + " and active set to " + active)); + + + return createdAlert; + } + + @Override + public SystemWideAlert find(final Context context, final int alertId) throws SQLException { + return systemWideAlertDAO.findByID(context, SystemWideAlert.class, alertId); + } + + @Override + public List findAll(final Context context) throws SQLException { + return systemWideAlertDAO.findAll(context, SystemWideAlert.class); + } + + @Override + public List findAll(final Context context, final int limit, final int offset) throws SQLException { + return systemWideAlertDAO.findAll(context, limit, offset); + } + + @Override + public List findAllActive(final Context context, final int limit, final int offset) + throws SQLException { + return systemWideAlertDAO.findAllActive(context, limit, offset); + } + + @Override + public void delete(final Context context, final SystemWideAlert systemWideAlert) + throws SQLException, IOException, AuthorizeException { + if (!authorizeService.isAdmin(context)) { + throw new AuthorizeException( + "Only administrators can create a system-wide alert"); + } + systemWideAlertDAO.delete(context, systemWideAlert); + log.info(LogHelper.getHeader(context, "system_wide_alert_create", + "System Wide Alert with ID " + systemWideAlert.getID() + " has been deleted")); + + } + + @Override + public void update(final Context context, final SystemWideAlert systemWideAlert) + throws SQLException, AuthorizeException { + if (!authorizeService.isAdmin(context)) { + throw new AuthorizeException( + "Only administrators can create a system-wide alert"); + } + systemWideAlertDAO.save(context, systemWideAlert); + + } + + @Override + public boolean canNonAdminUserLogin(Context context) throws SQLException { + List active = findAllActive(context, 1, 0); + if (active == null || active.isEmpty()) { + return true; + } + return active.get(0).getAllowSessions() == AllowSessionsEnum.ALLOW_ALL_SESSIONS; + } + + @Override + public boolean canUserMaintainSession(Context context, EPerson ePerson) throws SQLException { + if (authorizeService.isAdmin(context, ePerson)) { + return true; + } + List active = findAllActive(context, 1, 0); + if (active == null || active.isEmpty()) { + return true; + } + return active.get(0).getAllowSessions() != AllowSessionsEnum.ALLOW_ADMIN_SESSIONS_ONLY; + } +} diff --git a/dspace-api/src/main/java/org/dspace/alerts/dao/SystemWideAlertDAO.java b/dspace-api/src/main/java/org/dspace/alerts/dao/SystemWideAlertDAO.java new file mode 100644 index 0000000000..b26b647583 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/alerts/dao/SystemWideAlertDAO.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.alerts.dao; + +import java.sql.SQLException; +import java.util.List; + +import org.dspace.alerts.SystemWideAlert; +import org.dspace.core.Context; +import org.dspace.core.GenericDAO; + +/** + * This is the Data Access Object for the {@link SystemWideAlert} object + */ +public interface SystemWideAlertDAO extends GenericDAO { + + /** + * Returns a list of all SystemWideAlert objects in the database + * + * @param context The relevant DSpace context + * @param limit The limit for the amount of SystemWideAlerts returned + * @param offset The offset for the Processes to be returned + * @return The list of all SystemWideAlert objects in the Database + * @throws SQLException If something goes wrong + */ + List findAll(Context context, int limit, int offset) throws SQLException; + + /** + * Returns a list of all active SystemWideAlert objects in the database + * + * @param context The relevant DSpace context + * @param limit The limit for the amount of SystemWideAlerts returned + * @param offset The offset for the Processes to be returned + * @return The list of all SystemWideAlert objects in the Database + * @throws SQLException If something goes wrong + */ + List findAllActive(Context context, int limit, int offset) throws SQLException; + + +} 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 new file mode 100644 index 0000000000..13a0e0af23 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/alerts/dao/impl/SystemWideAlertDAOImpl.java @@ -0,0 +1,48 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.alerts.dao.impl; + +import java.sql.SQLException; +import java.util.List; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Root; + +import org.dspace.alerts.SystemWideAlert; +import org.dspace.alerts.SystemWideAlert_; +import org.dspace.alerts.dao.SystemWideAlertDAO; +import org.dspace.core.AbstractHibernateDAO; +import org.dspace.core.Context; + +/** + * Implementation class for the {@link SystemWideAlertDAO} + */ +public class SystemWideAlertDAOImpl extends AbstractHibernateDAO implements SystemWideAlertDAO { + + public List findAll(final Context context, final int limit, final int offset) throws SQLException { + CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); + CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, SystemWideAlert.class); + Root alertRoot = criteriaQuery.from(SystemWideAlert.class); + criteriaQuery.select(alertRoot); + + return list(context, criteriaQuery, false, SystemWideAlert.class, limit, offset); + } + + public List findAllActive(final Context context, final int limit, final int offset) + throws SQLException { + CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); + CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, SystemWideAlert.class); + Root alertRoot = criteriaQuery.from(SystemWideAlert.class); + criteriaQuery.select(alertRoot); + criteriaQuery.where(criteriaBuilder.equal(alertRoot.get(SystemWideAlert_.active), true)); + + return list(context, criteriaQuery, false, SystemWideAlert.class, limit, offset); + } + + +} diff --git a/dspace-api/src/main/java/org/dspace/alerts/service/SystemWideAlertService.java b/dspace-api/src/main/java/org/dspace/alerts/service/SystemWideAlertService.java new file mode 100644 index 0000000000..cf23130884 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/alerts/service/SystemWideAlertService.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.alerts.service; + +import java.io.IOException; +import java.sql.SQLException; +import java.util.Date; +import java.util.List; + +import org.dspace.alerts.AllowSessionsEnum; +import org.dspace.alerts.SystemWideAlert; +import org.dspace.authorize.AuthorizeException; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; + +/** + * An interface for the SystemWideAlertService with methods regarding the SystemWideAlert workload + */ +public interface SystemWideAlertService { + + /** + * This method will create a SystemWideAlert object in the database + * + * @param context The relevant DSpace context + * @param message The message of the system-wide alert + * @param allowSessionsType Which sessions need to be allowed for the system-wide alert + * @param countdownTo The date to which to count down to when the system-wide alert is active + * @param active Whether the system-wide alert os active + * @return The created SystemWideAlert object + * @throws SQLException If something goes wrong + */ + SystemWideAlert create(Context context, String message, AllowSessionsEnum allowSessionsType, + Date countdownTo, boolean active + ) throws SQLException, AuthorizeException; + + /** + * This method will retrieve a SystemWideAlert object from the Database with the given ID + * + * @param context The relevant DSpace context + * @param alertId The alert id on which we'll search for in the database + * @return The system-wide alert that holds the given alert id + * @throws SQLException If something goes wrong + */ + SystemWideAlert find(Context context, int alertId) throws SQLException; + + /** + * Returns a list of all SystemWideAlert objects in the database + * + * @param context The relevant DSpace context + * @return The list of all SystemWideAlert objects in the Database + * @throws SQLException If something goes wrong + */ + List findAll(Context context) throws SQLException; + + /** + * Returns a list of all SystemWideAlert objects in the database + * + * @param context The relevant DSpace context + * @param limit The limit for the amount of system-wide alerts returned + * @param offset The offset for the system-wide alerts to be returned + * @return The list of all SystemWideAlert objects in the Database + * @throws SQLException If something goes wrong + */ + List findAll(Context context, int limit, int offset) throws SQLException; + + + /** + * Returns a list of all active SystemWideAlert objects in the database + * + * @param context The relevant DSpace context + * @return The list of all active SystemWideAlert objects in the database + * @throws SQLException If something goes wrong + */ + List findAllActive(Context context, int limit, int offset) throws SQLException; + + /** + * This method will delete the given SystemWideAlert object from the database + * + * @param context The relevant DSpace context + * @param systemWideAlert The SystemWideAlert object to be deleted + * @throws SQLException If something goes wrong + */ + void delete(Context context, SystemWideAlert systemWideAlert) + throws SQLException, IOException, AuthorizeException; + + + /** + * This method will be used to update the given SystemWideAlert object in the database + * + * @param context The relevant DSpace context + * @param systemWideAlert The SystemWideAlert object to be updated + * @throws SQLException If something goes wrong + */ + void update(Context context, SystemWideAlert systemWideAlert) throws SQLException, AuthorizeException; + + + /** + * Verifies if the user connected to the current context can retain its session + * + * @param context The relevant DSpace context + * @return if the user connected to the current context can retain its session + */ + boolean canUserMaintainSession(Context context, EPerson ePerson) throws SQLException; + + + /** + * Verifies if a non admin user can log in + * + * @param context The relevant DSpace context + * @return if a non admin user can log in + */ + boolean canNonAdminUserLogin(Context context) throws SQLException; +} 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 1d53ca51c0..4161bbb4d8 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 @@ -598,18 +598,19 @@ public class MetadataImport extends DSpaceRunnable { .getItemImportService(); try { itemImportService.setTest(isTest); + itemImportService.setExcludeContent(isExcludeContent); itemImportService.setResume(isResume); itemImportService.setUseWorkflow(useWorkflow); itemImportService.setUseWorkflowSendEmail(useWorkflowSendEmail); @@ -233,6 +239,9 @@ public class ItemImport extends DSpaceRunnable { if (zip) { FileUtils.deleteDirectory(new File(sourcedir)); FileUtils.deleteDirectory(workDir); + if (remoteUrl && workFile != null && workFile.exists()) { + workFile.delete(); + } } Date endTime = new Date(); @@ -249,6 +258,17 @@ public class ItemImport extends DSpaceRunnable { * @param context */ protected void validate(Context context) { + // check zip type: uploaded file or remote url + if (commandLine.hasOption('z')) { + zipfilename = commandLine.getOptionValue('z'); + } else if (commandLine.hasOption('u')) { + remoteUrl = true; + zipfilename = commandLine.getOptionValue('u'); + } + if (StringUtils.isBlank(zipfilename)) { + throw new UnsupportedOperationException("Must run with either name of zip file or url of zip file"); + } + if (command == null) { handler.logError("Must run with either add, replace, or remove (run with -h flag for details)"); throw new UnsupportedOperationException("Must run with either add, replace, or remove"); @@ -291,7 +311,6 @@ public class ItemImport extends DSpaceRunnable { handler.writeFilestream(context, MAPFILE_FILENAME, mapfileInputStream, MAPFILE_BITSTREAM_TYPE); } finally { mapFile.delete(); - workFile.delete(); } } @@ -302,17 +321,24 @@ public class ItemImport extends DSpaceRunnable { * @throws Exception */ protected void readZip(Context context, ItemImportService itemImportService) throws Exception { - Optional optionalFileStream = handler.getFileStream(context, zipfilename); + Optional optionalFileStream = Optional.empty(); + if (!remoteUrl) { + // manage zip via upload + optionalFileStream = handler.getFileStream(context, zipfilename); + } else { + // manage zip via remote url + optionalFileStream = Optional.ofNullable(new URL(zipfilename).openStream()); + } if (optionalFileStream.isPresent()) { workFile = new File(itemImportService.getTempWorkDir() + File.separator + zipfilename + "-" + context.getCurrentUser().getID()); FileUtils.copyInputStreamToFile(optionalFileStream.get(), workFile); - workDir = new File(itemImportService.getTempWorkDir() + File.separator + TEMP_DIR); - sourcedir = itemImportService.unzip(workFile, workDir.getAbsolutePath()); } else { throw new IllegalArgumentException( "Error reading file, the file couldn't be found for filename: " + zipfilename); } + workDir = new File(itemImportService.getTempWorkDir() + File.separator + TEMP_DIR); + sourcedir = itemImportService.unzip(workFile, workDir.getAbsolutePath()); } /** @@ -352,7 +378,6 @@ public class ItemImport extends DSpaceRunnable { */ protected void setZip() { zip = true; - zipfilename = commandLine.getOptionValue('z'); } /** diff --git a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportCLI.java b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportCLI.java index 35de7b443a..1a71a8c4c0 100644 --- a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportCLI.java +++ b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportCLI.java @@ -8,10 +8,14 @@ package org.dspace.app.itemimport; import java.io.File; +import java.io.InputStream; +import java.net.URL; import java.sql.SQLException; import java.util.List; +import java.util.Optional; import java.util.UUID; +import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.StringUtils; import org.dspace.app.itemimport.service.ItemImportService; import org.dspace.content.Collection; @@ -62,7 +66,7 @@ public class ItemImportCLI extends ItemImport { handler.logError("Must run with either add, replace, or remove (run with -h flag for details)"); throw new UnsupportedOperationException("Must run with either add, replace, or remove"); } else if ("add".equals(command) || "replace".equals(command)) { - if (sourcedir == null) { + if (!remoteUrl && sourcedir == null) { handler.logError("A source directory containing items must be set (run with -h flag for details)"); throw new UnsupportedOperationException("A source directory containing items must be set"); } @@ -96,10 +100,25 @@ public class ItemImportCLI extends ItemImport { protected void readZip(Context context, ItemImportService itemImportService) throws Exception { // If this is a zip archive, unzip it first if (zip) { - workDir = new File(itemImportService.getTempWorkDir() + File.separator + TEMP_DIR - + File.separator + context.getCurrentUser().getID()); - sourcedir = itemImportService.unzip( - new File(sourcedir + File.separator + zipfilename), workDir.getAbsolutePath()); + if (!remoteUrl) { + workDir = new File(itemImportService.getTempWorkDir() + File.separator + TEMP_DIR + + File.separator + context.getCurrentUser().getID()); + sourcedir = itemImportService.unzip( + new File(sourcedir + File.separator + zipfilename), workDir.getAbsolutePath()); + } else { + // manage zip via remote url + Optional optionalFileStream = Optional.ofNullable(new URL(zipfilename).openStream()); + if (optionalFileStream.isPresent()) { + workFile = new File(itemImportService.getTempWorkDir() + File.separator + + zipfilename + "-" + context.getCurrentUser().getID()); + FileUtils.copyInputStreamToFile(optionalFileStream.get(), workFile); + } else { + throw new IllegalArgumentException( + "Error reading file, the file couldn't be found for filename: " + zipfilename); + } + workDir = new File(itemImportService.getTempWorkDir() + File.separator + TEMP_DIR); + sourcedir = itemImportService.unzip(workFile, workDir.getAbsolutePath()); + } } } @@ -120,6 +139,12 @@ public class ItemImportCLI extends ItemImport { zip = true; zipfilename = commandLine.getOptionValue('z'); } + + if (commandLine.hasOption('u')) { // remote url + zip = true; + remoteUrl = true; + zipfilename = commandLine.getOptionValue('u'); + } } @Override diff --git a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportCLIScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportCLIScriptConfiguration.java index 6582996f36..89abd7155b 100644 --- a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportCLIScriptConfiguration.java +++ b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportCLIScriptConfiguration.java @@ -13,7 +13,7 @@ import org.dspace.scripts.configuration.ScriptConfiguration; /** * The {@link ScriptConfiguration} for the {@link ItemImportCLI} script - * + * * @author Francesco Pio Scognamiglio (francescopio.scognamiglio at 4science.com) */ public class ItemImportCLIScriptConfiguration extends ItemImportScriptConfiguration { @@ -37,6 +37,9 @@ public class ItemImportCLIScriptConfiguration extends ItemImportScriptConfigurat options.addOption(Option.builder("z").longOpt("zip") .desc("name of zip file") .hasArg().required(false).build()); + options.addOption(Option.builder("u").longOpt("url") + .desc("url of zip file") + .hasArg().build()); options.addOption(Option.builder("c").longOpt("collection") .desc("destination collection(s) Handle or database ID") .hasArg().required(false).build()); @@ -55,6 +58,9 @@ public class ItemImportCLIScriptConfiguration extends ItemImportScriptConfigurat options.addOption(Option.builder("v").longOpt("validate") .desc("test run - do not actually import items") .hasArg(false).required(false).build()); + options.addOption(Option.builder("x").longOpt("exclude-bitstreams") + .desc("do not load or expect content bitstreams") + .hasArg(false).required(false).build()); options.addOption(Option.builder("p").longOpt("template") .desc("apply template") .hasArg(false).required(false).build()); diff --git a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportScriptConfiguration.java index fc761af0fa..2d304d8a7d 100644 --- a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportScriptConfiguration.java +++ b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportScriptConfiguration.java @@ -19,7 +19,7 @@ import org.springframework.beans.factory.annotation.Autowired; /** * The {@link ScriptConfiguration} for the {@link ItemImport} script - * + * * @author Francesco Pio Scognamiglio (francescopio.scognamiglio at 4science.com) */ public class ItemImportScriptConfiguration extends ScriptConfiguration { @@ -64,7 +64,10 @@ public class ItemImportScriptConfiguration extends ScriptC options.addOption(Option.builder("z").longOpt("zip") .desc("name of zip file") .type(InputStream.class) - .hasArg().required().build()); + .hasArg().build()); + options.addOption(Option.builder("u").longOpt("url") + .desc("url of zip file") + .hasArg().build()); options.addOption(Option.builder("c").longOpt("collection") .desc("destination collection(s) Handle or database ID") .hasArg().required(false).build()); @@ -81,6 +84,9 @@ public class ItemImportScriptConfiguration extends ScriptC options.addOption(Option.builder("v").longOpt("validate") .desc("test run - do not actually import items") .hasArg(false).required(false).build()); + options.addOption(Option.builder("x").longOpt("exclude-bitstreams") + .desc("do not load or expect content bitstreams") + .hasArg(false).required(false).build()); options.addOption(Option.builder("p").longOpt("template") .desc("apply template") .hasArg(false).required(false).build()); 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 047a589b66..4148232cf3 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 @@ -62,6 +62,7 @@ import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.exception.ExceptionUtils; +import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.app.itemimport.service.ItemImportService; import org.dspace.app.util.LocalSchemaFilenameFilter; @@ -135,7 +136,7 @@ import org.xml.sax.SAXException; * allow the registration of files (bitstreams) into DSpace. */ public class ItemImportServiceImpl implements ItemImportService, InitializingBean { - private final Logger log = org.apache.logging.log4j.LogManager.getLogger(ItemImportServiceImpl.class); + private final Logger log = LogManager.getLogger(); private DSpaceRunnableHandler handler; @@ -181,6 +182,7 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea protected String tempWorkDir; protected boolean isTest = false; + protected boolean isExcludeContent = false; protected boolean isResume = false; protected boolean useWorkflow = false; protected boolean useWorkflowSendEmail = false; @@ -950,9 +952,10 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea String qualifier = getAttributeValue(n, "qualifier"); //NodeValue(); // //getElementData(n, // "qualifier"); - String language = getAttributeValue(n, "language"); - if (language != null) { - language = language.trim(); + + String language = null; + if (StringUtils.isNotBlank(getAttributeValue(n, "language"))) { + language = getAttributeValue(n, "language").trim(); } if (!isQuiet) { @@ -1403,6 +1406,10 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea protected void processContentFileEntry(Context c, Item i, String path, String fileName, String bundleName, boolean primary) throws SQLException, IOException, AuthorizeException { + if (isExcludeContent) { + return; + } + String fullpath = path + File.separatorChar + fileName; // get an input stream @@ -2342,6 +2349,11 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea this.isTest = isTest; } + @Override + public void setExcludeContent(boolean isExcludeContent) { + this.isExcludeContent = isExcludeContent; + } + @Override public void setResume(boolean isResume) { this.isResume = isResume; 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 76314897ec..e99ece31b9 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 @@ -211,6 +211,13 @@ public interface ItemImportService { */ public void setTest(boolean isTest); + /** + * Set exclude-content flag. + * + * @param isExcludeContent true or false + */ + public void setExcludeContent(boolean isExcludeContent); + /** * Set resume flag * 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 a79fd42d59..d16243e3e3 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 @@ -14,6 +14,9 @@ import java.io.InputStream; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.dspace.content.Bitstream; import org.dspace.content.Bundle; import org.dspace.content.Item; @@ -119,6 +122,39 @@ public abstract class ImageMagickThumbnailFilter extends MediaFilter { f2.deleteOnExit(); ConvertCmd cmd = new ConvertCmd(); IMOperation op = new IMOperation(); + + // Optionally override ImageMagick's default density of 72 DPI to use a + // "supersample" when creating the PDF thumbnail. Note that I prefer to + // use the getProperty() method here instead of getIntPropert() because + // the latter always returns an integer (0 in the case it's not set). I + // would prefer to keep ImageMagick's default to itself rather than for + // us to set one. Also note that the density option *must* come before + // we open the input file. + String density = configurationService.getProperty(PRE + ".density"); + if (density != null) { + op.density(Integer.valueOf(density)); + } + + // Check the PDF's MediaBox and CropBox to see if they are the same. + // If not, then tell ImageMagick to use the CropBox when generating + // 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 + // 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 + // only need to check the first page, since that's what we use for + // generating the thumbnail (PDDocument uses a zero-based index). + PDPage pdfPage = PDDocument.load(f).getPage(0); + PDRectangle pdfPageMediaBox = pdfPage.getMediaBox(); + PDRectangle pdfPageCropBox = pdfPage.getCropBox(); + + // This option must come *before* we open the input file. + if (pdfPageCropBox != pdfPageMediaBox) { + op.define("pdf:use-cropbox=true"); + } + String s = "[" + page + "]"; op.addImage(f.getAbsolutePath() + s); if (configurationService.getBooleanProperty(PRE + ".flatten", true)) { 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 50efa68ff4..88061d1d4d 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 @@ -315,25 +315,25 @@ public class MediaFilterServiceImpl implements MediaFilterService, InitializingB // check if destination bitstream exists Bundle existingBundle = null; - Bitstream existingBitstream = null; + List existingBitstreams = new ArrayList(); List bundles = itemService.getBundles(item, formatFilter.getBundleName()); if (bundles.size() > 0) { - // only finds the last match (FIXME?) + // only finds the last matching bundle and all matching bitstreams in the proper bundle(s) for (Bundle bundle : bundles) { List bitstreams = bundle.getBitstreams(); for (Bitstream bitstream : bitstreams) { if (bitstream.getName().trim().equals(newName.trim())) { existingBundle = bundle; - existingBitstream = bitstream; + existingBitstreams.add(bitstream); } } } } // if exists and overwrite = false, exit - if (!overWrite && (existingBitstream != null)) { + if (!overWrite && (existingBitstreams.size() > 0)) { if (!isQuiet) { logInfo("SKIPPED: bitstream " + source.getID() + " (item: " + item.getHandle() + ") because '" + newName + "' already exists"); @@ -397,8 +397,8 @@ public class MediaFilterServiceImpl implements MediaFilterService, InitializingB Group anonymous = groupService.findByName(context, Group.ANONYMOUS); authorizeService.addPolicy(context, b, Constants.READ, anonymous); } else { - //- Inherit policies from the source bitstream - authorizeService.inheritPolicies(context, source, b); + //- replace the policies using the same in the source bitstream + authorizeService.replaceAllPolicies(context, source, b); } //do post-processing of the generated bitstream @@ -408,9 +408,8 @@ public class MediaFilterServiceImpl implements MediaFilterService, InitializingB logError("!!! OutOfMemoryError !!!"); } - // fixme - set date? // we are overwriting, so remove old bitstream - if (existingBitstream != null) { + for (Bitstream existingBitstream : existingBitstreams) { bundleService.removeBitstream(context, existingBundle, existingBitstream); } diff --git a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemAuthorExtractor.java b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemAuthorExtractor.java index bc97bc64bf..5c6e48ee3f 100644 --- a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemAuthorExtractor.java +++ b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemAuthorExtractor.java @@ -31,5 +31,5 @@ public interface RequestItemAuthorExtractor { */ @NonNull public List getRequestItemAuthor(Context context, Item item) - throws SQLException; + throws SQLException; } 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 02054ee1a0..8d19597547 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 @@ -56,7 +56,8 @@ public class RequestItemEmailNotifier { private static final RequestItemAuthorExtractor requestItemAuthorExtractor = DSpaceServicesFactory.getInstance() .getServiceManager() - .getServiceByName(null, RequestItemAuthorExtractor.class); + .getServiceByName("requestItemAuthorExtractor", + RequestItemAuthorExtractor.class); private RequestItemEmailNotifier() {} @@ -154,9 +155,9 @@ public class RequestItemEmailNotifier { email.setContent("body", message); email.setSubject(subject); email.addRecipient(ri.getReqEmail()); - if (ri.isAccept_request()) { - // Attach bitstreams. - try { + // Attach bitstreams. + try { + if (ri.isAccept_request()) { if (ri.isAllfiles()) { Item item = ri.getItem(); List bundles = item.getBundles("ORIGINAL"); @@ -166,24 +167,39 @@ public class RequestItemEmailNotifier { if (!bitstream.getFormat(context).isInternal() && requestItemService.isRestricted(context, bitstream)) { + // #8636 Anyone receiving the email can respond to the + // request without authenticating into DSpace + context.turnOffAuthorisationSystem(); email.addAttachment(bitstreamService.retrieve(context, bitstream), bitstream.getName(), bitstream.getFormat(context).getMIMEType()); + context.restoreAuthSystemState(); } } } } else { Bitstream bitstream = ri.getBitstream(); + // #8636 Anyone receiving the email can respond to the request without authenticating into DSpace + context.turnOffAuthorisationSystem(); email.addAttachment(bitstreamService.retrieve(context, bitstream), bitstream.getName(), bitstream.getFormat(context).getMIMEType()); + context.restoreAuthSystemState(); } email.send(); - } catch (MessagingException | IOException | SQLException | AuthorizeException e) { - LOG.warn(LogHelper.getHeader(context, - "error_mailing_requestItem", e.getMessage())); - throw new IOException("Reply not sent: " + e.getMessage()); + } else { + boolean sendRejectEmail = configurationService + .getBooleanProperty("request.item.reject.email", true); + // Not all sites want the "refusal" to be sent back to the requester via + // email. However, by default, the rejection email is sent back. + if (sendRejectEmail) { + email.send(); + } } + } catch (MessagingException | IOException | SQLException | AuthorizeException e) { + LOG.warn(LogHelper.getHeader(context, + "error_mailing_requestItem", e.getMessage())); + throw new IOException("Reply not sent: " + e.getMessage()); } LOG.info(LogHelper.getHeader(context, "sent_attach_requestItem", "token={}"), ri.getToken()); @@ -220,8 +236,13 @@ public class RequestItemEmailNotifier { message.addArgument(bitstreamName); // {0} bitstream name or "all" message.addArgument(item.getHandle()); // {1} Item handle message.addArgument(ri.getToken()); // {2} Request token - message.addArgument(approver.getFullName()); // {3} Approver's name - message.addArgument(approver.getEmail()); // {4} Approver's address + if (approver != null) { + message.addArgument(approver.getFullName()); // {3} Approver's name + message.addArgument(approver.getEmail()); // {4} Approver's address + } else { + message.addArgument("anonymous approver"); // [3] Approver's name + message.addArgument(configurationService.getProperty("mail.admin")); // [4] Approver's address + } // Who gets this message? String recipient; diff --git a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemHelpdeskStrategy.java b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemHelpdeskStrategy.java index f440ba380a..dee0ed7a23 100644 --- a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemHelpdeskStrategy.java +++ b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemHelpdeskStrategy.java @@ -22,21 +22,27 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.lang.NonNull; /** - * RequestItem strategy to allow DSpace support team's helpdesk to receive requestItem request. - * With this enabled, then the Item author/submitter doesn't receive the request, but the helpdesk instead does. + * RequestItem strategy to allow DSpace support team's help desk to receive + * requestItem requests. With this enabled, the Item author/submitter doesn't + * receive the request, but the help desk instead does. * - * Failover to the RequestItemSubmitterStrategy, which means the submitter would get the request if there is no - * specified helpdesk email. + *

Fails over to the {@link RequestItemSubmitterStrategy}, which means the + * submitter would get the request if there is no specified help desk email. * * @author Sam Ottenhoff * @author Peter Dietz */ -public class RequestItemHelpdeskStrategy extends RequestItemSubmitterStrategy { +public class RequestItemHelpdeskStrategy + extends RequestItemSubmitterStrategy { + static final String P_HELPDESK_OVERRIDE + = "request.item.helpdesk.override"; + static final String P_MAIL_HELPDESK = "mail.helpdesk"; + @Autowired(required = true) protected EPersonService ePersonService; @Autowired(required = true) - private ConfigurationService configuration; + protected ConfigurationService configurationService; public RequestItemHelpdeskStrategy() { } @@ -45,9 +51,9 @@ public class RequestItemHelpdeskStrategy extends RequestItemSubmitterStrategy { @NonNull public List getRequestItemAuthor(Context context, Item item) throws SQLException { - boolean helpdeskOverridesSubmitter = configuration + boolean helpdeskOverridesSubmitter = configurationService .getBooleanProperty("request.item.helpdesk.override", false); - String helpDeskEmail = configuration.getProperty("mail.helpdesk"); + String helpDeskEmail = configurationService.getProperty("mail.helpdesk"); if (helpdeskOverridesSubmitter && StringUtils.isNotBlank(helpDeskEmail)) { List authors = new ArrayList<>(1); @@ -60,16 +66,18 @@ public class RequestItemHelpdeskStrategy extends RequestItemSubmitterStrategy { } /** - * Return a RequestItemAuthor object for the specified helpdesk email address. - * It makes an attempt to find if there is a matching eperson for the helpdesk address, to use the name, - * Otherwise it falls back to a helpdeskname key in the Messages.props. + * Return a RequestItemAuthor object for the specified help desk email address. + * It makes an attempt to find if there is a matching {@link EPerson} for + * the help desk address, to use its name. Otherwise it falls back to the + * {@code helpdeskname} key in {@code Messages.properties}. * * @param context context * @param helpDeskEmail email * @return RequestItemAuthor * @throws SQLException if database error */ - public RequestItemAuthor getHelpDeskPerson(Context context, String helpDeskEmail) throws SQLException { + public RequestItemAuthor getHelpDeskPerson(Context context, String helpDeskEmail) + throws SQLException { context.turnOffAuthorisationSystem(); EPerson helpdeskEPerson = ePersonService.findByEmail(context, helpDeskEmail); context.restoreAuthSystemState(); diff --git a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemServiceImpl.java index d2b249f6ec..b915cfedd3 100644 --- a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemServiceImpl.java @@ -9,6 +9,7 @@ package org.dspace.app.requestitem; import java.sql.SQLException; import java.util.Date; +import java.util.Iterator; import java.util.List; import org.apache.logging.log4j.LogManager; @@ -90,6 +91,11 @@ public class RequestItemServiceImpl implements RequestItemService { } } + @Override + public Iterator findByItem(Context context, Item item) throws SQLException { + return requestItemDAO.findByItem(context, item); + } + @Override public void update(Context context, RequestItem requestItem) { try { diff --git a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemSubmitterStrategy.java b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemSubmitterStrategy.java index dcc1a3e80e..6cfeee4426 100644 --- a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemSubmitterStrategy.java +++ b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemSubmitterStrategy.java @@ -22,7 +22,6 @@ import org.springframework.lang.NonNull; * @author Andrea Bollini */ public class RequestItemSubmitterStrategy implements RequestItemAuthorExtractor { - public RequestItemSubmitterStrategy() { } diff --git a/dspace-api/src/main/java/org/dspace/app/requestitem/dao/RequestItemDAO.java b/dspace-api/src/main/java/org/dspace/app/requestitem/dao/RequestItemDAO.java index 4a4ea6cd90..b36ae58e0c 100644 --- a/dspace-api/src/main/java/org/dspace/app/requestitem/dao/RequestItemDAO.java +++ b/dspace-api/src/main/java/org/dspace/app/requestitem/dao/RequestItemDAO.java @@ -8,8 +8,10 @@ package org.dspace.app.requestitem.dao; import java.sql.SQLException; +import java.util.Iterator; import org.dspace.app.requestitem.RequestItem; +import org.dspace.content.Item; import org.dspace.core.Context; import org.dspace.core.GenericDAO; @@ -32,4 +34,6 @@ public interface RequestItemDAO extends GenericDAO { * @throws SQLException passed through. */ public RequestItem findByToken(Context context, String token) throws SQLException; + + public Iterator findByItem(Context context, Item item) throws SQLException; } 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 fa1ed9ffeb..008174ded8 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 @@ -8,6 +8,8 @@ package org.dspace.app.requestitem.dao.impl; import java.sql.SQLException; +import java.util.Iterator; +import javax.persistence.Query; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Root; @@ -15,6 +17,7 @@ import javax.persistence.criteria.Root; import org.dspace.app.requestitem.RequestItem; import org.dspace.app.requestitem.RequestItem_; import org.dspace.app.requestitem.dao.RequestItemDAO; +import org.dspace.content.Item; import org.dspace.core.AbstractHibernateDAO; import org.dspace.core.Context; @@ -39,4 +42,10 @@ public class RequestItemDAOImpl extends AbstractHibernateDAO implem criteriaQuery.where(criteriaBuilder.equal(requestItemRoot.get(RequestItem_.token), token)); return uniqueResult(context, criteriaQuery, false, RequestItem.class); } + @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()); + return iterate(query); + } } diff --git a/dspace-api/src/main/java/org/dspace/app/requestitem/service/RequestItemService.java b/dspace-api/src/main/java/org/dspace/app/requestitem/service/RequestItemService.java index 5cab72e4e9..efac3b18bc 100644 --- a/dspace-api/src/main/java/org/dspace/app/requestitem/service/RequestItemService.java +++ b/dspace-api/src/main/java/org/dspace/app/requestitem/service/RequestItemService.java @@ -8,6 +8,7 @@ package org.dspace.app.requestitem.service; import java.sql.SQLException; +import java.util.Iterator; import java.util.List; import org.dspace.app.requestitem.RequestItem; @@ -62,6 +63,14 @@ public interface RequestItemService { */ public RequestItem findByToken(Context context, String token); + /** + * Retrieve a request based on the item. + * @param context current DSpace session. + * @param item the item to find requests for. + * @return the matching requests, or null if not found. + */ + public Iterator findByItem(Context context, Item item) throws SQLException; + /** * Save updates to the record. Only accept_request, and decision_date are set-able. * 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 d2830bf89a..11f9aadd86 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 @@ -10,6 +10,7 @@ package org.dspace.app.util; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; import javax.annotation.Nullable; @@ -131,10 +132,15 @@ public class DCInput { private boolean closedVocabulary = false; /** - * the regex to comply with, null if nothing + * the regex in ECMAScript standard format, usable also by rests. */ private String regex = null; + /** + * the computed pattern, null if nothing + */ + private Pattern pattern = null; + /** * allowed document types */ @@ -178,7 +184,7 @@ public class DCInput { //check if the input have a language tag language = Boolean.valueOf(fieldMap.get("language")); - valueLanguageList = new ArrayList(); + valueLanguageList = new ArrayList<>(); if (language) { String languageNameTmp = fieldMap.get("value-pairs-name"); if (StringUtils.isBlank(languageNameTmp)) { @@ -191,7 +197,7 @@ public class DCInput { repeatable = "true".equalsIgnoreCase(repStr) || "yes".equalsIgnoreCase(repStr); String nameVariantsString = fieldMap.get("name-variants"); - nameVariants = (StringUtils.isNotBlank(nameVariantsString)) ? + nameVariants = StringUtils.isNotBlank(nameVariantsString) ? nameVariantsString.equalsIgnoreCase("true") : false; label = fieldMap.get("label"); inputType = fieldMap.get("input-type"); @@ -203,17 +209,17 @@ public class DCInput { } hint = fieldMap.get("hint"); warning = fieldMap.get("required"); - required = (warning != null && warning.length() > 0); + required = warning != null && warning.length() > 0; visibility = fieldMap.get("visibility"); readOnly = fieldMap.get("readonly"); vocabulary = fieldMap.get("vocabulary"); - regex = fieldMap.get("regex"); + this.initRegex(fieldMap.get("regex")); String closedVocabularyStr = fieldMap.get("closedVocabulary"); closedVocabulary = "true".equalsIgnoreCase(closedVocabularyStr) || "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(","); @@ -238,6 +244,22 @@ public class DCInput { } + protected void initRegex(String regex) { + this.regex = null; + this.pattern = null; + if (regex != null) { + try { + Optional.ofNullable(RegexPatternUtils.computePattern(regex)) + .ifPresent(pattern -> { + this.pattern = pattern; + this.regex = regex; + }); + } catch (PatternSyntaxException e) { + log.warn("The regex field of input {} with value {} is invalid!", this.label, regex); + } + } + } + /** * Is this DCInput for display in the given scope? The scope should be * either "workflow" or "submit", as per the input forms definition. If the @@ -248,7 +270,7 @@ public class DCInput { * @return whether the input should be displayed or not */ public boolean isVisible(String scope) { - return (visibility == null || visibility.equals(scope)); + return visibility == null || visibility.equals(scope); } /** @@ -381,7 +403,7 @@ public class DCInput { /** * Get the style for this form field - * + * * @return the style */ public String getStyle() { @@ -512,8 +534,12 @@ public class DCInput { return visibility; } + public Pattern getPattern() { + return this.pattern; + } + public String getRegex() { - return regex; + return this.regex; } public String getFieldName() { @@ -546,8 +572,7 @@ public class DCInput { public boolean validate(String value) { if (StringUtils.isNotBlank(value)) { try { - if (StringUtils.isNotBlank(regex)) { - Pattern pattern = Pattern.compile(regex); + if (this.pattern != null) { if (!pattern.matcher(value).matches()) { return false; } @@ -557,7 +582,6 @@ public class DCInput { } } - return true; } 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 new file mode 100644 index 0000000000..578e57fb09 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/util/RegexPatternUtils.java @@ -0,0 +1,73 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.util; + +import static java.util.regex.Pattern.CASE_INSENSITIVE; + +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +import org.apache.commons.lang3.StringUtils; + +/** + * Utility class useful for check regex and patterns. + * + * @author Vincenzo Mecca (vins01-4science - vincenzo.mecca at 4science.com) + * + */ +public class RegexPatternUtils { + + // checks input having the format /{pattern}/{flags} + // allowed flags are: g,i,m,s,u,y + public static final String REGEX_INPUT_VALIDATOR = "(/?)(.+)\\1([gimsuy]*)"; + // flags usable inside regex definition using format (?i|m|s|u|y) + public static final String REGEX_FLAGS = "(?%s)"; + public static final Pattern PATTERN_REGEX_INPUT_VALIDATOR = + Pattern.compile(REGEX_INPUT_VALIDATOR, CASE_INSENSITIVE); + + /** + * 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. + *
+ * 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, + * an exception will be thrown otherwise. + * + * @param regex with format /{regex}/{flags} + * @return {@code Pattern} regex pattern instance + * @throws PatternSyntaxException + */ + public static final Pattern computePattern(String regex) throws PatternSyntaxException { + if (StringUtils.isBlank(regex)) { + return null; + } + Matcher inputMatcher = PATTERN_REGEX_INPUT_VALIDATOR.matcher(regex); + String regexPattern = regex; + String regexFlags = ""; + if (inputMatcher.matches()) { + regexPattern = + Optional.of(inputMatcher.group(2)) + .filter(StringUtils::isNotBlank) + .orElse(regex); + regexFlags = + Optional.ofNullable(inputMatcher.group(3)) + .filter(StringUtils::isNotBlank) + .map(flags -> String.format(REGEX_FLAGS, flags)) + .orElse("") + .replaceAll("g", ""); + } + return Pattern.compile(regexFlags + regexPattern); + } + + private RegexPatternUtils() {} + +} 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 8f155b6330..c1402499c4 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 @@ -51,6 +51,7 @@ import org.dspace.content.service.CollectionService; import org.dspace.content.service.CommunityService; import org.dspace.content.service.ItemService; import org.dspace.core.Context; +import org.dspace.core.I18nUtil; import org.dspace.discovery.IndexableObject; import org.dspace.discovery.indexobject.IndexableCollection; import org.dspace.discovery.indexobject.IndexableCommunity; @@ -91,6 +92,7 @@ public class SyndicationFeed { // default DC fields for entry protected String defaultTitleField = "dc.title"; + protected String defaultDescriptionField = "dc.description"; protected String defaultAuthorField = "dc.contributor.author"; protected String defaultDateField = "dc.date.issued"; private static final String[] defaultDescriptionFields = @@ -196,15 +198,15 @@ public class SyndicationFeed { // dso is null for the whole site, or a search without scope if (dso == null) { defaultTitle = configurationService.getProperty("dspace.name"); - feed.setDescription(localize(labels, MSG_FEED_DESCRIPTION)); + defaultDescriptionField = localize(labels, MSG_FEED_DESCRIPTION); objectURL = resolveURL(request, null); } else { Bitstream logo = null; if (dso instanceof IndexableCollection) { Collection col = ((IndexableCollection) dso).getIndexedObject(); defaultTitle = col.getName(); - feed.setDescription(collectionService.getMetadataFirstValue(col, - CollectionService.MD_SHORT_DESCRIPTION, Item.ANY)); + defaultDescriptionField = collectionService.getMetadataFirstValue(col, + CollectionService.MD_SHORT_DESCRIPTION, Item.ANY); logo = col.getLogo(); String cols = configurationService.getProperty("webui.feed.podcast.collections"); if (cols != null && cols.length() > 1 && cols.contains(col.getHandle())) { @@ -214,8 +216,8 @@ public class SyndicationFeed { } else if (dso instanceof IndexableCommunity) { Community comm = ((IndexableCommunity) dso).getIndexedObject(); defaultTitle = comm.getName(); - feed.setDescription(communityService.getMetadataFirstValue(comm, - CommunityService.MD_SHORT_DESCRIPTION, Item.ANY)); + defaultDescriptionField = communityService.getMetadataFirstValue(comm, + CommunityService.MD_SHORT_DESCRIPTION, Item.ANY); logo = comm.getLogo(); String comms = configurationService.getProperty("webui.feed.podcast.communities"); if (comms != null && comms.length() > 1 && comms.contains(comm.getHandle())) { @@ -230,6 +232,12 @@ public class SyndicationFeed { } feed.setTitle(labels.containsKey(MSG_FEED_TITLE) ? localize(labels, MSG_FEED_TITLE) : defaultTitle); + + if (defaultDescriptionField == null || defaultDescriptionField == "") { + defaultDescriptionField = I18nUtil.getMessage("org.dspace.app.util.SyndicationFeed.no-description"); + } + + feed.setDescription(defaultDescriptionField); feed.setLink(objectURL); feed.setPublishedDate(new Date()); feed.setUri(objectURL); 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 9c37fcee47..3b23660344 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/IPAuthentication.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/IPAuthentication.java @@ -52,11 +52,6 @@ public class IPAuthentication implements AuthenticationMethod { */ private static Logger log = org.apache.logging.log4j.LogManager.getLogger(IPAuthentication.class); - /** - * Whether to look for x-forwarded headers for logging IP addresses - */ - protected static Boolean useProxies; - /** * All the IP matchers */ @@ -250,7 +245,7 @@ public class IPAuthentication implements AuthenticationMethod { log.debug(LogHelper.getHeader(context, "authenticated", "special_groups=" + gsb.toString() - + " (by IP=" + addr + ", useProxies=" + useProxies.toString() + ")" + + " (by IP=" + addr + ")" )); } 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 f3c6022e02..afd82db863 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/LDAPAuthentication.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/LDAPAuthentication.java @@ -11,9 +11,11 @@ import static org.dspace.eperson.service.EPersonService.MD_PHONE; import java.io.IOException; import java.sql.SQLException; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Hashtable; +import java.util.Iterator; import java.util.List; import javax.naming.NamingEnumeration; import javax.naming.NamingException; @@ -64,6 +66,7 @@ import org.dspace.services.factory.DSpaceServicesFactory; * @author Reuben Pasquini * @author Samuel Ottenhoff * @author Ivan Masár + * @author Michael Plate */ public class LDAPAuthentication implements AuthenticationMethod { @@ -391,7 +394,7 @@ public class LDAPAuthentication protected String ldapGivenName = null; protected String ldapSurname = null; protected String ldapPhone = null; - protected String ldapGroup = null; + protected ArrayList ldapGroup = null; /** * LDAP settings @@ -406,9 +409,9 @@ public class LDAPAuthentication final String ldap_surname_field; final String ldap_phone_field; final String ldap_group_field; - final boolean useTLS; + SpeakerToLDAP(Logger thelog) { ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); @@ -547,7 +550,11 @@ public class LDAPAuthentication if (attlist[4] != null) { att = atts.get(attlist[4]); if (att != null) { - ldapGroup = (String) att.get(); + // loop through all groups returned by LDAP + ldapGroup = new ArrayList(); + for (NamingEnumeration val = att.getAll(); val.hasMoreElements(); ) { + ldapGroup.add((String) val.next()); + } } } @@ -693,48 +700,69 @@ public class LDAPAuthentication /* * Add authenticated users to the group defined in dspace.cfg by * the authentication-ldap.login.groupmap.* key. + * + * @param dn + * The string containing distinguished name of the user + * + * @param group + * List of strings with LDAP dn of groups + * + * @param context + * DSpace context */ - private void assignGroups(String dn, String group, Context context) { + private void assignGroups(String dn, ArrayList group, Context context) { if (StringUtils.isNotBlank(dn)) { System.out.println("dn:" + dn); int i = 1; String groupMap = configurationService.getProperty("authentication-ldap.login.groupmap." + i); - boolean cmp; + + // groupmap contains the mapping of LDAP groups to DSpace groups + // outer loop with the DSpace groups while (groupMap != null) { String t[] = groupMap.split(":"); String ldapSearchString = t[0]; String dspaceGroupName = t[1]; - if (group == null) { - cmp = StringUtils.containsIgnoreCase(dn, ldapSearchString + ","); - } else { - cmp = StringUtils.equalsIgnoreCase(group, ldapSearchString); - } + // list of strings with dn from LDAP groups + // inner loop + Iterator groupIterator = group.iterator(); + while (groupIterator.hasNext()) { - if (cmp) { - // assign user to this group - try { - Group ldapGroup = groupService.findByName(context, dspaceGroupName); - if (ldapGroup != null) { - groupService.addMember(context, ldapGroup, context.getCurrentUser()); - groupService.update(context, ldapGroup); - } else { - // The group does not exist - log.warn(LogHelper.getHeader(context, - "ldap_assignGroupsBasedOnLdapDn", - "Group defined in authentication-ldap.login.groupmap." + i - + " does not exist :: " + dspaceGroupName)); + // save the current entry from iterator for further use + String currentGroup = groupIterator.next(); + + // very much the old code from DSpace <= 7.5 + if (currentGroup == null) { + cmp = StringUtils.containsIgnoreCase(dn, ldapSearchString + ","); + } else { + cmp = StringUtils.equalsIgnoreCase(currentGroup, ldapSearchString); + } + + if (cmp) { + // assign user to this group + try { + Group ldapGroup = groupService.findByName(context, dspaceGroupName); + if (ldapGroup != null) { + groupService.addMember(context, ldapGroup, context.getCurrentUser()); + groupService.update(context, ldapGroup); + } else { + // The group does not exist + log.warn(LogHelper.getHeader(context, + "ldap_assignGroupsBasedOnLdapDn", + "Group defined in authentication-ldap.login.groupmap." + i + + " does not exist :: " + dspaceGroupName)); + } + } catch (AuthorizeException ae) { + log.debug(LogHelper.getHeader(context, + "assignGroupsBasedOnLdapDn could not authorize addition to " + + "group", + dspaceGroupName)); + } catch (SQLException e) { + log.debug(LogHelper.getHeader(context, "assignGroupsBasedOnLdapDn could not find group", + dspaceGroupName)); } - } catch (AuthorizeException ae) { - log.debug(LogHelper.getHeader(context, - "assignGroupsBasedOnLdapDn could not authorize addition to " + - "group", - dspaceGroupName)); - } catch (SQLException e) { - log.debug(LogHelper.getHeader(context, "assignGroupsBasedOnLdapDn could not find group", - dspaceGroupName)); } } 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 a9874afda6..34543c078a 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java @@ -31,10 +31,12 @@ 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.CollectionService; import org.dspace.content.service.WorkspaceItemService; import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.discovery.DiscoverQuery; +import org.dspace.discovery.DiscoverQuery.SORT_ORDER; import org.dspace.discovery.DiscoverResult; import org.dspace.discovery.IndexableObject; import org.dspace.discovery.SearchService; @@ -521,6 +523,15 @@ public class AuthorizeServiceImpl implements AuthorizeService { addPolicies(c, nonAdminPolicies, dest); } + @Override + public void replaceAllPolicies(Context context, DSpaceObject source, DSpaceObject dest) + throws SQLException, AuthorizeException { + // find all policies for the source object + List policies = getPolicies(context, source); + removeAllPolicies(context, dest); + addPolicies(context, policies, dest); + } + @Override public void switchPoliciesAction(Context context, DSpaceObject dso, int fromAction, int toAction) throws SQLException, AuthorizeException { @@ -830,7 +841,7 @@ public class AuthorizeServiceImpl implements AuthorizeService { query = formatCustomQuery(query); DiscoverResult discoverResult = getDiscoverResult(context, query + "search.resourcetype:" + IndexableCommunity.TYPE, - offset, limit); + offset, limit, null, null); for (IndexableObject solrCollections : discoverResult.getIndexableObjects()) { Community community = ((IndexableCommunity) solrCollections).getIndexedObject(); communities.add(community); @@ -852,7 +863,7 @@ public class AuthorizeServiceImpl implements AuthorizeService { query = formatCustomQuery(query); DiscoverResult discoverResult = getDiscoverResult(context, query + "search.resourcetype:" + IndexableCommunity.TYPE, - null, null); + null, null, null, null); return discoverResult.getTotalSearchResults(); } @@ -877,7 +888,7 @@ public class AuthorizeServiceImpl implements AuthorizeService { query = formatCustomQuery(query); DiscoverResult discoverResult = getDiscoverResult(context, query + "search.resourcetype:" + IndexableCollection.TYPE, - offset, limit); + offset, limit, CollectionService.SOLR_SORT_FIELD, SORT_ORDER.asc); for (IndexableObject solrCollections : discoverResult.getIndexableObjects()) { Collection collection = ((IndexableCollection) solrCollections).getIndexedObject(); collections.add(collection); @@ -899,7 +910,7 @@ public class AuthorizeServiceImpl implements AuthorizeService { query = formatCustomQuery(query); DiscoverResult discoverResult = getDiscoverResult(context, query + "search.resourcetype:" + IndexableCollection.TYPE, - null, null); + null, null, null, null); return discoverResult.getTotalSearchResults(); } @@ -919,7 +930,7 @@ public class AuthorizeServiceImpl implements AuthorizeService { } try { - DiscoverResult discoverResult = getDiscoverResult(context, query, null, null); + DiscoverResult discoverResult = getDiscoverResult(context, query, null, null, null, null); if (discoverResult.getTotalSearchResults() > 0) { return true; } @@ -931,7 +942,8 @@ public class AuthorizeServiceImpl implements AuthorizeService { return false; } - private DiscoverResult getDiscoverResult(Context context, String query, Integer offset, Integer limit) + private DiscoverResult getDiscoverResult(Context context, String query, Integer offset, Integer limit, + String sortField, SORT_ORDER sortOrder) throws SearchServiceException, SQLException { String groupQuery = getGroupToQuery(groupService.allMemberGroups(context, context.getCurrentUser())); @@ -947,7 +959,9 @@ public class AuthorizeServiceImpl implements AuthorizeService { if (limit != null) { discoverQuery.setMaxResults(limit); } - + if (sortField != null && sortOrder != null) { + discoverQuery.setSortField(sortField, sortOrder); + } return searchService.search(context, discoverQuery); } 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 954bb96990..c781400bae 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/ResourcePolicy.java +++ b/dspace-api/src/main/java/org/dspace/authorize/ResourcePolicy.java @@ -41,9 +41,16 @@ import org.hibernate.proxy.HibernateProxyHelper; @Entity @Table(name = "resourcepolicy") public class ResourcePolicy implements ReloadableEntity { + /** This policy was set on submission, to give the submitter access. */ public static String TYPE_SUBMISSION = "TYPE_SUBMISSION"; + + /** This policy was set to allow access by a workflow group. */ public static String TYPE_WORKFLOW = "TYPE_WORKFLOW"; + + /** This policy was explicitly set on this object. */ public static String TYPE_CUSTOM = "TYPE_CUSTOM"; + + /** This policy was copied from the containing object's default policies. */ public static String TYPE_INHERITED = "TYPE_INHERITED"; @Id @@ -93,7 +100,7 @@ public class ResourcePolicy implements ReloadableEntity { private String rptype; @Lob - @Type(type = "org.dspace.storage.rdbms.hibernate.DatabaseAwareLobType") + @Type(type = "org.hibernate.type.TextType") @Column(name = "rpdescription") private String rpdescription; diff --git a/dspace-api/src/main/java/org/dspace/authorize/package-info.java b/dspace-api/src/main/java/org/dspace/authorize/package-info.java new file mode 100644 index 0000000000..f36c39cfe3 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/authorize/package-info.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/ + */ + +/** + * Represents permissions for access to DSpace content. + * + *

Philosophy

+ * DSpace's authorization system follows the classical "police state" + * philosophy of security - the user can do nothing, unless it is + * specifically allowed. Those permissions are spelled out with + * {@link ResourcePolicy} objects, stored in the {@code resourcepolicy} table + * in the database. + * + *

Policies are attached to Content

+ * Resource Policies get assigned to all of the content objects in + * DSpace - collections, communities, items, bundles, and bitstreams. + * (Currently they are not attached to non-content objects such as + * {@code EPerson} or {@code Group}. But they could be, hence the name + * {@code ResourcePolicy} instead of {@code ContentPolicy}.) + * + *

Policies are tuples

+ * Authorization is based on evaluating the tuple of (object, action, actor), + * such as (ITEM, READ, EPerson John Smith) to check if the {@code EPerson} + * "John Smith" can read an item. {@code ResourcePolicy} objects are pretty + * simple, describing a single instance of (object, action, actor). If + * multiple actors are desired, such as groups 10, 11, and 12 are allowed to + * READ Item 13, you simply create a {@code ResourcePolicy} for each group. + * + *

Built-in groups

+ * The install process should create two built-in groups - {@code Anonymous} + * for anonymous/public access, and {@code Administrators} for administrators. + * Group {@code Anonymous} allows anyone access, even if not authenticated. + * Group {@code Administrators}' members have super-user rights, + * and are allowed to do any action to any object. + * + *

Policy types + * Policies have a "type" used to distinguish policies which are applied for + * specific purposes. + *
+ *
CUSTOM
+ *
These are created and assigned explicitly by users.
+ *
INHERITED
+ *
These are copied from a containing object's default policies.
+ *
SUBMISSION
+ *
These are applied during submission to give the submitter access while + * composing a submission.
+ *
WORKFLOW
+ *
These are automatically applied during workflow, to give curators + * access to submissions in their curation queues. They usually have an + * automatically-created workflow group as the actor.
+ * + *

Start and End dates

+ * A policy may have a start date and/or an end date. The policy is + * considered not valid before the start date or after the end date. No date + * means do not apply the related test. For example, embargo until a given + * date can be expressed by a READ policy with a given start date, and a + * limited-time offer by a READ policy with a given end date. + * + * @author dstuve + * @author mwood + */ +package org.dspace.authorize; diff --git a/dspace-api/src/main/java/org/dspace/authorize/package.html b/dspace-api/src/main/java/org/dspace/authorize/package.html deleted file mode 100644 index 66ce0f8247..0000000000 --- a/dspace-api/src/main/java/org/dspace/authorize/package.html +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - - -

Handles permissions for DSpace content. -

- -

Philosophy
-DSpace's authorization system follows the classical "police state" -philosophy of security - the user can do nothing, unless it is -specifically allowed. Those permissions are spelled out with -ResourcePolicy objects, stored in the resourcepolicy table in the -database. -

- -

Policies are attached to Content

-

Policies are attached to Content
-Resource Policies get assigned to all of the content objects in -DSpace - collections, communities, items, bundles, and bitstreams. -(Currently they are not attached to non-content objects such as EPerson -or Group. But they could be, hence the name ResourcePolicy instead of -ContentPolicy.) -

- -

Policies are tuples

-Authorization is based on evaluating the tuple of (object, action, who), -such as (ITEM, READ, EPerson John Smith) to check if the EPerson "John Smith" -can read an item. ResourcePolicy objects are pretty simple, describing a single instance of -(object, action, who). If multiple who's are desired, such as Groups 10, 11, and -12 are allowed to READ Item 13, you simply create a ResourcePolicy for each -group. -

- -

Special Groups

-The install process should create two special groups - group 0, for -anonymous/public access, and group 1 for administrators. -Group 0 (public/anonymous) allows anyone access, even if they are not -authenticated. Group 1's (admin) members have super-user rights, and -are allowed to do any action to any object. -

- -

Unused ResourcePolicy attributes

-ResourcePolicies have a few attributes that are currently unused, -but are included with the intent that they will be used someday. -One is start and end dates, for when policies will be active, so that -permissions for content can change over time. The other is the EPerson - -policies could apply to only a single EPerson, but for ease of -administration currently a Group is the recommended unit to use to -describe 'who'. -

- - - 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 6b097cdd73..36679f94c6 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 @@ -600,4 +600,17 @@ public interface AuthorizeService { * @return true if the current user can manage accounts */ boolean isAccountManager(Context context); + + /** + * Replace all the policies in the target object with exactly the same policies that exist in the source object + * + * @param context DSpace Context + * @param source source of policies + * @param dest destination of inherited policies + * @throws SQLException if there's a database problem + * @throws AuthorizeException if the current user is not authorized to add these policies + */ + public void replaceAllPolicies(Context context, DSpaceObject source, DSpaceObject dest) + throws SQLException, AuthorizeException; + } 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 f1d8b30242..726078d743 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 @@ -53,12 +53,19 @@ public interface ResourcePolicyService extends DSpaceCRUDService throws SQLException; /** - * Look for ResourcePolicies by DSpaceObject, Group, and action, ignoring IDs with a specific PolicyID. - * This method can be used to detect duplicate ResourcePolicies. + * Look for ResourcePolicies by DSpaceObject, Group, and action, ignoring + * IDs with a specific PolicyID. This method can be used to detect duplicate + * ResourcePolicies. * - * @param notPolicyID ResourcePolicies with this ID will be ignored while looking out for equal ResourcePolicies. - * @return List of resource policies for the same DSpaceObject, group and action but other policyID. - * @throws SQLException + * @param context current DSpace session. + * @param dso find policies for this object. + * @param group find policies referring to this group. + * @param action find policies for this action. + * @param notPolicyID ResourcePolicies with this ID will be ignored while + * looking out for equal ResourcePolicies. + * @return List of resource policies for the same DSpaceObject, group and + * action but other policyID. + * @throws SQLException passed through. */ public List findByTypeGroupActionExceptId(Context context, DSpaceObject dso, Group group, int action, int notPolicyID) @@ -68,6 +75,16 @@ public interface ResourcePolicyService extends DSpaceCRUDService public boolean isDateValid(ResourcePolicy resourcePolicy); + /** + * Create and persist a copy of a given ResourcePolicy, with an empty + * dSpaceObject field. + * + * @param context current DSpace session. + * @param resourcePolicy the policy to be copied. + * @return the copy. + * @throws SQLException passed through. + * @throws AuthorizeException passed through. + */ public ResourcePolicy clone(Context context, ResourcePolicy resourcePolicy) throws SQLException, AuthorizeException; public void removeAllPolicies(Context c, DSpaceObject o) throws SQLException, AuthorizeException; @@ -117,6 +134,7 @@ public interface ResourcePolicyService extends DSpaceCRUDService * @param ePerson ePerson whose policies want to find * @param offset the position of the first result to return * @param limit paging limit + * @return some of the policies referring to {@code ePerson}. * @throws SQLException if database error */ public List findByEPerson(Context context, EPerson ePerson, int offset, int limit) diff --git a/dspace-api/src/main/java/org/dspace/browse/BrowseDAO.java b/dspace-api/src/main/java/org/dspace/browse/BrowseDAO.java index 29debf64e2..03130e39e7 100644 --- a/dspace-api/src/main/java/org/dspace/browse/BrowseDAO.java +++ b/dspace-api/src/main/java/org/dspace/browse/BrowseDAO.java @@ -8,8 +8,8 @@ package org.dspace.browse; import java.util.List; -import java.util.UUID; +import org.dspace.content.DSpaceObject; import org.dspace.content.Item; /** @@ -140,21 +140,21 @@ public interface BrowseDAO { public void setAscending(boolean ascending); /** - * Get the database ID of the container object. The container object will be a + * Get the container object. The container object will be a * Community or a Collection. * - * @return the database id of the container, or -1 if none is set + * @return the container, or null if none is set */ - public UUID getContainerID(); + public DSpaceObject getContainer(); /** - * Set the database id of the container object. This should be the id of a - * Community or Collection. This will constrain the results of the browse - * to only items or values within items that appear in the given container. + * Set the container object. This should be a Community or Collection. + * This will constrain the results of the browse to only items or values within items that appear in the given + * container and add the related configuration default filters. * - * @param containerID community/collection internal ID (UUID) + * @param container community/collection */ - public void setContainerID(UUID containerID); + public void setContainer(DSpaceObject container); /** * get the name of the field in which to look for the container id. This is diff --git a/dspace-api/src/main/java/org/dspace/browse/BrowseEngine.java b/dspace-api/src/main/java/org/dspace/browse/BrowseEngine.java index 6f0235e6c1..351c362482 100644 --- a/dspace-api/src/main/java/org/dspace/browse/BrowseEngine.java +++ b/dspace-api/src/main/java/org/dspace/browse/BrowseEngine.java @@ -141,12 +141,12 @@ public class BrowseEngine { Collection col = (Collection) scope.getBrowseContainer(); dao.setContainerTable("collection2item"); dao.setContainerIDField("collection_id"); - dao.setContainerID(col.getID()); + dao.setContainer(col); } else if (scope.inCommunity()) { Community com = (Community) scope.getBrowseContainer(); dao.setContainerTable("communities2item"); dao.setContainerIDField("community_id"); - dao.setContainerID(com.getID()); + dao.setContainer(com); } } @@ -247,12 +247,12 @@ public class BrowseEngine { Collection col = (Collection) scope.getBrowseContainer(); dao.setContainerTable("collection2item"); dao.setContainerIDField("collection_id"); - dao.setContainerID(col.getID()); + dao.setContainer(col); } else if (scope.inCommunity()) { Community com = (Community) scope.getBrowseContainer(); dao.setContainerTable("communities2item"); dao.setContainerIDField("community_id"); - dao.setContainerID(com.getID()); + dao.setContainer(com); } } @@ -413,12 +413,12 @@ public class BrowseEngine { Collection col = (Collection) scope.getBrowseContainer(); dao.setContainerTable("collection2item"); dao.setContainerIDField("collection_id"); - dao.setContainerID(col.getID()); + dao.setContainer(col); } else if (scope.inCommunity()) { Community com = (Community) scope.getBrowseContainer(); dao.setContainerTable("communities2item"); dao.setContainerIDField("community_id"); - dao.setContainerID(com.getID()); + dao.setContainer(com); } } 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 aa30862e3c..1ce2e55886 100644 --- a/dspace-api/src/main/java/org/dspace/browse/CrossLinks.java +++ b/dspace-api/src/main/java/org/dspace/browse/CrossLinks.java @@ -59,7 +59,16 @@ public class CrossLinks { * @return true/false */ public boolean hasLink(String metadata) { - return links.containsKey(metadata); + return findLinkType(metadata) != null; + } + + /** + * Is there a link for the given browse name (eg 'author') + * @param browseIndexName + * @return true/false + */ + public boolean hasBrowseName(String browseIndexName) { + return links.containsValue(browseIndexName); } /** @@ -69,6 +78,41 @@ public class CrossLinks { * @return type */ public String getLinkType(String metadata) { - return links.get(metadata); + return findLinkType(metadata); + } + + /** + * Get full map of field->indexname link configurations + * @return + */ + public Map getLinks() { + return links; + } + + /** + * Find and return the browse name for a given metadata field. + * If the link key contains a wildcard eg dc.subject.*, it should + * match dc.subject.other, etc. + * @param metadata + * @return + */ + 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" + // 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); + } + } else { + // Exact match, if the key field has no .* wildcard + if (links.containsKey(metadata)) { + return links.get(key); + } + } + } + // No match + return null; } } diff --git a/dspace-api/src/main/java/org/dspace/browse/SolrBrowseDAO.java b/dspace-api/src/main/java/org/dspace/browse/SolrBrowseDAO.java index 391ed90771..e02367f6eb 100644 --- a/dspace-api/src/main/java/org/dspace/browse/SolrBrowseDAO.java +++ b/dspace-api/src/main/java/org/dspace/browse/SolrBrowseDAO.java @@ -8,17 +8,17 @@ package org.dspace.browse; import java.io.Serializable; -import java.sql.SQLException; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; -import java.util.UUID; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; +import org.apache.solr.client.solrj.util.ClientUtils; import org.dspace.authorize.factory.AuthorizeServiceFactory; import org.dspace.authorize.service.AuthorizeService; +import org.dspace.content.DSpaceObject; import org.dspace.content.Item; import org.dspace.core.Context; import org.dspace.discovery.DiscoverFacetField; @@ -30,6 +30,8 @@ import org.dspace.discovery.DiscoverResult.SearchDocument; import org.dspace.discovery.IndexableObject; import org.dspace.discovery.SearchService; import org.dspace.discovery.SearchServiceException; +import org.dspace.discovery.SearchUtils; +import org.dspace.discovery.configuration.DiscoveryConfiguration; import org.dspace.discovery.configuration.DiscoveryConfigurationParameters; import org.dspace.discovery.indexobject.IndexableItem; import org.dspace.services.factory.DSpaceServicesFactory; @@ -123,9 +125,9 @@ public class SolrBrowseDAO implements BrowseDAO { private String containerIDField = null; /** - * the database id of the container we are constraining to + * the container we are constraining to */ - private UUID containerID = null; + private DSpaceObject container = null; /** * the column that we are sorting results by @@ -175,7 +177,7 @@ public class SolrBrowseDAO implements BrowseDAO { if (sResponse == null) { DiscoverQuery query = new DiscoverQuery(); addLocationScopeFilter(query); - addStatusFilter(query); + addDefaultFilterQueries(query); if (distinct) { DiscoverFacetField dff; if (StringUtils.isNotBlank(startsWith)) { @@ -206,7 +208,8 @@ public class SolrBrowseDAO implements BrowseDAO { query.addFilterQueries("{!field f=" + facetField + "_partial}" + value); } if (StringUtils.isNotBlank(startsWith) && orderField != null) { - query.addFilterQueries("bi_" + orderField + "_sort:" + startsWith + "*"); + query.addFilterQueries( + "bi_" + orderField + "_sort:" + ClientUtils.escapeQueryChars(startsWith) + "*"); } // filter on item to be sure to don't include any other object // indexed in the Discovery Search core @@ -225,26 +228,19 @@ public class SolrBrowseDAO implements BrowseDAO { return sResponse; } - private void addStatusFilter(DiscoverQuery query) { - try { - if (!authorizeService.isAdmin(context) - && (authorizeService.isCommunityAdmin(context) - || authorizeService.isCollectionAdmin(context))) { - query.addFilterQueries(searcher.createLocationQueryForAdministrableItems(context)); + private void addLocationScopeFilter(DiscoverQuery query) { + if (container != null) { + if (containerIDField.startsWith("collection")) { + query.addFilterQueries("location.coll:" + container.getID()); + } else if (containerIDField.startsWith("community")) { + query.addFilterQueries("location.comm:" + container.getID()); } - } catch (SQLException ex) { - log.error("Error looking up authorization rights of current user", ex); } } - private void addLocationScopeFilter(DiscoverQuery query) { - if (containerID != null) { - if (containerIDField.startsWith("collection")) { - query.addFilterQueries("location.coll:" + containerID); - } else if (containerIDField.startsWith("community")) { - query.addFilterQueries("location.comm:" + containerID); - } - } + private void addDefaultFilterQueries(DiscoverQuery query) { + DiscoveryConfiguration discoveryConfiguration = SearchUtils.getDiscoveryConfiguration(container); + discoveryConfiguration.getDefaultFilterQueries().forEach(query::addFilterQueries); } @Override @@ -335,7 +331,7 @@ public class SolrBrowseDAO implements BrowseDAO { throws BrowseException { DiscoverQuery query = new DiscoverQuery(); addLocationScopeFilter(query); - addStatusFilter(query); + addDefaultFilterQueries(query); query.setMaxResults(0); query.addFilterQueries("search.resourcetype:" + IndexableItem.TYPE); @@ -396,8 +392,8 @@ public class SolrBrowseDAO implements BrowseDAO { * @see org.dspace.browse.BrowseDAO#getContainerID() */ @Override - public UUID getContainerID() { - return containerID; + public DSpaceObject getContainer() { + return container; } /* @@ -559,8 +555,8 @@ public class SolrBrowseDAO implements BrowseDAO { * @see org.dspace.browse.BrowseDAO#setContainerID(int) */ @Override - public void setContainerID(UUID containerID) { - this.containerID = containerID; + public void setContainer(DSpaceObject container) { + this.container = container; } 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 071bf3972f..cc89cea33a 100644 --- a/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java @@ -332,8 +332,8 @@ public class BitstreamServiceImpl extends DSpaceObjectServiceImpl imp } @Override - public List findDeletedBitstreams(Context context) throws SQLException { - return bitstreamDAO.findDeletedBitstreams(context); + public List findDeletedBitstreams(Context context, int limit, int offset) throws SQLException { + return bitstreamDAO.findDeletedBitstreams(context, limit, offset); } @Override 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 e54f609389..ddfd38694f 100644 --- a/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java @@ -43,6 +43,7 @@ import org.dspace.core.I18nUtil; import org.dspace.core.LogHelper; import org.dspace.core.service.LicenseService; import org.dspace.discovery.DiscoverQuery; +import org.dspace.discovery.DiscoverQuery.SORT_ORDER; import org.dspace.discovery.DiscoverResult; import org.dspace.discovery.IndexableObject; import org.dspace.discovery.SearchService; @@ -735,7 +736,7 @@ public class CollectionServiceImpl extends DSpaceObjectServiceImpl i collection.getID(), collection.getHandle(), getIdentifiers(context, collection))); // remove subscriptions - hmm, should this be in Subscription.java? - subscribeService.deleteByCollection(context, collection); + subscribeService.deleteByDspaceObject(context, collection); // Remove Template Item removeTemplateItem(context, collection); @@ -946,6 +947,7 @@ public class CollectionServiceImpl extends DSpaceObjectServiceImpl i discoverQuery.setDSpaceObjectFilter(IndexableCollection.TYPE); discoverQuery.setStart(offset); discoverQuery.setMaxResults(limit); + discoverQuery.setSortField(SOLR_SORT_FIELD, SORT_ORDER.asc); DiscoverResult resp = retrieveCollectionsWithSubmit(context, discoverQuery, null, community, q); for (IndexableObject solrCollections : resp.getIndexableObjects()) { Collection c = ((IndexableCollection) solrCollections).getIndexedObject(); @@ -1025,6 +1027,7 @@ public class CollectionServiceImpl extends DSpaceObjectServiceImpl i discoverQuery.setDSpaceObjectFilter(IndexableCollection.TYPE); discoverQuery.setStart(offset); discoverQuery.setMaxResults(limit); + discoverQuery.setSortField(SOLR_SORT_FIELD, SORT_ORDER.asc); DiscoverResult resp = retrieveCollectionsWithSubmit(context, discoverQuery, entityType, community, q); for (IndexableObject solrCollections : resp.getIndexableObjects()) { 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 d0c414eba2..923745f761 100644 --- a/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java @@ -36,6 +36,7 @@ import org.dspace.core.I18nUtil; import org.dspace.core.LogHelper; import org.dspace.eperson.Group; import org.dspace.eperson.service.GroupService; +import org.dspace.eperson.service.SubscribeService; import org.dspace.event.Event; import org.dspace.identifier.IdentifierException; import org.dspace.identifier.service.IdentifierService; @@ -73,7 +74,8 @@ public class CommunityServiceImpl extends DSpaceObjectServiceImpl imp protected SiteService siteService; @Autowired(required = true) protected IdentifierService identifierService; - + @Autowired(required = true) + protected SubscribeService subscribeService; protected CommunityServiceImpl() { super(); @@ -217,12 +219,12 @@ public class CommunityServiceImpl extends DSpaceObjectServiceImpl imp @Override public Bitstream setLogo(Context context, Community community, InputStream is) - throws AuthorizeException, IOException, SQLException { + throws AuthorizeException, IOException, SQLException { // Check authorisation // authorized to remove the logo when DELETE rights // authorized when canEdit if (!((is == null) && authorizeService.authorizeActionBoolean( - context, community, Constants.DELETE))) { + context, community, Constants.DELETE))) { canEdit(context, community); } @@ -242,7 +244,7 @@ public class CommunityServiceImpl extends DSpaceObjectServiceImpl imp // now create policy for logo bitstream // to match our READ policy List policies = authorizeService - .getPoliciesActionFilter(context, community, Constants.READ); + .getPoliciesActionFilter(context, community, Constants.READ); authorizeService.addPolicies(context, policies, newLogo); log.info(LogHelper.getHeader(context, "set_logo", @@ -549,6 +551,8 @@ public class CommunityServiceImpl extends DSpaceObjectServiceImpl imp context.addEvent(new Event(Event.DELETE, Constants.COMMUNITY, community.getID(), community.getHandle(), getIdentifiers(context, community))); + subscribeService.deleteByDspaceObject(context, community); + // Remove collections Iterator collections = community.getCollections().iterator(); 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 1ac88241f4..59217a109f 100644 --- a/dspace-api/src/main/java/org/dspace/content/DSpaceObject.java +++ b/dspace-api/src/main/java/org/dspace/content/DSpaceObject.java @@ -48,6 +48,12 @@ public abstract class DSpaceObject implements Serializable, ReloadableEntity metadata = new ArrayList<>(); @@ -116,7 +122,7 @@ public abstract class DSpaceObject implements Serializable, ReloadableEntity handle) { 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 24778824bf..2119959073 100644 --- a/dspace-api/src/main/java/org/dspace/content/DSpaceObjectServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/DSpaceObjectServiceImpl.java @@ -126,6 +126,11 @@ public abstract class DSpaceObjectServiceImpl implements } } + // Sort the metadataValues if they have been modified, + // is used to preserve the default order. + if (dso.isMetadataModified()) { + values.sort(MetadataValueComparators.defaultComparator); + } // Create an array of matching values return values; } @@ -542,7 +547,7 @@ public abstract class DSpaceObjectServiceImpl implements int add = 4 - tokens.length; if (add > 0) { - tokens = (String[]) ArrayUtils.addAll(tokens, new String[add]); + tokens = ArrayUtils.addAll(tokens, new String[add]); } return tokens; @@ -603,21 +608,18 @@ public abstract class DSpaceObjectServiceImpl implements //If two places are the same then the MetadataValue instance will be placed before the //RelationshipMetadataValue instance. //This is done to ensure that the order is correct. - metadataValues.sort(new Comparator() { - @Override - public int compare(MetadataValue o1, MetadataValue o2) { - int compare = o1.getPlace() - o2.getPlace(); - if (compare == 0) { - if (o1 instanceof RelationshipMetadataValue && o2 instanceof RelationshipMetadataValue) { - return compare; - } else if (o1 instanceof RelationshipMetadataValue) { - return 1; - } else if (o2 instanceof RelationshipMetadataValue) { - return -1; - } + metadataValues.sort((o1, o2) -> { + int compare = o1.getPlace() - o2.getPlace(); + if (compare == 0) { + if (o1 instanceof RelationshipMetadataValue && o2 instanceof RelationshipMetadataValue) { + return compare; + } else if (o1 instanceof RelationshipMetadataValue) { + return 1; + } else if (o2 instanceof RelationshipMetadataValue) { + return -1; } - return compare; } + return compare; }); for (MetadataValue metadataValue : metadataValues) { //Retrieve & store the place for each metadata value @@ -634,7 +636,7 @@ public abstract class DSpaceObjectServiceImpl implements String authority = metadataValue.getAuthority(); String relationshipId = StringUtils.split(authority, "::")[1]; Relationship relationship = relationshipService.find(context, Integer.parseInt(relationshipId)); - if (relationship.getLeftItem().equals((Item) dso)) { + if (relationship.getLeftItem().equals(dso)) { relationship.setLeftPlace(mvPlace); } else { relationship.setRightPlace(mvPlace); 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 11cd4c107c..32c5b92c60 100644 --- a/dspace-api/src/main/java/org/dspace/content/InstallItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/InstallItemServiceImpl.java @@ -10,9 +10,14 @@ package org.dspace.content; import java.io.IOException; import java.sql.SQLException; import java.util.List; +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.factory.ContentServiceFactory; +import org.dspace.content.logic.Filter; +import org.dspace.content.logic.FilterUtils; import org.dspace.content.service.CollectionService; import org.dspace.content.service.InstallItemService; import org.dspace.content.service.ItemService; @@ -20,8 +25,11 @@ import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.embargo.service.EmbargoService; import org.dspace.event.Event; +import org.dspace.identifier.Identifier; import org.dspace.identifier.IdentifierException; import org.dspace.identifier.service.IdentifierService; +import org.dspace.supervision.SupervisionOrder; +import org.dspace.supervision.service.SupervisionOrderService; import org.springframework.beans.factory.annotation.Autowired; /** @@ -42,9 +50,13 @@ public class InstallItemServiceImpl implements InstallItemService { protected IdentifierService identifierService; @Autowired(required = true) protected ItemService itemService; + @Autowired(required = true) + protected SupervisionOrderService supervisionOrderService; + @Autowired(required = false) + + Logger log = LogManager.getLogger(InstallItemServiceImpl.class); protected InstallItemServiceImpl() { - } @Override @@ -59,10 +71,14 @@ public class InstallItemServiceImpl implements InstallItemService { AuthorizeException { Item item = is.getItem(); Collection collection = is.getCollection(); + // Get map of filters to use for identifier types. + Map, Filter> filters = FilterUtils.getIdentifierFilters(false); try { if (suppliedHandle == null) { - identifierService.register(c, item); + // Register with the filters we've set up + identifierService.register(c, item, filters); } else { + // This will register the handle but a pending DOI won't be compatible and so won't be registered identifierService.register(c, item, suppliedHandle); } } catch (IdentifierException e) { @@ -222,9 +238,19 @@ public class InstallItemServiceImpl implements InstallItemService { // set embargo lift date and take away read access if indicated. embargoService.setEmbargo(c, item); + // delete all related supervision orders + deleteSupervisionOrders(c, item); + return item; } + private void deleteSupervisionOrders(Context c, Item item) throws SQLException, AuthorizeException { + List supervisionOrders = supervisionOrderService.findByItem(c, item); + for (SupervisionOrder supervisionOrder : supervisionOrders) { + supervisionOrderService.delete(c, supervisionOrder); + } + } + @Override public String getBitstreamProvenanceMessage(Context context, Item myitem) throws SQLException { 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 27d0ba189c..a290cb0d99 100644 --- a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java @@ -12,7 +12,6 @@ import java.io.InputStream; import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; -import java.util.Comparator; import java.util.Date; import java.util.Iterator; import java.util.LinkedList; @@ -27,6 +26,8 @@ import java.util.stream.Stream; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; +import org.dspace.app.requestitem.RequestItem; +import org.dspace.app.requestitem.service.RequestItemService; import org.dspace.app.util.AuthorizeUtil; import org.dspace.authorize.AuthorizeConfiguration; import org.dspace.authorize.AuthorizeException; @@ -51,8 +52,15 @@ import org.dspace.content.virtual.VirtualMetadataPopulator; import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.core.LogHelper; +import org.dspace.discovery.DiscoverQuery; +import org.dspace.discovery.DiscoverResult; +import org.dspace.discovery.SearchService; +import org.dspace.discovery.SearchServiceException; +import org.dspace.discovery.indexobject.IndexableItem; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; +import org.dspace.eperson.service.GroupService; +import org.dspace.eperson.service.SubscribeService; import org.dspace.event.Event; import org.dspace.harvest.HarvestedItem; import org.dspace.harvest.service.HarvestedItemService; @@ -93,6 +101,8 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl implements It @Autowired(required = true) protected CommunityService communityService; @Autowired(required = true) + protected GroupService groupService; + @Autowired(required = true) protected AuthorizeService authorizeService; @Autowired(required = true) protected BundleService bundleService; @@ -105,6 +115,8 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl implements It @Autowired(required = true) protected InstallItemService installItemService; @Autowired(required = true) + protected SearchService searchService; + @Autowired(required = true) protected ResourcePolicyService resourcePolicyService; @Autowired(required = true) protected CollectionService collectionService; @@ -148,6 +160,11 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl implements It @Autowired(required = true) private ResearcherProfileService researcherProfileService; + @Autowired(required = true) + private RequestItemService requestItemService; + + @Autowired(required = true) + protected SubscribeService subscribeService; protected ItemServiceImpl() { super(); @@ -270,9 +287,10 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl implements It return itemDAO.findAll(context, true, true); } + @Override public Iterator findAllRegularItems(Context context) throws SQLException { return itemDAO.findAllRegularItems(context); - }; + } @Override public Iterator findBySubmitter(Context context, EPerson eperson) throws SQLException { @@ -755,7 +773,8 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl implements It log.info(LogHelper.getHeader(context, "delete_item", "item_id=" + item.getID())); - + //remove subscription related with it + subscribeService.deleteByDspaceObject(context, item); // Remove relationships for (Relationship relationship : relationshipService.findByItem(context, item, -1, -1, false, false)) { relationshipService.forceDelete(context, relationship, false, false); @@ -770,6 +789,8 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl implements It // remove version attached to the item removeVersion(context, item); + removeRequest(context, item); + removeOrcidSynchronizationStuff(context, item); // Also delete the item if it appears in a harvested collection. @@ -792,6 +813,14 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl implements It itemDAO.delete(context, item); } + protected void removeRequest(Context context, Item item) throws SQLException { + Iterator requestItems = requestItemService.findByItem(context, item); + while (requestItems.hasNext()) { + RequestItem requestItem = requestItems.next(); + requestItemService.delete(context, requestItem); + } + } + @Override public void removeAllBundles(Context context, Item item) throws AuthorizeException, SQLException, IOException { Iterator bundles = item.getBundles().iterator(); @@ -1025,7 +1054,7 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl implements It List linkedCollections = item.getCollections(); List notLinkedCollections = new ArrayList<>(allCollections.size() - linkedCollections.size()); - if ((allCollections.size() - linkedCollections.size()) == 0) { + if (allCollections.size() - linkedCollections.size() == 0) { return notLinkedCollections; } for (Collection collection : allCollections) { @@ -1065,6 +1094,53 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl implements It return collectionService.canEditBoolean(context, item.getOwningCollection(), false); } + /** + * Finds all Indexed Items where the current user has edit rights. If the user is an Admin, + * this is all Indexed Items. Otherwise, it includes those Items where + * an indexed "edit" policy lists either the eperson or one of the eperson's groups + * + * @param context DSpace context + * @param discoverQuery + * @return discovery search result objects + * @throws SQLException if something goes wrong + * @throws SearchServiceException if search error + */ + private DiscoverResult retrieveItemsWithEdit(Context context, DiscoverQuery discoverQuery) + throws SQLException, SearchServiceException { + EPerson currentUser = context.getCurrentUser(); + if (!authorizeService.isAdmin(context)) { + String userId = currentUser != null ? "e" + currentUser.getID().toString() : "e"; + Stream groupIds = groupService.allMemberGroupsSet(context, currentUser).stream() + .map(group -> "g" + group.getID()); + String query = Stream.concat(Stream.of(userId), groupIds) + .collect(Collectors.joining(" OR ", "edit:(", ")")); + discoverQuery.addFilterQueries(query); + } + return searchService.search(context, discoverQuery); + } + + @Override + public List findItemsWithEdit(Context context, int offset, int limit) + throws SQLException, SearchServiceException { + DiscoverQuery discoverQuery = new DiscoverQuery(); + discoverQuery.setDSpaceObjectFilter(IndexableItem.TYPE); + discoverQuery.setStart(offset); + discoverQuery.setMaxResults(limit); + DiscoverResult resp = retrieveItemsWithEdit(context, discoverQuery); + return resp.getIndexableObjects().stream() + .map(solrItems -> ((IndexableItem) solrItems).getIndexedObject()) + .collect(Collectors.toList()); + } + + @Override + public int countItemsWithEdit(Context context) throws SQLException, SearchServiceException { + DiscoverQuery discoverQuery = new DiscoverQuery(); + discoverQuery.setMaxResults(0); + discoverQuery.setDSpaceObjectFilter(IndexableItem.TYPE); + DiscoverResult resp = retrieveItemsWithEdit(context, discoverQuery); + return (int) resp.getTotalSearchResults(); + } + /** * Check if the item is an inprogress submission * @@ -1073,6 +1149,7 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl implements It * @return true if the item is an inprogress submission, i.e. a WorkspaceItem or WorkflowItem * @throws SQLException An exception that provides information on a database access error or other errors. */ + @Override public boolean isInProgressSubmission(Context context, Item item) throws SQLException { return workspaceItemService.findByItem(context, item) != null || workflowItemService.findByItem(context, item) != null; @@ -1103,8 +1180,8 @@ prevent the generation of resource policy entry values with null dspace_object a if (!authorizeService .isAnIdenticalPolicyAlreadyInPlace(context, dso, defaultPolicy.getGroup(), Constants.READ, defaultPolicy.getID()) && - ((!appendMode && this.isNotAlreadyACustomRPOfThisTypeOnDSO(context, dso)) || - (appendMode && this.shouldBeAppended(context, dso, defaultPolicy)))) { + (!appendMode && this.isNotAlreadyACustomRPOfThisTypeOnDSO(context, dso) || + appendMode && this.shouldBeAppended(context, dso, defaultPolicy))) { ResourcePolicy newPolicy = resourcePolicyService.clone(context, defaultPolicy); newPolicy.setdSpaceObject(dso); newPolicy.setAction(Constants.READ); @@ -1146,7 +1223,7 @@ prevent the generation of resource policy entry values with null dspace_object a * Check if the provided default policy should be appended or not to the final * item. If an item has at least one custom READ policy any anonymous READ * policy with empty start/end date should be skipped - * + * * @param context DSpace context * @param dso DSpace object to check for custom read RP * @param defaultPolicy The policy to check @@ -1535,7 +1612,7 @@ prevent the generation of resource policy entry values with null dspace_object a fullMetadataValueList.addAll(relationshipMetadataService.getRelationshipMetadata(item, true)); fullMetadataValueList.addAll(dbMetadataValues); - item.setCachedMetadata(sortMetadataValueList(fullMetadataValueList)); + item.setCachedMetadata(MetadataValueComparators.sort(fullMetadataValueList)); } log.debug("Called getMetadata for " + item.getID() + " based on cache"); @@ -1577,28 +1654,6 @@ prevent the generation of resource policy entry values with null dspace_object a } } - /** - * This method will sort the List of MetadataValue objects based on the MetadataSchema, MetadataField Element, - * MetadataField Qualifier and MetadataField Place in that order. - * @param listToReturn The list to be sorted - * @return The list sorted on those criteria - */ - private List sortMetadataValueList(List listToReturn) { - Comparator comparator = Comparator.comparing( - metadataValue -> metadataValue.getMetadataField().getMetadataSchema().getName(), - Comparator.nullsFirst(Comparator.naturalOrder())); - comparator = comparator.thenComparing(metadataValue -> metadataValue.getMetadataField().getElement(), - Comparator.nullsFirst(Comparator.naturalOrder())); - comparator = comparator.thenComparing(metadataValue -> metadataValue.getMetadataField().getQualifier(), - Comparator.nullsFirst(Comparator.naturalOrder())); - comparator = comparator.thenComparing(metadataValue -> metadataValue.getPlace(), - Comparator.nullsFirst(Comparator.naturalOrder())); - - Stream metadataValueStream = listToReturn.stream().sorted(comparator); - listToReturn = metadataValueStream.collect(Collectors.toList()); - return listToReturn; - } - @Override public MetadataValue addMetadata(Context context, Item dso, String schema, String element, String qualifier, String lang, String value, String authority, int confidence, int place) throws SQLException { 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 9ff3cb9ec2..31479e6206 100644 --- a/dspace-api/src/main/java/org/dspace/content/MetadataValue.java +++ b/dspace-api/src/main/java/org/dspace/content/MetadataValue.java @@ -19,6 +19,7 @@ import javax.persistence.Lob; import javax.persistence.ManyToOne; import javax.persistence.SequenceGenerator; import javax.persistence.Table; +import javax.persistence.Transient; import org.dspace.core.Context; import org.dspace.core.ReloadableEntity; @@ -59,7 +60,7 @@ public class MetadataValue implements ReloadableEntity { * The value of the field */ @Lob - @Type(type = "org.dspace.storage.rdbms.hibernate.DatabaseAwareLobType") + @Type(type = "org.hibernate.type.TextType") @Column(name = "text_value") private String value; @@ -171,6 +172,14 @@ public class MetadataValue implements ReloadableEntity { this.metadataField = metadataField; } + /** + * @return {@code MetadataField#getID()} + */ + @Transient + protected Integer getMetadataFieldId() { + return getMetadataField().getID(); + } + /** * Get the metadata value. * diff --git a/dspace-api/src/main/java/org/dspace/content/MetadataValueComparators.java b/dspace-api/src/main/java/org/dspace/content/MetadataValueComparators.java new file mode 100644 index 0000000000..306258f36a --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/MetadataValueComparators.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.content; + +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; + +/** + * This class contains only static members that can be used + * to sort list of {@link MetadataValue} + * + * @author Vincenzo Mecca (vins01-4science - vincenzo.mecca at 4science.com) + * + */ +public final class MetadataValueComparators { + + private MetadataValueComparators() {} + + /** + * This is the default comparator that mimics the ordering + * applied by the standard {@code @OrderBy} annotation inside + * {@link DSpaceObject#getMetadata()} + */ + public static final Comparator defaultComparator = + Comparator.comparing(MetadataValue::getMetadataFieldId) + .thenComparing( + MetadataValue::getPlace, + Comparator.nullsFirst(Comparator.naturalOrder()) + ); + + /** + * This method creates a new {@code List} ordered by the + * {@code MetadataComparators#defaultComparator}. + * + * @param metadataValues + * @return {@code List} ordered copy list using stream. + */ + public static final List sort(List metadataValues) { + return metadataValues + .stream() + .sorted(MetadataValueComparators.defaultComparator) + .collect(Collectors.toList()); + } + +} diff --git a/dspace-api/src/main/java/org/dspace/content/SupervisedItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/SupervisedItemServiceImpl.java deleted file mode 100644 index b0eb77ec2a..0000000000 --- a/dspace-api/src/main/java/org/dspace/content/SupervisedItemServiceImpl.java +++ /dev/null @@ -1,40 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.content; - -import java.sql.SQLException; -import java.util.List; - -import org.dspace.content.service.SupervisedItemService; -import org.dspace.content.service.WorkspaceItemService; -import org.dspace.core.Context; -import org.dspace.eperson.EPerson; -import org.springframework.beans.factory.annotation.Autowired; - -public class SupervisedItemServiceImpl implements SupervisedItemService { - - @Autowired(required = true) - protected WorkspaceItemService workspaceItemService; - - protected SupervisedItemServiceImpl() { - - } - - @Override - public List getAll(Context context) - throws SQLException { - return workspaceItemService.findAllSupervisedItems(context); - } - - @Override - public List findbyEPerson(Context context, EPerson ep) - throws SQLException { - return workspaceItemService.findSupervisedItemsByEPerson(context, ep); - } - -} 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 8049aa976c..a4c880173b 100644 --- a/dspace-api/src/main/java/org/dspace/content/WorkspaceItem.java +++ b/dspace-api/src/main/java/org/dspace/content/WorkspaceItem.java @@ -8,8 +8,6 @@ package org.dspace.content; import java.io.Serializable; -import java.util.ArrayList; -import java.util.List; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; @@ -17,8 +15,6 @@ import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.JoinColumn; -import javax.persistence.JoinTable; -import javax.persistence.ManyToMany; import javax.persistence.ManyToOne; import javax.persistence.OneToOne; import javax.persistence.SequenceGenerator; @@ -27,7 +23,6 @@ import javax.persistence.Table; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.dspace.core.Context; import org.dspace.eperson.EPerson; -import org.dspace.eperson.Group; import org.dspace.workflow.WorkflowItem; import org.hibernate.proxy.HibernateProxyHelper; @@ -78,14 +73,6 @@ public class WorkspaceItem @Column(name = "page_reached") private Integer pageReached = -1; - @ManyToMany(fetch = FetchType.LAZY) - @JoinTable( - name = "epersongroup2workspaceitem", - joinColumns = {@JoinColumn(name = "workspace_item_id")}, - inverseJoinColumns = {@JoinColumn(name = "eperson_group_id")} - ) - private final List supervisorGroups = new ArrayList<>(); - /** * Protected constructor, create object using: * {@link org.dspace.content.service.WorkspaceItemService#create(Context, Collection, boolean)} @@ -226,15 +213,4 @@ public class WorkspaceItem publishedBefore = b; } - public List getSupervisorGroups() { - return supervisorGroups; - } - - void removeSupervisorGroup(Group group) { - supervisorGroups.remove(group); - } - - void addSupervisorGroup(Group group) { - supervisorGroups.add(group); - } } 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 f40bb5256f..b6e7372af1 100644 --- a/dspace-api/src/main/java/org/dspace/content/WorkspaceItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/WorkspaceItemServiceImpl.java @@ -24,6 +24,8 @@ import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.ResourcePolicy; import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.dao.WorkspaceItemDAO; +import org.dspace.content.logic.Filter; +import org.dspace.content.logic.FilterUtils; import org.dspace.content.service.CollectionService; import org.dspace.content.service.ItemService; import org.dspace.content.service.WorkspaceItemService; @@ -32,6 +34,13 @@ import org.dspace.core.Context; import org.dspace.core.LogHelper; import org.dspace.eperson.EPerson; import org.dspace.event.Event; +import org.dspace.identifier.DOI; +import org.dspace.identifier.DOIIdentifierProvider; +import org.dspace.identifier.Identifier; +import org.dspace.identifier.IdentifierException; +import org.dspace.identifier.factory.IdentifierServiceFactory; +import org.dspace.identifier.service.DOIService; +import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.workflow.WorkflowItem; import org.dspace.workflow.WorkflowService; import org.springframework.beans.factory.annotation.Autowired; @@ -58,6 +67,8 @@ public class WorkspaceItemServiceImpl implements WorkspaceItemService { protected ItemService itemService; @Autowired(required = true) protected WorkflowService workflowService; + @Autowired(required = true) + protected DOIService doiService; protected WorkspaceItemServiceImpl() { @@ -160,6 +171,26 @@ public class WorkspaceItemServiceImpl implements WorkspaceItemService { } 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 + if (DSpaceServicesFactory.getInstance().getConfigurationService() + .getBooleanProperty("identifiers.submission.register", false)) { + try { + // Get map of filters to use for identifier types, while the item is in progress + Map, Filter> filters = FilterUtils.getIdentifierFilters(true); + IdentifierServiceFactory.getInstance().getIdentifierService().register(context, item, filters); + // Look for a DOI and move it to PENDING + DOI doi = doiService.findDOIByDSpaceObject(context, item); + if (doi != null) { + doi.setStatus(DOIIdentifierProvider.PENDING); + doiService.update(context, doi); + } + } catch (IdentifierException e) { + log.error("Could not register identifier(s) for item {}: {}", item.getID(), e.getMessage()); + } + } + workspaceItem.setItem(item); log.info(LogHelper.getHeader(context, "create_workspace_item", @@ -212,16 +243,6 @@ public class WorkspaceItemServiceImpl implements WorkspaceItemService { return workspaceItemDAO.findByItem(context, item); } - @Override - public List findAllSupervisedItems(Context context) throws SQLException { - return workspaceItemDAO.findWithSupervisedGroup(context); - } - - @Override - public List findSupervisedItemsByEPerson(Context context, EPerson ePerson) throws SQLException { - return workspaceItemDAO.findBySupervisedGroupMember(context, ePerson); - } - @Override public List findAll(Context context) throws SQLException { return workspaceItemDAO.findAll(context); @@ -268,10 +289,6 @@ public class WorkspaceItemServiceImpl implements WorkspaceItemService { "workspace_item_id=" + workspaceItem.getID() + "item_id=" + item.getID() + "collection_id=" + workspaceItem.getCollection().getID())); - // Need to delete the epersongroup2workspaceitem row first since it refers - // to workspaceitem ID - workspaceItem.getSupervisorGroups().clear(); - // Need to delete the workspaceitem row first since it refers // to item ID workspaceItemDAO.delete(context, workspaceItem); @@ -307,14 +324,6 @@ public class WorkspaceItemServiceImpl implements WorkspaceItemService { // deleteSubmitPermissions(); - // Need to delete the workspaceitem row first since it refers - // to item ID - try { - workspaceItem.getSupervisorGroups().clear(); - } catch (Exception e) { - log.error("failed to clear supervisor group", e); - } - workspaceItemDAO.delete(context, workspaceItem); } 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 dfaf4a107f..16632ee546 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 @@ -136,7 +136,9 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Hiera } protected String buildString(Node node) { - if (node.getNodeType() == Node.DOCUMENT_NODE) { + if (node.getNodeType() == Node.DOCUMENT_NODE || ( + node.getParentNode() != null && + node.getParentNode().getNodeType() == Node.DOCUMENT_NODE)) { return (""); } else { String parentValue = buildString(node.getParentNode()); 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 497fa08f2f..123626cd09 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 @@ -200,8 +200,8 @@ public class SolrAuthority implements ChoiceAuthority { } private String toQuery(String searchField, String text) { - return searchField + ":(" + text.toLowerCase().replaceAll(":", "\\:") + "*) or " + searchField + ":(" + text - .toLowerCase().replaceAll(":", "\\:") + ")"; + return searchField + ":(" + text.toLowerCase().replaceAll(":", "\\\\:") + "*) or " + searchField + ":(" + text + .toLowerCase().replaceAll(":", "\\\\:") + ")"; } @Override @@ -225,7 +225,7 @@ public class SolrAuthority implements ChoiceAuthority { log.debug("requesting label for key " + key + " using locale " + locale); } SolrQuery queryArgs = new SolrQuery(); - queryArgs.setQuery("id:" + key); + queryArgs.setQuery("id:" + key.replaceAll(":", "\\\\:")); queryArgs.setRows(1); QueryResponse searchResponse = getSearchService().search(queryArgs); SolrDocumentList docs = searchResponse.getResults(); 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 new file mode 100644 index 0000000000..05fda2b974 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/crosswalk/SubscriptionDsoMetadataForEmailCompose.java @@ -0,0 +1,80 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.crosswalk; + +import static org.dspace.content.Item.ANY; + +import java.io.OutputStream; +import java.io.PrintStream; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +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.factory.HandleServiceFactory; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Creates a String to be sent as email body for subscriptions + * + * @author Alba Aliu + */ +public class SubscriptionDsoMetadataForEmailCompose implements StreamDisseminationCrosswalk { + + private List metadata = new ArrayList<>(); + + @Autowired + private ItemService itemService; + + @Override + public boolean canDisseminate(Context context, DSpaceObject dso) { + return Objects.nonNull(dso) && dso.getType() == Constants.ITEM; + } + + @Override + public void disseminate(Context context, DSpaceObject dso, OutputStream out) throws SQLException { + if (dso.getType() == Constants.ITEM) { + Item item = (Item) dso; + PrintStream printStream = new PrintStream(out); + for (String actualMetadata : metadata) { + String[] splitted = actualMetadata.split("\\."); + String qualifier = null; + if (splitted.length == 1) { + qualifier = splitted[2]; + } + var metadataValue = itemService.getMetadataFirstValue(item, splitted[0], splitted[1], qualifier, ANY); + printStream.print(metadataValue + " "); + } + String itemURL = HandleServiceFactory.getInstance() + .getHandleService() + .resolveToURL(context, item.getHandle()); + printStream.print(itemURL); + printStream.print("\n"); + printStream.close(); + } + } + + @Override + public String getMIMEType() { + return "text/plain"; + } + + public List getMetadata() { + return metadata; + } + + public void setMetadata(List metadata) { + this.metadata = metadata; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/content/dao/BitstreamDAO.java b/dspace-api/src/main/java/org/dspace/content/dao/BitstreamDAO.java index c1ef923131..0d7afaa3cd 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/BitstreamDAO.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/BitstreamDAO.java @@ -29,7 +29,7 @@ public interface BitstreamDAO extends DSpaceObjectLegacySupportDAO { public Iterator findAll(Context context, int limit, int offset) throws SQLException; - public List findDeletedBitstreams(Context context) throws SQLException; + public List findDeletedBitstreams(Context context, int limit, int offset) throws SQLException; public List findDuplicateInternalIdentifier(Context context, Bitstream bitstream) throws SQLException; diff --git a/dspace-api/src/main/java/org/dspace/content/dao/WorkspaceItemDAO.java b/dspace-api/src/main/java/org/dspace/content/dao/WorkspaceItemDAO.java index 4ae8dc620b..900858b728 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/WorkspaceItemDAO.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/WorkspaceItemDAO.java @@ -41,10 +41,6 @@ public interface WorkspaceItemDAO extends GenericDAO { public List findAll(Context context, Integer limit, Integer offset) throws SQLException; - public List findWithSupervisedGroup(Context context) throws SQLException; - - public List findBySupervisedGroupMember(Context context, EPerson ePerson) throws SQLException; - int countRows(Context context) throws SQLException; List> getStageReachedCounts(Context context) 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 02e3509c31..d6d77fe7f0 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 @@ -41,13 +41,14 @@ public class BitstreamDAOImpl extends AbstractHibernateDSODAO impleme } @Override - public List findDeletedBitstreams(Context context) throws SQLException { + public List findDeletedBitstreams(Context context, int limit, int offset) throws SQLException { CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Bitstream.class); Root bitstreamRoot = criteriaQuery.from(Bitstream.class); criteriaQuery.select(bitstreamRoot); + criteriaQuery.orderBy(criteriaBuilder.desc(bitstreamRoot.get(Bitstream_.ID))); criteriaQuery.where(criteriaBuilder.equal(bitstreamRoot.get(Bitstream_.deleted), true)); - return list(context, criteriaQuery, false, Bitstream.class, -1, -1); + return list(context, criteriaQuery, false, Bitstream.class, limit, offset); } 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 de1b9a5aea..1384513655 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 @@ -15,7 +15,6 @@ 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 org.dspace.content.Collection; @@ -26,8 +25,6 @@ import org.dspace.content.dao.WorkspaceItemDAO; import org.dspace.core.AbstractHibernateDAO; import org.dspace.core.Context; import org.dspace.eperson.EPerson; -import org.dspace.eperson.EPerson_; -import org.dspace.eperson.Group; /** * Hibernate implementation of the Database Access Object interface class for the WorkspaceItem object. @@ -114,33 +111,6 @@ public class WorkspaceItemDAOImpl extends AbstractHibernateDAO im return list(context, criteriaQuery, false, WorkspaceItem.class, limit, offset); } - @Override - public List findWithSupervisedGroup(Context context) throws SQLException { - CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); - CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, WorkspaceItem.class); - Root workspaceItemRoot = criteriaQuery.from(WorkspaceItem.class); - criteriaQuery.select(workspaceItemRoot); - criteriaQuery.where(criteriaBuilder.isNotEmpty(workspaceItemRoot.get(WorkspaceItem_.supervisorGroups))); - - List orderList = new LinkedList<>(); - orderList.add(criteriaBuilder.asc(workspaceItemRoot.get(WorkspaceItem_.workspaceItemId))); - criteriaQuery.orderBy(orderList); - return list(context, criteriaQuery, false, WorkspaceItem.class, -1, -1); - } - - @Override - public List findBySupervisedGroupMember(Context context, EPerson ePerson) throws SQLException { - CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); - CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, WorkspaceItem.class); - Root workspaceItemRoot = criteriaQuery.from(WorkspaceItem.class); - Join join = workspaceItemRoot.join("supervisorGroups"); - Join secondJoin = join.join("epeople"); - criteriaQuery.select(workspaceItemRoot); - criteriaQuery.where(criteriaBuilder.equal(secondJoin.get(EPerson_.id), ePerson.getID())); - criteriaQuery.orderBy(criteriaBuilder.asc(workspaceItemRoot.get(WorkspaceItem_.workspaceItemId))); - return list(context, criteriaQuery, false, WorkspaceItem.class, -1, -1); - } - @Override public int countRows(Context context) throws SQLException { return count(createQuery(context, "SELECT count(*) from WorkspaceItem")); 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 4010e14861..0b06b34038 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 @@ -31,8 +31,8 @@ import org.dspace.content.service.MetadataValueService; import org.dspace.content.service.RelationshipService; import org.dspace.content.service.RelationshipTypeService; import org.dspace.content.service.SiteService; -import org.dspace.content.service.SupervisedItemService; import org.dspace.content.service.WorkspaceItemService; +import org.dspace.eperson.service.SubscribeService; import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.workflow.factory.WorkflowServiceFactory; @@ -71,10 +71,10 @@ public abstract class ContentServiceFactory { public abstract InstallItemService getInstallItemService(); - public abstract SupervisedItemService getSupervisedItemService(); - public abstract SiteService getSiteService(); + public abstract SubscribeService getSubscribeService(); + /** * Return the implementation of the RelationshipTypeService interface * @@ -114,11 +114,7 @@ public abstract class ContentServiceFactory { } public DSpaceObjectService getDSpaceObjectService(T dso) { - // No need to worry when supressing, as long as our "getDSpaceObjectManager" method is properly implemented - // no casting issues should occur - @SuppressWarnings("unchecked") - DSpaceObjectService manager = getDSpaceObjectService(dso.getType()); - return manager; + return getDSpaceObjectService(dso.getType()); } @SuppressWarnings("unchecked") 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 6f123ae1ba..e970f0bdab 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 @@ -28,8 +28,8 @@ import org.dspace.content.service.MetadataValueService; import org.dspace.content.service.RelationshipService; import org.dspace.content.service.RelationshipTypeService; import org.dspace.content.service.SiteService; -import org.dspace.content.service.SupervisedItemService; import org.dspace.content.service.WorkspaceItemService; +import org.dspace.eperson.service.SubscribeService; import org.springframework.beans.factory.annotation.Autowired; /** @@ -68,10 +68,9 @@ public class ContentServiceFactoryImpl extends ContentServiceFactory { @Autowired(required = true) private InstallItemService installItemService; @Autowired(required = true) - private SupervisedItemService supervisedItemService; - @Autowired(required = true) private SiteService siteService; - + @Autowired(required = true) + private SubscribeService subscribeService; @Autowired(required = true) private RelationshipService relationshipService; @Autowired(required = true) @@ -149,13 +148,13 @@ public class ContentServiceFactoryImpl extends ContentServiceFactory { } @Override - public SupervisedItemService getSupervisedItemService() { - return supervisedItemService; + public SiteService getSiteService() { + return siteService; } @Override - public SiteService getSiteService() { - return siteService; + public SubscribeService getSubscribeService() { + return subscribeService ; } @Override diff --git a/dspace-api/src/main/java/org/dspace/content/logic/DefaultFilter.java b/dspace-api/src/main/java/org/dspace/content/logic/DefaultFilter.java index 490c3949ea..1ac3930952 100644 --- a/dspace-api/src/main/java/org/dspace/content/logic/DefaultFilter.java +++ b/dspace-api/src/main/java/org/dspace/content/logic/DefaultFilter.java @@ -18,10 +18,10 @@ import org.dspace.core.Context; * statement as a property (unlike an operator) and takes no parameters (unlike a condition) * * @author Kim Shepherd - * @version $Revision$ */ public class DefaultFilter implements Filter { private LogicalStatement statement; + private String name; private final static Logger log = LogManager.getLogger(); /** @@ -44,4 +44,15 @@ public class DefaultFilter implements Filter { public boolean getResult(Context context, Item item) throws LogicalStatementException { return this.statement.getResult(context, item); } + + @Override + public void setBeanName(String name) { + log.debug("Initialize bean " + name); + this.name = name; + } + + @Override + public String getName() { + return name; + } } diff --git a/dspace-api/src/main/java/org/dspace/content/logic/Filter.java b/dspace-api/src/main/java/org/dspace/content/logic/Filter.java index 84e9d6bc08..f789860e77 100644 --- a/dspace-api/src/main/java/org/dspace/content/logic/Filter.java +++ b/dspace-api/src/main/java/org/dspace/content/logic/Filter.java @@ -9,6 +9,7 @@ package org.dspace.content.logic; import org.dspace.content.Item; import org.dspace.core.Context; +import org.springframework.beans.factory.BeanNameAware; /** * The interface for Filter currently doesn't add anything to LogicalStatement but inherits from it @@ -22,7 +23,7 @@ import org.dspace.core.Context; * @author Kim Shepherd * @see org.dspace.content.logic.DefaultFilter */ -public interface Filter extends LogicalStatement { +public interface Filter extends LogicalStatement, BeanNameAware { /** * Get the result of logical evaluation for an item * @param context DSpace context @@ -32,4 +33,11 @@ public interface Filter extends LogicalStatement { */ @Override boolean getResult(Context context, Item item) throws LogicalStatementException; + + /** + * Get the name of a filter. This can be used by filters which make use of BeanNameAware + * to return the bean name. + * @return the id/name of this spring bean + */ + String getName(); } 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 new file mode 100644 index 0000000000..a878d69e6e --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/logic/FilterUtils.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.logic; + +import java.util.HashMap; +import java.util.Map; + +import org.dspace.identifier.DOI; +import org.dspace.identifier.Handle; +import org.dspace.identifier.Identifier; +import org.dspace.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * General utility methods for logical item filtering + * + * @author Kim Shepherd + */ +public class FilterUtils { + + @Autowired(required = true) + ConfigurationService configurationService; + + /** + * Get a Filter by configuration property name + * For example, if a module has implemented "my-feature.filter" configuration property + * this method will return a filter with the ID specified by the configuration property + * @param property DSpace configuration property name (Apache Commons config) + * @return Filter object, with a bean ID configured for this property key, or null + */ + public static Filter getFilterFromConfiguration(String property) { + String filterName = DSpaceServicesFactory.getInstance().getConfigurationService().getProperty(property); + if (filterName != null) { + return DSpaceServicesFactory.getInstance().getServiceManager().getServiceByName(filterName, Filter.class); + } + return null; + } + + /** + * Get a Filter by configuration property name + * For example, if a module has implemented "my-feature.filter" configuration property + * this method will return a filter with the ID specified by the configuration property + * @param property DSpace configuration property name (Apache Commons config) + * @return Filter object, with a bean ID configured for this property key, or default filter + */ + public static Filter getFilterFromConfiguration(String property, Filter defaultFilter) { + Filter filter = getFilterFromConfiguration(property); + if (filter != null) { + return filter; + } + return defaultFilter; + } + + /** + * Get a map of identifier types and filters to use when creating workspace or archived items + * This is used by services installing new archived or workspace items to filter by identifier type + * as some filters should apply to DOI creation but not Handle creation, and so on. + * The in progress or archived status will be used to load the appropriate filter from configuration + *

+ * @param inProgress + * @return + */ + public static Map, Filter> getIdentifierFilters(boolean inProgress) { + String configurationSuffix = "install"; + if (inProgress) { + configurationSuffix = "workspace"; + } + 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") + 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 + filters.put(Handle.class, DSpaceServicesFactory.getInstance().getServiceManager().getServiceByName( + "always_true_filter", TrueFilter.class)); + return filters; + } +} diff --git a/dspace-api/src/main/java/org/dspace/content/logic/LogicalStatement.java b/dspace-api/src/main/java/org/dspace/content/logic/LogicalStatement.java index 5fc3e76cd5..0119f48b51 100644 --- a/dspace-api/src/main/java/org/dspace/content/logic/LogicalStatement.java +++ b/dspace-api/src/main/java/org/dspace/content/logic/LogicalStatement.java @@ -17,7 +17,6 @@ import org.dspace.core.Context; * used as sub-statements in other Filters and Operators. * * @author Kim Shepherd - * @version $Revision$ */ public interface LogicalStatement { /** diff --git a/dspace-api/src/main/java/org/dspace/content/logic/LogicalStatementException.java b/dspace-api/src/main/java/org/dspace/content/logic/LogicalStatementException.java index 758a0a7124..4e3b3e3b7d 100644 --- a/dspace-api/src/main/java/org/dspace/content/logic/LogicalStatementException.java +++ b/dspace-api/src/main/java/org/dspace/content/logic/LogicalStatementException.java @@ -12,7 +12,6 @@ package org.dspace.content.logic; * defined as spring beans. * * @author Kim Shepherd - * @version $Revision$ */ public class LogicalStatementException extends RuntimeException { diff --git a/dspace-api/src/main/java/org/dspace/content/logic/TestLogicRunner.java b/dspace-api/src/main/java/org/dspace/content/logic/TestLogicRunner.java index b78de7f190..bf218eaa8a 100644 --- a/dspace-api/src/main/java/org/dspace/content/logic/TestLogicRunner.java +++ b/dspace-api/src/main/java/org/dspace/content/logic/TestLogicRunner.java @@ -33,7 +33,6 @@ import org.dspace.services.factory.DSpaceServicesFactory; * A command-line runner used for testing a logical filter against an item, or all items * * @author Kim Shepherd - * @version $Revision$ */ public class TestLogicRunner { diff --git a/dspace-api/src/main/java/org/dspace/content/logic/TrueFilter.java b/dspace-api/src/main/java/org/dspace/content/logic/TrueFilter.java new file mode 100644 index 0000000000..b15ab4eaaa --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/logic/TrueFilter.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.content.logic; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.dspace.content.Item; +import org.dspace.core.Context; + +/** + * Extremely simple filter that always returns true! + * Useful to pass to methods that expect a filter, in order to effectively say "all items". + * This could be configured in Spring XML but it is more stable and reliable to have it hard-coded here + * so that any broken configuration doesn't silently break parts of DSpace that expect it to work. + * + * @author Kim Shepherd + */ +public class TrueFilter implements Filter { + private String name; + private final static Logger log = LogManager.getLogger(); + + public boolean getResult(Context context, Item item) throws LogicalStatementException { + return true; + } + + @Override + public void setBeanName(String name) { + log.debug("Initialize bean " + name); + this.name = name; + } + + @Override + public String getName() { + return name; + } +} diff --git a/dspace-api/src/main/java/org/dspace/content/logic/condition/AbstractCondition.java b/dspace-api/src/main/java/org/dspace/content/logic/condition/AbstractCondition.java index 0202243265..ce5b274a8d 100644 --- a/dspace-api/src/main/java/org/dspace/content/logic/condition/AbstractCondition.java +++ b/dspace-api/src/main/java/org/dspace/content/logic/condition/AbstractCondition.java @@ -23,7 +23,6 @@ import org.springframework.beans.factory.annotation.Autowired; * Abstract class for conditions, to implement the basic getter and setter parameters * * @author Kim Shepherd - * @version $Revision$ */ public abstract class AbstractCondition implements Condition { diff --git a/dspace-api/src/main/java/org/dspace/content/logic/condition/BitstreamCountCondition.java b/dspace-api/src/main/java/org/dspace/content/logic/condition/BitstreamCountCondition.java index 635f0997d3..36e506122e 100644 --- a/dspace-api/src/main/java/org/dspace/content/logic/condition/BitstreamCountCondition.java +++ b/dspace-api/src/main/java/org/dspace/content/logic/condition/BitstreamCountCondition.java @@ -18,7 +18,6 @@ import org.dspace.core.Context; * A condition to evaluate an item based on how many bitstreams it has in a particular bundle * * @author Kim Shepherd - * @version $Revision$ */ public class BitstreamCountCondition extends AbstractCondition { /** diff --git a/dspace-api/src/main/java/org/dspace/content/logic/condition/Condition.java b/dspace-api/src/main/java/org/dspace/content/logic/condition/Condition.java index c86509899f..7647dce4a4 100644 --- a/dspace-api/src/main/java/org/dspace/content/logic/condition/Condition.java +++ b/dspace-api/src/main/java/org/dspace/content/logic/condition/Condition.java @@ -22,7 +22,6 @@ import org.dspace.core.Context; * operator is not a condition but also a logical statement. * * @author Kim Shepherd - * @version $Revision$ */ public interface Condition extends LogicalStatement { diff --git a/dspace-api/src/main/java/org/dspace/content/logic/condition/InCollectionCondition.java b/dspace-api/src/main/java/org/dspace/content/logic/condition/InCollectionCondition.java index 0aaa1bff1d..df94f183d1 100644 --- a/dspace-api/src/main/java/org/dspace/content/logic/condition/InCollectionCondition.java +++ b/dspace-api/src/main/java/org/dspace/content/logic/condition/InCollectionCondition.java @@ -23,7 +23,6 @@ import org.dspace.core.Context; * if the item belongs to any of them. * * @author Kim Shepherd - * @version $Revision$ */ public class InCollectionCondition extends AbstractCondition { private static Logger log = LogManager.getLogger(InCollectionCondition.class); diff --git a/dspace-api/src/main/java/org/dspace/content/logic/condition/InCommunityCondition.java b/dspace-api/src/main/java/org/dspace/content/logic/condition/InCommunityCondition.java index 9f588f9c3b..6a72011e73 100644 --- a/dspace-api/src/main/java/org/dspace/content/logic/condition/InCommunityCondition.java +++ b/dspace-api/src/main/java/org/dspace/content/logic/condition/InCommunityCondition.java @@ -24,7 +24,6 @@ import org.dspace.core.Context; * if the item belongs to any of them. * * @author Kim Shepherd - * @version $Revision$ */ public class InCommunityCondition extends AbstractCondition { private final static Logger log = LogManager.getLogger(); diff --git a/dspace-api/src/main/java/org/dspace/content/logic/condition/IsArchivedCondition.java b/dspace-api/src/main/java/org/dspace/content/logic/condition/IsArchivedCondition.java new file mode 100644 index 0000000000..4f50d2b6f6 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/logic/condition/IsArchivedCondition.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.content.logic.condition; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.dspace.content.Item; +import org.dspace.content.logic.LogicalStatementException; +import org.dspace.core.Context; + +/** + * A condition that returns true if the item is archived + * + * @author Kim Shepherd + */ +public class IsArchivedCondition extends AbstractCondition { + private final static Logger log = LogManager.getLogger(); + + /** + * Return true if item is archived + * Return false if not + * @param context DSpace context + * @param item Item to evaluate + * @return boolean result of evaluation + * @throws LogicalStatementException + */ + @Override + public boolean getResult(Context context, Item item) throws LogicalStatementException { + log.debug("Result of isArchived is " + item.isArchived()); + return item.isArchived(); + } +} diff --git a/dspace-api/src/main/java/org/dspace/content/logic/condition/IsWithdrawnCondition.java b/dspace-api/src/main/java/org/dspace/content/logic/condition/IsWithdrawnCondition.java index 6424e6f35f..850b69bda0 100644 --- a/dspace-api/src/main/java/org/dspace/content/logic/condition/IsWithdrawnCondition.java +++ b/dspace-api/src/main/java/org/dspace/content/logic/condition/IsWithdrawnCondition.java @@ -17,7 +17,6 @@ import org.dspace.core.Context; * A condition that returns true if the item is withdrawn * * @author Kim Shepherd - * @version $Revision$ */ public class IsWithdrawnCondition extends AbstractCondition { private final static Logger log = LogManager.getLogger(); diff --git a/dspace-api/src/main/java/org/dspace/content/logic/condition/MetadataValueMatchCondition.java b/dspace-api/src/main/java/org/dspace/content/logic/condition/MetadataValueMatchCondition.java index 4e30c75a2a..e87c479de6 100644 --- a/dspace-api/src/main/java/org/dspace/content/logic/condition/MetadataValueMatchCondition.java +++ b/dspace-api/src/main/java/org/dspace/content/logic/condition/MetadataValueMatchCondition.java @@ -23,7 +23,6 @@ import org.dspace.core.Context; * in a given metadata field * * @author Kim Shepherd - * @version $Revision$ */ public class MetadataValueMatchCondition extends AbstractCondition { diff --git a/dspace-api/src/main/java/org/dspace/content/logic/condition/MetadataValuesMatchCondition.java b/dspace-api/src/main/java/org/dspace/content/logic/condition/MetadataValuesMatchCondition.java index 74ccfa4ca8..c6ca9dfb9f 100644 --- a/dspace-api/src/main/java/org/dspace/content/logic/condition/MetadataValuesMatchCondition.java +++ b/dspace-api/src/main/java/org/dspace/content/logic/condition/MetadataValuesMatchCondition.java @@ -23,7 +23,6 @@ import org.dspace.core.Context; * in a given metadata field * * @author Kim Shepherd - * @version $Revision$ */ public class MetadataValuesMatchCondition extends AbstractCondition { diff --git a/dspace-api/src/main/java/org/dspace/content/logic/condition/ReadableByGroupCondition.java b/dspace-api/src/main/java/org/dspace/content/logic/condition/ReadableByGroupCondition.java index 65f9925222..20138beb47 100644 --- a/dspace-api/src/main/java/org/dspace/content/logic/condition/ReadableByGroupCondition.java +++ b/dspace-api/src/main/java/org/dspace/content/logic/condition/ReadableByGroupCondition.java @@ -25,7 +25,6 @@ import org.dspace.core.Context; * can perform the action on a given item * * @author Kim Shepherd - * @version $Revision$ */ public class ReadableByGroupCondition extends AbstractCondition { private final static Logger log = LogManager.getLogger(); diff --git a/dspace-api/src/main/java/org/dspace/content/logic/operator/AbstractOperator.java b/dspace-api/src/main/java/org/dspace/content/logic/operator/AbstractOperator.java index 99ece622f7..3882414def 100644 --- a/dspace-api/src/main/java/org/dspace/content/logic/operator/AbstractOperator.java +++ b/dspace-api/src/main/java/org/dspace/content/logic/operator/AbstractOperator.java @@ -22,7 +22,6 @@ import org.dspace.core.Context; * as a logical result * * @author Kim Shepherd - * @version $Revision$ */ public abstract class AbstractOperator implements LogicalStatement { diff --git a/dspace-api/src/main/java/org/dspace/content/logic/operator/And.java b/dspace-api/src/main/java/org/dspace/content/logic/operator/And.java index 26606f2099..79bc5c381e 100644 --- a/dspace-api/src/main/java/org/dspace/content/logic/operator/And.java +++ b/dspace-api/src/main/java/org/dspace/content/logic/operator/And.java @@ -19,7 +19,6 @@ import org.dspace.core.Context; * true if all sub-statements return true * * @author Kim Shepherd - * @version $Revision$ */ public class And extends AbstractOperator { diff --git a/dspace-api/src/main/java/org/dspace/content/logic/operator/Nand.java b/dspace-api/src/main/java/org/dspace/content/logic/operator/Nand.java index 1021ec6722..2a4b6823b6 100644 --- a/dspace-api/src/main/java/org/dspace/content/logic/operator/Nand.java +++ b/dspace-api/src/main/java/org/dspace/content/logic/operator/Nand.java @@ -18,7 +18,6 @@ import org.dspace.core.Context; * An operator that implements NAND by negating an AND operation * * @author Kim Shepherd - * @version $Revision$ */ public class Nand extends AbstractOperator { diff --git a/dspace-api/src/main/java/org/dspace/content/logic/operator/Not.java b/dspace-api/src/main/java/org/dspace/content/logic/operator/Not.java index 35c7bb22a7..277acdfd01 100644 --- a/dspace-api/src/main/java/org/dspace/content/logic/operator/Not.java +++ b/dspace-api/src/main/java/org/dspace/content/logic/operator/Not.java @@ -19,7 +19,6 @@ import org.dspace.core.Context; * Not can have one sub-statement only, while and, or, nor, ... can have multiple sub-statements. * * @author Kim Shepherd - * @version $Revision$ */ public class Not implements LogicalStatement { diff --git a/dspace-api/src/main/java/org/dspace/content/logic/operator/Or.java b/dspace-api/src/main/java/org/dspace/content/logic/operator/Or.java index 5110ac31ba..e5697f8cc3 100644 --- a/dspace-api/src/main/java/org/dspace/content/logic/operator/Or.java +++ b/dspace-api/src/main/java/org/dspace/content/logic/operator/Or.java @@ -19,7 +19,6 @@ import org.dspace.core.Context; * true if one or more sub-statements return true * * @author Kim Shepherd - * @version $Revision$ */ public class Or extends AbstractOperator { 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 4621c95e7c..8effabf284 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 @@ -183,7 +183,7 @@ public interface BitstreamService extends DSpaceObjectService, DSpace * @return a list of all bitstreams that have been "deleted" * @throws SQLException if database error */ - public List findDeletedBitstreams(Context context) throws SQLException; + public List findDeletedBitstreams(Context context, int limit, int offset) throws SQLException; /** 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 522bdac224..a5b2b7d8d8 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 @@ -33,6 +33,11 @@ import org.dspace.eperson.Group; public interface CollectionService extends DSpaceObjectService, DSpaceObjectLegacySupportService { + /* + * Field used to sort community and collection lists at solr + */ + public static final String SOLR_SORT_FIELD = "dc.title_sort"; + /** * Create a new collection with a new ID. * Once created the collection is added to the given community @@ -46,7 +51,6 @@ public interface CollectionService public Collection create(Context context, Community community) throws SQLException, AuthorizeException; - /** * Create a new collection with the supplied handle and with a new ID. * Once created the collection is added to the given community 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 8b7badf223..b7a479469b 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 @@ -28,6 +28,7 @@ import org.dspace.content.MetadataValue; import org.dspace.content.Thumbnail; import org.dspace.content.WorkspaceItem; import org.dspace.core.Context; +import org.dspace.discovery.SearchServiceException; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; @@ -768,6 +769,27 @@ public interface ItemService */ int countWithdrawnItems(Context context) throws SQLException; + /** + * finds all items for which the current user has editing rights + * @param context DSpace context object + * @param offset page offset + * @param limit page size limit + * @return list of items for which the current user has editing rights + * @throws SQLException + * @throws SearchServiceException + */ + public List findItemsWithEdit(Context context, int offset, int limit) + throws SQLException, SearchServiceException; + + /** + * counts all items for which the current user has editing rights + * @param context DSpace context object + * @return list of items for which the current user has editing rights + * @throws SQLException + * @throws SearchServiceException + */ + public int countItemsWithEdit(Context context) throws SQLException, SearchServiceException; + /** * Check if the supplied item is an inprogress submission * diff --git a/dspace-api/src/main/java/org/dspace/content/service/SupervisedItemService.java b/dspace-api/src/main/java/org/dspace/content/service/SupervisedItemService.java deleted file mode 100644 index 883e0f9fd2..0000000000 --- a/dspace-api/src/main/java/org/dspace/content/service/SupervisedItemService.java +++ /dev/null @@ -1,44 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.content.service; - -import java.sql.SQLException; -import java.util.List; - -import org.dspace.content.WorkspaceItem; -import org.dspace.core.Context; -import org.dspace.eperson.EPerson; - -/** - * Class to handle WorkspaceItems which are being supervised. - * - * @author Richard Jones - * @version $Revision$ - */ -public interface SupervisedItemService { - /** - * Get all workspace items which are being supervised - * - * @param context the context this object exists in - * @return array of SupervisedItems - * @throws SQLException if database error - */ - public List getAll(Context context) throws SQLException; - - - /** - * Get items being supervised by given EPerson - * - * @param ep the eperson who's items to supervise we want - * @param context the dspace context - * @return the items eperson is supervising in an array - * @throws SQLException if database error - */ - public List findbyEPerson(Context context, EPerson ep) - throws SQLException; -} 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 8f572f6108..c8df68e434 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 @@ -127,10 +127,6 @@ public interface WorkspaceItemService extends InProgressSubmissionService findAllSupervisedItems(Context context) throws SQLException; - - public List findSupervisedItemsByEPerson(Context context, EPerson ePerson) throws SQLException; - /** * Get all workspace items in the whole system * 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 6db27c9e4f..64da629bcc 100644 --- a/dspace-api/src/main/java/org/dspace/core/Email.java +++ b/dspace-api/src/main/java/org/dspace/core/Email.java @@ -355,7 +355,7 @@ public class Email { for (String headerName : config.getArrayProperty("mail.message.headers")) { String headerValue = (String) vctx.get(headerName); if ("subject".equalsIgnoreCase(headerName)) { - if (null != subject) { + if (null != headerValue) { subject = headerValue; } } else if ("charset".equalsIgnoreCase(headerName)) { diff --git a/dspace-api/src/main/java/org/dspace/ctask/general/RegisterDOI.java b/dspace-api/src/main/java/org/dspace/ctask/general/RegisterDOI.java index 4e777d70a8..0765d7b000 100644 --- a/dspace-api/src/main/java/org/dspace/ctask/general/RegisterDOI.java +++ b/dspace-api/src/main/java/org/dspace/ctask/general/RegisterDOI.java @@ -13,11 +13,15 @@ import java.sql.SQLException; import org.apache.logging.log4j.Logger; import org.dspace.content.DSpaceObject; import org.dspace.content.Item; +import org.dspace.content.logic.Filter; +import org.dspace.content.logic.FilterUtils; +import org.dspace.content.logic.TrueFilter; import org.dspace.curate.AbstractCurationTask; import org.dspace.curate.Curator; import org.dspace.identifier.DOIIdentifierProvider; import org.dspace.identifier.IdentifierException; import org.dspace.identifier.doi.DOIIdentifierNotApplicableException; +import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.utils.DSpace; /** @@ -39,6 +43,7 @@ public class RegisterDOI extends AbstractCurationTask { private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(RegisterDOI.class); // DOI provider private DOIIdentifierProvider provider; + private Filter trueFilter; /** * Initialise the curation task and read configuration, instantiate the DOI provider @@ -46,14 +51,14 @@ public class RegisterDOI extends AbstractCurationTask { @Override public void init(Curator curator, String taskId) throws IOException { super.init(curator, taskId); - // Get 'skip filter' behaviour from configuration, with a default value of 'true' - skipFilter = configurationService.getBooleanProperty(PLUGIN_PREFIX + ".skip-filter", true); // Get distribution behaviour from configuration, with a default value of 'false' distributed = configurationService.getBooleanProperty(PLUGIN_PREFIX + ".distributed", false); log.debug("PLUGIN_PREFIX = " + PLUGIN_PREFIX + ", skipFilter = " + skipFilter + ", distributed = " + distributed); // Instantiate DOI provider singleton provider = new DSpace().getSingletonService(DOIIdentifierProvider.class); + trueFilter = DSpaceServicesFactory.getInstance().getServiceManager().getServiceByName( + "always_true_filter", TrueFilter.class); } /** @@ -118,8 +123,9 @@ public class RegisterDOI extends AbstractCurationTask { String doi = null; // Attempt DOI registration and report successes and failures try { - log.debug("Registering DOI with skipFilter = " + skipFilter); - doi = provider.register(Curator.curationContext(), item, skipFilter); + Filter filter = FilterUtils.getFilterFromConfiguration("identifiers.submission.filter.curation", + trueFilter); + doi = provider.register(Curator.curationContext(), item, filter); if (doi != null) { String message = "New DOI minted in database for item " + item.getHandle() + ": " + doi + ". This DOI will be registered online with the DOI provider when the queue is next run"; diff --git a/dspace-api/src/main/java/org/dspace/discovery/DiscoverResult.java b/dspace-api/src/main/java/org/dspace/discovery/DiscoverResult.java index b2bd0fc5ff..00236d2bfe 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/DiscoverResult.java +++ b/dspace-api/src/main/java/org/dspace/discovery/DiscoverResult.java @@ -7,6 +7,8 @@ */ package org.dspace.discovery; +import static org.dspace.discovery.SolrServiceImpl.SOLR_FIELD_SUFFIX_FACET_PREFIXES; + import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -109,6 +111,9 @@ public class DiscoverResult { if (facetValues.size() == 0 && field.getType().equals(DiscoveryConfigurationParameters.TYPE_DATE)) { facetValues = getFacetResult(field.getIndexFieldName() + ".year"); } + if (facetValues.isEmpty()) { + facetValues = getFacetResult(field.getIndexFieldName() + SOLR_FIELD_SUFFIX_FACET_PREFIXES); + } return ListUtils.emptyIfNull(facetValues); } 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 fcb3e79d1d..661c48d91c 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/IndexClient.java +++ b/dspace-api/src/main/java/org/dspace/discovery/IndexClient.java @@ -56,37 +56,18 @@ public class IndexClient extends DSpaceRunnable indexableObject = Optional.empty(); + + if (indexClientOptions == IndexClientOptions.REMOVE || indexClientOptions == IndexClientOptions.INDEX) { + final String param = indexClientOptions == IndexClientOptions.REMOVE ? commandLine.getOptionValue('r') : + commandLine.getOptionValue('i'); UUID uuid = null; try { uuid = UUID.fromString(param); } catch (Exception e) { - // nothing to do, it should be an handle + // nothing to do, it should be a handle } - Optional indexableObject = Optional.empty(); + if (uuid != null) { final Item item = ContentServiceFactory.getInstance().getItemService().find(context, uuid); if (item != null) { @@ -118,7 +99,32 @@ public class IndexClient extends DSpaceRunnable + * + * @param context DSpace context object + * @param community Community for which we search the ancestors + * @return A list of ancestor communities. + * @throws SQLException if database error + */ + static List getAncestorCommunities(Context context, Community community) throws SQLException { + ArrayList communities = new ArrayList<>(); + while (community != null) { + communities.add(community); + community = (Community) ContentServiceFactory.getInstance().getDSpaceObjectService(community) + .getParentObject(context, community); + } + return communities; + } + + /** + * Retrieve the ids of all groups that have ADMIN rights to the given community, either directly + * (through direct resource policy) or indirectly (through a policy on an ancestor community). + * + * @param context DSpace context object + * @param community Community for which we search the admin group IDs + * @return A list of admin group IDs + * @throws SQLException if database error + */ + static List findTransitiveAdminGroupIds(Context context, Community community) throws SQLException { + return getAncestorCommunities(context, community).stream() + .filter(parent -> parent.getAdministrators() != null) + .map(parent -> parent.getAdministrators().getID()) + .collect(Collectors.toList()); + } + + /** + * Retrieve the ids of all groups that have ADMIN rights to the given collection, either directly + * (through direct resource policy) or indirectly (through a policy on its community, or one of + * its ancestor communities). + * + * @param context DSpace context object + * @param collection Collection for which we search the admin group IDs + * @return A list of admin group IDs + * @throws SQLException if database error + */ + static List findTransitiveAdminGroupIds(Context context, Collection collection) throws SQLException { + List ids = new ArrayList<>(); + if (collection.getAdministrators() != null) { + ids.add(collection.getAdministrators().getID()); + } + for (Community community : collection.getCommunities()) { + for (UUID id : findTransitiveAdminGroupIds(context, community)) { + ids.add(id); + } + } + return ids; + } + + /** + * Retrieve group and eperson IDs for all groups and eperson who have _any_ of the given authorizations + * on the given DSpaceObject. The resulting IDs are prefixed with "e" in the case of an eperson ID, and "g" in the + * case of a group ID. + * + * @param authService The authentication service + * @param context DSpace context object + * @param obj DSpaceObject for which we search the admin group IDs + * @return A stream of admin group IDs as Strings, prefixed with either "e" or "g", depending on whether it is a + * group or eperson ID. + * @throws SQLException if database error + */ + static List findDirectlyAuthorizedGroupAndEPersonPrefixedIds( + AuthorizeService authService, Context context, DSpaceObject obj, int[] authorizations) + throws SQLException { + ArrayList prefixedIds = new ArrayList<>(); + for (int auth : authorizations) { + for (ResourcePolicy policy : authService.getPoliciesActionFilter(context, obj, auth)) { + // Avoid NPE in cases where the policy does not have group or eperson + if (policy.getGroup() == null && policy.getEPerson() == null) { + continue; + } + String prefixedId = policy.getGroup() == null + ? "e" + policy.getEPerson().getID() + : "g" + policy.getGroup().getID(); + prefixedIds.add(prefixedId); + context.uncacheEntity(policy); + } + } + return prefixedIds; + } +} 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 383121072d..0cf2aa50af 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java @@ -105,6 +105,10 @@ public class SolrServiceImpl implements SearchService, IndexingService { private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(SolrServiceImpl.class); + // Suffix of the solr field used to index the facet/filter so that the facet search can search all word in a + // facet by indexing "each word to end of value' partial value + public static final String SOLR_FIELD_SUFFIX_FACET_PREFIXES = "_prefix"; + @Autowired protected ContentServiceFactory contentServiceFactory; @Autowired @@ -252,7 +256,12 @@ public class SolrServiceImpl implements SearchService, IndexingService { try { if (solrSearchCore.getSolr() != null) { - indexObjectServiceFactory.getIndexableObjectFactory(searchUniqueID).delete(searchUniqueID); + IndexFactory index = indexObjectServiceFactory.getIndexableObjectFactory(searchUniqueID); + if (index != null) { + index.delete(searchUniqueID); + } else { + log.warn("Object not found in Solr index: " + searchUniqueID); + } if (commit) { solrSearchCore.getSolr().commit(); } @@ -905,6 +914,9 @@ public class SolrServiceImpl implements SearchService, IndexingService { //Only add facet information if there are any facets for (DiscoverFacetField facetFieldConfig : facetFields) { String field = transformFacetField(facetFieldConfig, facetFieldConfig.getField(), false); + if (facetFieldConfig.getPrefix() != null) { + field = transformPrefixFacetField(facetFieldConfig, facetFieldConfig.getField(), false); + } solrQuery.addFacetField(field); // Setting the facet limit in this fashion ensures that each facet can have its own max @@ -1344,7 +1356,31 @@ public class SolrServiceImpl implements SearchService, IndexingService { } } + /** + * Gets the solr field that contains the facet value split on each word break to the end, so can be searched + * on each word in the value, see {@link org.dspace.discovery.indexobject.ItemIndexFactoryImpl + * #saveFacetPrefixParts(SolrInputDocument, DiscoverySearchFilter, String, String)} + * Ony applicable to facets of type {@link DiscoveryConfigurationParameters.TYPE_TEXT}, otherwise uses the regular + * facet filter field + */ + protected String transformPrefixFacetField(DiscoverFacetField facetFieldConfig, String field, + boolean removePostfix) { + if (facetFieldConfig.getType().equals(DiscoveryConfigurationParameters.TYPE_TEXT) || + facetFieldConfig.getType().equals(DiscoveryConfigurationParameters.TYPE_HIERARCHICAL)) { + if (removePostfix) { + return field.substring(0, field.lastIndexOf(SOLR_FIELD_SUFFIX_FACET_PREFIXES)); + } else { + return field + SOLR_FIELD_SUFFIX_FACET_PREFIXES; + } + } else { + return this.transformFacetField(facetFieldConfig, field, removePostfix); + } + } + protected String transformFacetField(DiscoverFacetField facetFieldConfig, String field, boolean removePostfix) { + if (field.contains(SOLR_FIELD_SUFFIX_FACET_PREFIXES)) { + return this.transformPrefixFacetField(facetFieldConfig, field, removePostfix); + } if (facetFieldConfig.getType().equals(DiscoveryConfigurationParameters.TYPE_TEXT)) { if (removePostfix) { return field.substring(0, field.lastIndexOf("_filter")); @@ -1390,7 +1426,7 @@ public class SolrServiceImpl implements SearchService, IndexingService { if (field.equals("location.comm") || field.equals("location.coll")) { value = locationToName(context, field, value); } else if (field.endsWith("_filter") || field.endsWith("_ac") - || field.endsWith("_acid")) { + || field.endsWith("_acid") || field.endsWith(SOLR_FIELD_SUFFIX_FACET_PREFIXES)) { //We have a filter make sure we split ! String separator = DSpaceServicesFactory.getInstance().getConfigurationService() .getProperty("discovery.solr.facets.split.char"); @@ -1422,7 +1458,7 @@ public class SolrServiceImpl implements SearchService, IndexingService { return value; } if (field.endsWith("_filter") || field.endsWith("_ac") - || field.endsWith("_acid")) { + || field.endsWith("_acid") || field.endsWith(SOLR_FIELD_SUFFIX_FACET_PREFIXES)) { //We have a filter make sure we split ! String separator = DSpaceServicesFactory.getInstance().getConfigurationService() .getProperty("discovery.solr.facets.split.char"); diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceIndexCollectionSubmittersPlugin.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceIndexCollectionSubmittersPlugin.java index 00b70f93d5..ee93f954a5 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceIndexCollectionSubmittersPlugin.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceIndexCollectionSubmittersPlugin.java @@ -7,16 +7,17 @@ */ package org.dspace.discovery; +import static org.dspace.discovery.IndexingUtils.findDirectlyAuthorizedGroupAndEPersonPrefixedIds; +import static org.dspace.discovery.IndexingUtils.findTransitiveAdminGroupIds; + import java.sql.SQLException; import java.util.List; +import java.util.UUID; import org.apache.logging.log4j.Logger; import org.apache.solr.common.SolrInputDocument; -import org.dspace.authorize.ResourcePolicy; import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.Collection; -import org.dspace.content.Community; -import org.dspace.content.factory.ContentServiceFactory; import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.core.LogHelper; @@ -42,29 +43,21 @@ public class SolrServiceIndexCollectionSubmittersPlugin implements SolrServiceIn Collection col = ((IndexableCollection) idxObj).getIndexedObject(); if (col != null) { try { - String fieldValue = null; - Community parent = (Community) ContentServiceFactory.getInstance().getDSpaceObjectService(col) - .getParentObject(context, col); - while (parent != null) { - if (parent.getAdministrators() != null) { - fieldValue = "g" + parent.getAdministrators().getID(); - document.addField("submit", fieldValue); - } - parent = (Community) ContentServiceFactory.getInstance().getDSpaceObjectService(parent) - .getParentObject(context, parent); + // Index groups with ADMIN rights on the Collection, on + // Communities containing those Collections, and recursively on any Community containing such a + // Community. + // TODO: Strictly speaking we should also check for epersons who received admin rights directly, + // without being part of the admin group. Finding them may be a lot slower though. + for (UUID unprefixedId : findTransitiveAdminGroupIds(context, col)) { + document.addField("submit", "g" + unprefixedId); } - List policies = authorizeService.getPoliciesActionFilter(context,col,Constants.ADD); - policies.addAll(authorizeService.getPoliciesActionFilter(context, col, Constants.ADMIN)); - for (ResourcePolicy resourcePolicy : policies) { - if (resourcePolicy.getGroup() != null) { - fieldValue = "g" + resourcePolicy.getGroup().getID(); - } else { - fieldValue = "e" + resourcePolicy.getEPerson().getID(); - - } - document.addField("submit", fieldValue); - context.uncacheEntity(resourcePolicy); + // Index groups and epersons with ADD or ADMIN rights on the Collection. + List prefixedIds = findDirectlyAuthorizedGroupAndEPersonPrefixedIds( + authorizeService, context, col, new int[] {Constants.ADD, Constants.ADMIN} + ); + for (String prefixedId : prefixedIds) { + document.addField("submit", prefixedId); } } catch (SQLException e) { log.error(LogHelper.getHeader(context, "Error while indexing resource policies", @@ -73,5 +66,4 @@ public class SolrServiceIndexCollectionSubmittersPlugin implements SolrServiceIn } } } - -} \ No newline at end of file +} diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceIndexItemEditorsPlugin.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceIndexItemEditorsPlugin.java new file mode 100644 index 0000000000..09308be759 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceIndexItemEditorsPlugin.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.discovery; + +import static org.dspace.discovery.IndexingUtils.findDirectlyAuthorizedGroupAndEPersonPrefixedIds; +import static org.dspace.discovery.IndexingUtils.findTransitiveAdminGroupIds; + +import java.sql.SQLException; +import java.util.List; +import java.util.UUID; + +import org.apache.logging.log4j.Logger; +import org.apache.solr.common.SolrInputDocument; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.content.Collection; +import org.dspace.content.Item; +import org.dspace.core.Constants; +import org.dspace.core.Context; +import org.dspace.core.LogHelper; +import org.dspace.discovery.indexobject.IndexableItem; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Indexes policies that yield write access to items. + * + * @author Koen Pauwels at atmire.com + */ +public class SolrServiceIndexItemEditorsPlugin implements SolrServiceIndexPlugin { + private static final Logger log = org.apache.logging.log4j.LogManager + .getLogger(SolrServiceIndexItemEditorsPlugin.class); + + @Autowired(required = true) + protected AuthorizeService authorizeService; + + @Override + public void additionalIndex(Context context, IndexableObject idxObj, SolrInputDocument document) { + if (idxObj instanceof IndexableItem) { + Item item = ((IndexableItem) idxObj).getIndexedObject(); + if (item != null) { + try { + // Index groups with ADMIN rights on Collections containing the Item, on + // Communities containing those Collections, and recursively on any Community containing ssuch a + // Community. + // TODO: Strictly speaking we should also check for epersons who received admin rights directly, + // without being part of the admin group. Finding them may be a lot slower though. + for (Collection collection : item.getCollections()) { + for (UUID unprefixedId : findTransitiveAdminGroupIds(context, collection)) { + document.addField("edit", "g" + unprefixedId); + } + } + + // Index groups and epersons with WRITE or direct ADMIN rights on the Item. + List prefixedIds = findDirectlyAuthorizedGroupAndEPersonPrefixedIds( + authorizeService, context, item, new int[] {Constants.WRITE, Constants.ADMIN} + ); + for (String prefixedId : prefixedIds) { + document.addField("edit", prefixedId); + } + } catch (SQLException e) { + log.error(LogHelper.getHeader(context, "Error while indexing resource policies", + "Item: (id " + item.getID() + " name " + item.getName() + ")" )); + } + } + } + } +} 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 d03ea359f5..746a0cb832 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceMetadataBrowseIndexingPlugin.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceMetadataBrowseIndexingPlugin.java @@ -7,6 +7,8 @@ */ package org.dspace.discovery; +import static org.dspace.discovery.SolrServiceImpl.SOLR_FIELD_SUFFIX_FACET_PREFIXES; + import java.util.HashSet; import java.util.List; import java.util.Set; @@ -261,9 +263,9 @@ public class SolrServiceMetadataBrowseIndexingPlugin implements SolrServiceIndex } } } - for (String facet : distFValues) { document.addField(bi.getDistinctTableName() + "_filter", facet); + document.addField(bi.getDistinctTableName() + SOLR_FIELD_SUFFIX_FACET_PREFIXES, facet); } for (String facet : distFAuths) { document.addField(bi.getDistinctTableName() diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceSupervisionOrderIndexingPlugin.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceSupervisionOrderIndexingPlugin.java new file mode 100644 index 0000000000..116b5ec88d --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceSupervisionOrderIndexingPlugin.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.discovery; + +import java.sql.SQLException; +import java.util.List; +import java.util.Objects; + +import org.apache.commons.collections4.CollectionUtils; +import org.apache.solr.common.SolrInputDocument; +import org.dspace.content.Item; +import org.dspace.core.Context; +import org.dspace.discovery.indexobject.IndexableInProgressSubmission; +import org.dspace.discovery.indexobject.IndexableWorkflowItem; +import org.dspace.discovery.indexobject.IndexableWorkspaceItem; +import org.dspace.supervision.SupervisionOrder; +import org.dspace.supervision.service.SupervisionOrderService; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * A Solr Indexing plugin responsible adding a `supervised` field. + * When item being indexed is a workspace or workflow item, + * and at least one supervision order is defined + * the 'supervised' field with value 'true' will be added to the solr document, + * if no supervision orders are defined field will be set to 'false' + * + * @author Mohamed Eskander (mohamed.eskander at 4science dot it) + */ +public class SolrServiceSupervisionOrderIndexingPlugin implements SolrServiceIndexPlugin { + + @Autowired(required = true) + private SupervisionOrderService supervisionOrderService; + + @Override + public void additionalIndex(Context context, IndexableObject indexableObject, SolrInputDocument document) { + try { + + if (!(indexableObject instanceof IndexableWorkspaceItem) && + !(indexableObject instanceof IndexableWorkflowItem)) { + return; + } + + Item item = + (((IndexableInProgressSubmission) indexableObject).getIndexedObject()).getItem(); + + if (Objects.isNull(item)) { + return; + } + addSupervisedField(context, item, document); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + + private void addSupervisedField(Context context, Item item, SolrInputDocument document) throws SQLException { + List supervisionOrders = supervisionOrderService.findByItem(context, item); + if (CollectionUtils.isNotEmpty(supervisionOrders)) { + document.addField("supervised", true); + } else { + document.addField("supervised", false); + } + } +} diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceWorkspaceWorkflowRestrictionPlugin.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceWorkspaceWorkflowRestrictionPlugin.java index fd05be1cb5..1618494756 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceWorkspaceWorkflowRestrictionPlugin.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceWorkspaceWorkflowRestrictionPlugin.java @@ -40,6 +40,11 @@ public class SolrServiceWorkspaceWorkflowRestrictionPlugin implements SolrServic */ public static final String DISCOVER_WORKFLOW_ADMIN_CONFIGURATION_NAME = "workflowAdmin"; + /** + * The name of the discover configuration used by administrators to search for workspace and workflow tasks + */ + public static final String DISCOVER_SUPERVISION_CONFIGURATION_NAME = "supervision"; + @Autowired(required = true) protected GroupService groupService; @@ -60,18 +65,22 @@ public class SolrServiceWorkspaceWorkflowRestrictionPlugin implements SolrServic ); boolean isWorkflowAdmin = isAdmin(context) && DISCOVER_WORKFLOW_ADMIN_CONFIGURATION_NAME.equals(discoveryQuery.getDiscoveryConfigurationName()); + + boolean isSupervision = + DISCOVER_SUPERVISION_CONFIGURATION_NAME.equals(discoveryQuery.getDiscoveryConfigurationName()); + EPerson currentUser = context.getCurrentUser(); // extra security check to avoid the possibility that an anonymous user // get access to workspace or workflow - if (currentUser == null && (isWorkflow || isWorkspace)) { + if (currentUser == null && (isWorkflow || isWorkspace || isSupervision)) { throw new IllegalStateException( "An anonymous user cannot perform a workspace or workflow search"); } if (isWorkspace) { // insert filter by submitter solrQuery.addFilterQuery("submitter_authority:(" + currentUser.getID() + ")"); - } else if (isWorkflow && !isWorkflowAdmin) { + } else if ((isWorkflow && !isWorkflowAdmin) || (isSupervision && !isAdmin(context))) { // Retrieve all the groups the current user is a member of ! Set groups; try { diff --git a/dspace-api/src/main/java/org/dspace/discovery/configuration/DiscoveryConfigurationService.java b/dspace-api/src/main/java/org/dspace/discovery/configuration/DiscoveryConfigurationService.java index 636e7ccd2a..c02c83ece6 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/configuration/DiscoveryConfigurationService.java +++ b/dspace-api/src/main/java/org/dspace/discovery/configuration/DiscoveryConfigurationService.java @@ -139,4 +139,23 @@ public class DiscoveryConfigurationService { } } } + + /** + * Retrieves a list of all DiscoveryConfiguration objects where key starts with prefixConfigurationName + * + * @param prefixConfigurationName string as prefix key + */ + public List getDiscoveryConfigurationWithPrefixName(final String prefixConfigurationName) { + List discoveryConfigurationList = new ArrayList<>(); + if (StringUtils.isNotBlank(prefixConfigurationName)) { + for (String key : map.keySet()) { + if (key.equals(prefixConfigurationName) || key.startsWith(prefixConfigurationName)) { + DiscoveryConfiguration config = map.get(key); + discoveryConfigurationList.add(config); + } + } + } + return discoveryConfigurationList; + } + } diff --git a/dspace-api/src/main/java/org/dspace/discovery/configuration/DiscoveryRelatedItemConfiguration.java b/dspace-api/src/main/java/org/dspace/discovery/configuration/DiscoveryRelatedItemConfiguration.java new file mode 100644 index 0000000000..6c24a6bac6 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/discovery/configuration/DiscoveryRelatedItemConfiguration.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.discovery.configuration; + +/** + * This class extends {@link DiscoveryConfiguration} and add method for set parameters + * to filter query list + * + * @author Danilo Di Nuzzo (danilo.dinuzzo at 4science.it) + */ +public class DiscoveryRelatedItemConfiguration extends DiscoveryConfiguration {} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/discovery/configuration/DiscoverySortFunctionConfiguration.java b/dspace-api/src/main/java/org/dspace/discovery/configuration/DiscoverySortFunctionConfiguration.java new file mode 100644 index 0000000000..7fb020cd56 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/discovery/configuration/DiscoverySortFunctionConfiguration.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.discovery.configuration; + +import java.io.Serializable; +import java.text.MessageFormat; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +/** + * + * Extension of {@link DiscoverySortFieldConfiguration} used to configure sorting + * taking advantage of solr function feature. + * + * Order is evaluated by mean of function parameter value and passed in arguments as input. + * + * @author Corrado Lombardi (corrado.lombardi at 4science.it) + * + */ +public class DiscoverySortFunctionConfiguration extends DiscoverySortFieldConfiguration { + + public static final String SORT_FUNCTION = "sort_function"; + private String function; + private List arguments; + private String id; + + public void setFunction(final String function) { + this.function = function; + } + + public void setArguments(final List arguments) { + this.arguments = arguments; + } + + @Override + public String getType() { + return SORT_FUNCTION; + } + + @Override + public String getMetadataField() { + return id; + } + + public void setId(final String id) { + this.id = id; + } + + /** + * Returns the function to be used by solr to sort result + * @param functionArgs variable arguments to be inserted in function + * @return + */ + public String getFunction(final Serializable... functionArgs) { + final String args = String.join(",", Optional.ofNullable(arguments).orElse(Collections.emptyList())); + final String result = function + "(" + args + ")"; + return MessageFormat.format(result, functionArgs); + } + +} diff --git a/dspace-api/src/main/java/org/dspace/discovery/indexobject/InprogressSubmissionIndexFactoryImpl.java b/dspace-api/src/main/java/org/dspace/discovery/indexobject/InprogressSubmissionIndexFactoryImpl.java index d0b0f363e6..8a24b997ff 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/indexobject/InprogressSubmissionIndexFactoryImpl.java +++ b/dspace-api/src/main/java/org/dspace/discovery/indexobject/InprogressSubmissionIndexFactoryImpl.java @@ -22,6 +22,8 @@ import org.dspace.discovery.indexobject.factory.CollectionIndexFactory; import org.dspace.discovery.indexobject.factory.InprogressSubmissionIndexFactory; import org.dspace.discovery.indexobject.factory.ItemIndexFactory; import org.dspace.eperson.EPerson; +import org.dspace.supervision.SupervisionOrder; +import org.dspace.supervision.service.SupervisionOrderService; import org.dspace.util.SolrUtils; import org.dspace.workflow.WorkflowItem; import org.springframework.beans.factory.annotation.Autowired; @@ -39,6 +41,9 @@ public abstract class InprogressSubmissionIndexFactoryImpl @Autowired protected ItemIndexFactory indexableItemService; + @Autowired + protected SupervisionOrderService supervisionOrderService; + @Override public SolrInputDocument buildDocument(Context context, T indexableObject) throws SQLException, IOException { @@ -60,6 +65,8 @@ public abstract class InprogressSubmissionIndexFactoryImpl submitter.getFullName()); } + addSupervisedByFacetIndex(context, item, doc); + doc.addField("inprogress.item", new IndexableItem(inProgressSubmission.getItem()).getUniqueIndexID()); // get the location string (for searching by collection & community) @@ -82,4 +89,13 @@ public abstract class InprogressSubmissionIndexFactoryImpl indexableItemService.addDiscoveryFields(doc, context, item, discoveryConfigurations); indexableCollectionService.storeCommunityCollectionLocations(doc, locations); } + + private void addSupervisedByFacetIndex(Context context, Item item, SolrInputDocument doc) throws SQLException { + List supervisionOrders = supervisionOrderService.findByItem(context, item); + for (SupervisionOrder supervisionOrder : supervisionOrders) { + addFacetIndex(doc, "supervisedBy", supervisionOrder.getGroup().getID().toString(), + supervisionOrder.getGroup().getName()); + } + + } } diff --git a/dspace-api/src/main/java/org/dspace/discovery/indexobject/ItemIndexFactoryImpl.java b/dspace-api/src/main/java/org/dspace/discovery/indexobject/ItemIndexFactoryImpl.java index 15a74b45d1..fc024cc524 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/indexobject/ItemIndexFactoryImpl.java +++ b/dspace-api/src/main/java/org/dspace/discovery/indexobject/ItemIndexFactoryImpl.java @@ -7,6 +7,8 @@ */ package org.dspace.discovery.indexobject; +import static org.dspace.discovery.SolrServiceImpl.SOLR_FIELD_SUFFIX_FACET_PREFIXES; + import java.io.IOException; import java.sql.SQLException; import java.util.ArrayList; @@ -20,6 +22,8 @@ import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.UUID; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; @@ -27,6 +31,7 @@ import org.apache.commons.lang3.time.DateFormatUtils; import org.apache.logging.log4j.Logger; import org.apache.solr.client.solrj.SolrServerException; import org.apache.solr.common.SolrInputDocument; +import org.dspace.authority.service.AuthorityValueService; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.Item; @@ -89,6 +94,8 @@ public class ItemIndexFactoryImpl extends DSpaceObjectIndexFactoryImpl + * E.g. Author "With Multiple Words" gets stored as: + *
+ * + * with multiple words ||| With Multiple Words,
+ * multiple words ||| With Multiple Words,
+ * words ||| With Multiple Words,
+ *
+ * in the author_prefix field. + * @param doc the solr document + * @param searchFilter the current discoverySearchFilter + * @param value the metadata value + * @param separator the separator being used to separate value part and original value + */ + private void saveFacetPrefixParts(SolrInputDocument doc, DiscoverySearchFilter searchFilter, String value, + String separator, String authority, String preferedLabel) { + value = StringUtils.normalizeSpace(value); + Pattern pattern = Pattern.compile("\\b\\w+\\b", Pattern.CASE_INSENSITIVE); + Matcher matcher = pattern.matcher(value); + while (matcher.find()) { + int index = matcher.start(); + String currentPart = StringUtils.substring(value, index); + if (authority != null) { + String facetValue = preferedLabel != null ? preferedLabel : currentPart; + doc.addField(searchFilter.getIndexFieldName() + SOLR_FIELD_SUFFIX_FACET_PREFIXES, + facetValue.toLowerCase() + separator + value + + SearchUtils.AUTHORITY_SEPARATOR + authority); + } else { + doc.addField(searchFilter.getIndexFieldName() + SOLR_FIELD_SUFFIX_FACET_PREFIXES, + currentPart.toLowerCase() + separator + value); + } + } + } } diff --git a/dspace-api/src/main/java/org/dspace/discovery/utils/DiscoverQueryBuilder.java b/dspace-api/src/main/java/org/dspace/discovery/utils/DiscoverQueryBuilder.java index d7a546d936..fa5cc32813 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/utils/DiscoverQueryBuilder.java +++ b/dspace-api/src/main/java/org/dspace/discovery/utils/DiscoverQueryBuilder.java @@ -245,6 +245,7 @@ public class DiscoverQueryBuilder implements InitializingBean { // "show more" url int facetLimit = pageSize + 1; //This should take care of the sorting for us + prefix = StringUtils.isNotBlank(prefix) ? prefix.toLowerCase() : null; queryArgs.addFacetField(new DiscoverFacetField(facet.getIndexFieldName(), facet.getType(), facetLimit, facet.getSortOrderSidebar(), StringUtils.trimToNull(prefix))); 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 d873d7230c..3d4eab125f 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/AccountServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/AccountServiceImpl.java @@ -14,6 +14,7 @@ import javax.mail.MessagingException; 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; import org.dspace.core.Email; @@ -51,6 +52,9 @@ public class AccountServiceImpl implements AccountService { @Autowired private ConfigurationService configurationService; + @Autowired + private AuthenticationService authenticationService; + protected AccountServiceImpl() { } @@ -79,6 +83,9 @@ public class AccountServiceImpl implements AccountService { if (!configurationService.getBooleanProperty("user.registration", true)) { throw new IllegalStateException("The user.registration parameter was set to false"); } + if (!authenticationService.canSelfRegister(context, null, email)) { + throw new IllegalStateException("self registration is not allowed with this email address"); + } sendInfo(context, email, true, true); } diff --git a/dspace-api/src/main/java/org/dspace/eperson/FrequencyType.java b/dspace-api/src/main/java/org/dspace/eperson/FrequencyType.java new file mode 100644 index 0000000000..72822fb871 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/eperson/FrequencyType.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.eperson; + +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Calendar; + +import org.apache.commons.codec.binary.StringUtils; + +/** + * This enum holds all the possible frequency types + * that can be used in "subscription-send" script + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com) + */ +public enum FrequencyType { + DAY("D"), + WEEK("W"), + MONTH("M"); + + private String shortName; + + private FrequencyType(String shortName) { + this.shortName = shortName; + } + + public static String findLastFrequency(String frequency) { + String startDate = ""; + String endDate = ""; + Calendar cal = Calendar.getInstance(); + // Full ISO 8601 is e.g. + SimpleDateFormat fullIsoStart = new SimpleDateFormat("yyyy-MM-dd'T'00:00:00'Z'"); + SimpleDateFormat fullIsoEnd = new SimpleDateFormat("yyyy-MM-dd'T'23:59:59'Z'"); + switch (frequency) { + case "D": + cal.add(Calendar.DAY_OF_MONTH, -1); + endDate = fullIsoEnd.format(cal.getTime()); + startDate = fullIsoStart.format(cal.getTime()); + break; + case "M": + int dayOfMonth = cal.get(Calendar.DAY_OF_MONTH); + cal.add(Calendar.DAY_OF_MONTH, -dayOfMonth); + endDate = fullIsoEnd.format(cal.getTime()); + cal.add(Calendar.MONTH, -1); + cal.add(Calendar.DAY_OF_MONTH, 1); + startDate = fullIsoStart.format(cal.getTime()); + break; + case "W": + cal.add(Calendar.DAY_OF_WEEK, -1); + int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK) - 1; + cal.add(Calendar.DAY_OF_WEEK, -dayOfWeek); + endDate = fullIsoEnd.format(cal.getTime()); + cal.add(Calendar.DAY_OF_WEEK, -6); + startDate = fullIsoStart.format(cal.getTime()); + break; + default: + return null; + } + return "[" + startDate + " TO " + endDate + "]"; + } + + public static boolean isSupportedFrequencyType(String value) { + for (FrequencyType ft : Arrays.asList(FrequencyType.values())) { + if (StringUtils.equals(ft.getShortName(), value)) { + return true; + } + } + return false; + } + + public String getShortName() { + return shortName; + } + +} 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 b2d3964895..6cb534146b 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/Group.java +++ b/dspace-api/src/main/java/org/dspace/eperson/Group.java @@ -23,7 +23,6 @@ import javax.persistence.Transient; import org.apache.commons.lang3.StringUtils; import org.dspace.content.DSpaceObject; import org.dspace.content.DSpaceObjectLegacySupport; -import org.dspace.content.WorkspaceItem; import org.dspace.core.Constants; import org.dspace.core.Context; import org.hibernate.annotations.CacheConcurrencyStrategy; @@ -83,9 +82,6 @@ public class Group extends DSpaceObject implements DSpaceObjectLegacySupport { @ManyToMany(fetch = FetchType.LAZY, mappedBy = "groups") private final List parentGroups = new ArrayList<>(); - @ManyToMany(fetch = FetchType.LAZY, mappedBy = "supervisorGroups") - private final List supervisedItems = new ArrayList<>(); - @Transient private boolean groupsChanged; @@ -218,10 +214,6 @@ public class Group extends DSpaceObject implements DSpaceObjectLegacySupport { return legacyId; } - public List getSupervisedItems() { - return supervisedItems; - } - /** * May this Group be renamed or deleted? (The content of any group may be * changed.) 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 2b23ecfeef..607e57af0b 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java @@ -353,8 +353,6 @@ public class GroupServiceImpl extends DSpaceObjectServiceImpl implements List groupCache = group2GroupCacheDAO.findByChildren(context, groups); // now we have all owning groups, also grab all parents of owning groups - // yes, I know this could have been done as one big query and a union, - // but doing the Oracle port taught me to keep to simple SQL! for (Group2GroupCache group2GroupCache : groupCache) { groups.add(group2GroupCache.getParent()); } @@ -480,9 +478,6 @@ public class GroupServiceImpl extends DSpaceObjectServiceImpl implements context.addEvent(new Event(Event.DELETE, Constants.GROUP, group.getID(), group.getName(), getIdentifiers(context, group))); - //Remove the supervised group from any workspace items linked to us. - group.getSupervisedItems().clear(); - // Remove any ResourcePolicies that reference this group authorizeService.removeGroupPolicies(context, group); diff --git a/dspace-api/src/main/java/org/dspace/eperson/SubscribeCLITool.java b/dspace-api/src/main/java/org/dspace/eperson/SubscribeCLITool.java deleted file mode 100644 index 9e5ecaa4fb..0000000000 --- a/dspace-api/src/main/java/org/dspace/eperson/SubscribeCLITool.java +++ /dev/null @@ -1,432 +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.eperson; - -import java.io.IOException; -import java.sql.SQLException; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Date; -import java.util.List; -import java.util.Locale; -import java.util.ResourceBundle; -import java.util.TimeZone; -import javax.mail.MessagingException; - -import org.apache.commons.cli.CommandLine; -import org.apache.commons.cli.DefaultParser; -import org.apache.commons.cli.HelpFormatter; -import org.apache.commons.cli.Option; -import org.apache.commons.cli.Options; -import org.apache.commons.lang3.StringUtils; -import org.apache.logging.log4j.Logger; -import org.dspace.content.Collection; -import org.dspace.content.DCDate; -import org.dspace.content.Item; -import org.dspace.content.MetadataSchemaEnum; -import org.dspace.content.MetadataValue; -import org.dspace.content.factory.ContentServiceFactory; -import org.dspace.content.service.ItemService; -import org.dspace.core.Context; -import org.dspace.core.Email; -import org.dspace.core.I18nUtil; -import org.dspace.core.LogHelper; -import org.dspace.eperson.factory.EPersonServiceFactory; -import org.dspace.eperson.service.SubscribeService; -import org.dspace.handle.factory.HandleServiceFactory; -import org.dspace.handle.service.HandleService; -import org.dspace.search.Harvest; -import org.dspace.search.HarvestedItemInfo; -import org.dspace.services.ConfigurationService; -import org.dspace.services.factory.DSpaceServicesFactory; - -/** - * CLI tool used for sending new item e-mail alerts to users - * - * @author Robert Tansley - * @version $Revision$ - */ -public class SubscribeCLITool { - - private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(SubscribeCLITool.class); - - private static final HandleService handleService - = HandleServiceFactory.getInstance().getHandleService(); - private static final ItemService itemService - = ContentServiceFactory.getInstance().getItemService(); - private static final SubscribeService subscribeService - = EPersonServiceFactory.getInstance().getSubscribeService(); - private static final ConfigurationService configurationService - = DSpaceServicesFactory.getInstance().getConfigurationService(); - - /** - * Default constructor - */ - private SubscribeCLITool() { } - - /** - * Process subscriptions. This must be invoked only once a day. Messages are - * only sent out when a collection has actually received new items, so that - * people's mailboxes are not clogged with many "no new items" mails. - *

- * Yesterday's newly available items are included. If this is run at for - * example midday, any items that have been made available during the - * current day will not be included, but will be included in the next day's - * run. - *

- * For example, if today's date is 2002-10-10 (in UTC) items made available - * during 2002-10-09 (UTC) will be included. - * - * @param context The relevant DSpace Context. - * @param test If true, do a "dry run", i.e. don't actually send email, just log the attempt - * @throws SQLException An exception that provides information on a database access error or other errors. - * @throws IOException A general class of exceptions produced by failed or interrupted I/O operations. - */ - public static void processDaily(Context context, boolean test) throws SQLException, - IOException { - // Grab the subscriptions - - List subscriptions = subscribeService.findAll(context); - - EPerson currentEPerson = null; - List collections = null; // List of Collections - - // Go through the list collating subscriptions for each e-person - for (Subscription subscription : subscriptions) { - // Does this row relate to the same e-person as the last? - if ((currentEPerson == null) - || (!subscription.getePerson().getID().equals(currentEPerson - .getID()))) { - // New e-person. Send mail for previous e-person - if (currentEPerson != null) { - - try { - sendEmail(context, currentEPerson, collections, test); - } catch (MessagingException me) { - log.error("Failed to send subscription to eperson_id=" - + currentEPerson.getID()); - log.error(me); - } - } - - currentEPerson = subscription.getePerson(); - collections = new ArrayList<>(); - } - - collections.add(subscription.getCollection()); - } - - // Process the last person - if (currentEPerson != null) { - try { - sendEmail(context, currentEPerson, collections, test); - } catch (MessagingException me) { - log.error("Failed to send subscription to eperson_id=" - + currentEPerson.getID()); - log.error(me); - } - } - } - - /** - * Sends an email to the given e-person with details of new items in the - * given collections, items that appeared yesterday. No e-mail is sent if - * there aren't any new items in any of the collections. - * - * @param context DSpace context object - * @param eperson eperson to send to - * @param collections List of collection IDs (Integers) - * @param test If true, do a "dry run", i.e. don't actually send email, just log the attempt - * @throws IOException A general class of exceptions produced by failed or interrupted I/O operations. - * @throws MessagingException A general class of exceptions for sending email. - * @throws SQLException An exception that provides information on a database access error or other errors. - */ - public static void sendEmail(Context context, EPerson eperson, - List collections, boolean test) throws IOException, MessagingException, - SQLException { - // Get a resource bundle according to the eperson language preferences - Locale supportedLocale = I18nUtil.getEPersonLocale(eperson); - ResourceBundle labels = ResourceBundle.getBundle("Messages", supportedLocale); - - // Get the start and end dates for yesterday - - // The date should reflect the timezone as well. Otherwise we stand to lose that information - // in truncation and roll to an earlier date than intended. - Calendar cal = Calendar.getInstance(TimeZone.getDefault()); - cal.setTime(new Date()); - - // What we actually want to pass to Harvest is "Midnight of yesterday in my current timezone" - // Truncation will actually pass in "Midnight of yesterday in UTC", which will be, - // at least in CDT, "7pm, the day before yesterday, in my current timezone". - cal.add(Calendar.HOUR, -24); - cal.set(Calendar.HOUR_OF_DAY, 0); - cal.set(Calendar.MINUTE, 0); - cal.set(Calendar.SECOND, 0); - Date midnightYesterday = cal.getTime(); - - - // FIXME: text of email should be more configurable from an - // i18n viewpoint - StringBuilder emailText = new StringBuilder(); - boolean isFirst = true; - - for (int i = 0; i < collections.size(); i++) { - Collection c = collections.get(i); - - try { - boolean includeAll = configurationService - .getBooleanProperty("harvest.includerestricted.subscription", true); - - // we harvest all the changed item from yesterday until now - List itemInfos = Harvest - .harvest(context, c, new DCDate(midnightYesterday).toString(), null, 0, // Limit - // and - // offset - // zero, - // get - // everything - 0, true, // Need item objects - false, // But not containers - false, // Or withdrawals - includeAll); - - if (configurationService.getBooleanProperty("eperson.subscription.onlynew", false)) { - // get only the items archived yesterday - itemInfos = filterOutModified(itemInfos); - } else { - // strip out the item archived today or - // not archived yesterday and modified today - itemInfos = filterOutToday(itemInfos); - } - - // Only add to buffer if there are new items - if (itemInfos.size() > 0) { - if (!isFirst) { - emailText - .append("\n---------------------------------------\n"); - } else { - isFirst = false; - } - - emailText.append(labels.getString("org.dspace.eperson.Subscribe.new-items")).append(" ").append( - c.getName()).append(": ").append( - itemInfos.size()).append("\n\n"); - - for (int j = 0; j < itemInfos.size(); j++) { - HarvestedItemInfo hii = (HarvestedItemInfo) itemInfos - .get(j); - - String title = hii.item.getName(); - emailText.append(" ").append(labels.getString("org.dspace.eperson.Subscribe.title")) - .append(" "); - - if (StringUtils.isNotBlank(title)) { - emailText.append(title); - } else { - emailText.append(labels.getString("org.dspace.eperson.Subscribe.untitled")); - } - - List authors = itemService - .getMetadata(hii.item, MetadataSchemaEnum.DC.getName(), "contributor", Item.ANY, Item.ANY); - - if (authors.size() > 0) { - emailText.append("\n ").append(labels.getString("org.dspace.eperson.Subscribe.authors")) - .append(" ").append( - authors.get(0).getValue()); - - for (int k = 1; k < authors.size(); k++) { - emailText.append("\n ").append( - authors.get(k).getValue()); - } - } - - emailText.append("\n ").append(labels.getString("org.dspace.eperson.Subscribe.id")) - .append(" ").append( - handleService.getCanonicalForm(hii.handle)).append( - "\n\n"); - } - } - } catch (ParseException pe) { - // This should never get thrown as the Dates are auto-generated - } - } - - // Send an e-mail if there were any new items - if (emailText.length() > 0) { - - if (test) { - log.info(LogHelper.getHeader(context, "subscription:", "eperson=" + eperson.getEmail())); - log.info(LogHelper.getHeader(context, "subscription:", "text=" + emailText.toString())); - - } else { - - Email email = Email.getEmail(I18nUtil.getEmailFilename(supportedLocale, "subscription")); - email.addRecipient(eperson.getEmail()); - email.addArgument(emailText.toString()); - email.send(); - - log.info(LogHelper.getHeader(context, "sent_subscription", "eperson_id=" + eperson.getID())); - - } - - - } - } - - /** - * Method for invoking subscriptions via the command line - * - * @param argv the command line arguments given - */ - public static void main(String[] argv) { - String usage = "org.dspace.eperson.Subscribe [-t] or nothing to send out subscriptions."; - - Options options = new Options(); - HelpFormatter formatter = new HelpFormatter(); - CommandLine line = null; - - { - Option opt = new Option("t", "test", false, "Run test session"); - opt.setRequired(false); - options.addOption(opt); - } - - { - Option opt = new Option("h", "help", false, "Print this help message"); - opt.setRequired(false); - options.addOption(opt); - } - - try { - line = new DefaultParser().parse(options, argv); - } catch (org.apache.commons.cli.ParseException e) { - // automatically generate the help statement - formatter.printHelp(usage, e.getMessage(), options, ""); - System.exit(1); - } - - if (line.hasOption("h")) { - // automatically generate the help statement - formatter.printHelp(usage, options); - System.exit(1); - } - - boolean test = line.hasOption("t"); - - Context context = null; - - try { - context = new Context(Context.Mode.READ_ONLY); - processDaily(context, test); - context.complete(); - } catch (IOException | SQLException e) { - log.fatal(e); - } finally { - if (context != null && context.isValid()) { - // Nothing is actually written - context.abort(); - } - } - } - - private static List filterOutToday(List completeList) { - log.debug("Filtering out all today item to leave new items list size=" - + completeList.size()); - List filteredList = new ArrayList<>(); - - SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); - String today = sdf.format(new Date()); - // Get the start and end dates for yesterday - Date thisTimeYesterday = new Date(System.currentTimeMillis() - - (24 * 60 * 60 * 1000)); - String yesterday = sdf.format(thisTimeYesterday); - - for (HarvestedItemInfo infoObject : completeList) { - Date lastUpdate = infoObject.item.getLastModified(); - String lastUpdateStr = sdf.format(lastUpdate); - - // has the item modified today? - if (lastUpdateStr.equals(today)) { - List dateAccArr = itemService.getMetadata(infoObject.item, "dc", - "date", "accessioned", Item.ANY); - // we need only the item archived yesterday - if (dateAccArr != null && dateAccArr.size() > 0) { - for (MetadataValue date : dateAccArr) { - if (date != null && date.getValue() != null) { - // if it hasn't been archived today - if (date.getValue().startsWith(yesterday)) { - filteredList.add(infoObject); - log.debug("adding : " + dateAccArr.get(0).getValue() - + " : " + today + " : " - + infoObject.handle); - break; - } else { - log.debug("ignoring : " + dateAccArr.get(0).getValue() - + " : " + today + " : " - + infoObject.handle); - } - } - } - } else { - log.debug("no date accessioned, adding : " - + infoObject.handle); - filteredList.add(infoObject); - } - } else { - // the item has been modified yesterday... - filteredList.add(infoObject); - } - } - - return filteredList; - } - - private static List filterOutModified(List completeList) { - log.debug("Filtering out all modified to leave new items list size=" + completeList.size()); - List filteredList = new ArrayList<>(); - - SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); - // Get the start and end dates for yesterday - Date thisTimeYesterday = new Date(System.currentTimeMillis() - - (24 * 60 * 60 * 1000)); - String yesterday = sdf.format(thisTimeYesterday); - - for (HarvestedItemInfo infoObject : completeList) { - List dateAccArr = itemService - .getMetadata(infoObject.item, "dc", "date", "accessioned", Item.ANY); - - if (dateAccArr != null && dateAccArr.size() > 0) { - for (MetadataValue date : dateAccArr) { - if (date != null && date.getValue() != null) { - // if it has been archived yesterday - if (date.getValue().startsWith(yesterday)) { - filteredList.add(infoObject); - log.debug("adding : " + dateAccArr.get(0) - .getValue() + " : " + yesterday + " : " + infoObject - .handle); - break; - } else { - log.debug("ignoring : " + dateAccArr.get(0) - .getValue() + " : " + yesterday + " : " + infoObject - .handle); - } - } - } - - - } else { - log.debug("no date accessioned, adding : " + infoObject.handle); - filteredList.add(infoObject); - } - } - - return filteredList; - } -} diff --git a/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java b/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java index 81c367f0ea..2e4d94f443 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java @@ -9,11 +9,16 @@ package org.dspace.eperson; import java.sql.SQLException; import java.util.List; +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.authorize.AuthorizeException; import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.DSpaceObject; import org.dspace.content.service.CollectionService; import org.dspace.core.Constants; import org.dspace.core.Context; @@ -29,106 +34,177 @@ import org.springframework.beans.factory.annotation.Autowired; * @version $Revision$ */ public class SubscribeServiceImpl implements SubscribeService { - /** - * log4j logger - */ - private Logger log = org.apache.logging.log4j.LogManager.getLogger(SubscribeServiceImpl.class); + + private Logger log = LogManager.getLogger(SubscribeServiceImpl.class); @Autowired(required = true) - protected SubscriptionDAO subscriptionDAO; - + private SubscriptionDAO subscriptionDAO; @Autowired(required = true) - protected AuthorizeService authorizeService; + private AuthorizeService authorizeService; @Autowired(required = true) - protected CollectionService collectionService; - - protected SubscribeServiceImpl() { - - } + private CollectionService collectionService; @Override - public List findAll(Context context) throws SQLException { - return subscriptionDAO.findAllOrderedByEPerson(context); - } - - @Override - public void subscribe(Context context, EPerson eperson, - Collection collection) throws SQLException, AuthorizeException { - // Check authorisation. Must be administrator, or the eperson. - if (authorizeService.isAdmin(context) - || ((context.getCurrentUser() != null) && (context - .getCurrentUser().getID().equals(eperson.getID())))) { - if (!isSubscribed(context, eperson, collection)) { - Subscription subscription = subscriptionDAO.create(context, new Subscription()); - subscription.setCollection(collection); - subscription.setePerson(eperson); - } + public List findAll(Context context, String resourceType, Integer limit, Integer offset) + throws Exception { + if (StringUtils.isBlank(resourceType)) { + return subscriptionDAO.findAllOrderedByDSO(context, limit, offset); } else { - throw new AuthorizeException( - "Only admin or e-person themselves can subscribe"); + if (resourceType.equals(Collection.class.getSimpleName()) || + resourceType.equals(Community.class.getSimpleName())) { + return subscriptionDAO.findAllOrderedByIDAndResourceType(context, resourceType, limit, offset); + } else { + log.error("Resource type must be Collection or Community"); + throw new Exception("Resource type must be Collection or Community"); + } } } @Override - public void unsubscribe(Context context, EPerson eperson, - Collection collection) throws SQLException, AuthorizeException { + public Subscription subscribe(Context context, EPerson eperson, + DSpaceObject dSpaceObject, + List subscriptionParameterList, + String type) throws SQLException, AuthorizeException { // Check authorisation. Must be administrator, or the eperson. if (authorizeService.isAdmin(context) - || ((context.getCurrentUser() != null) && (context - .getCurrentUser().getID().equals(eperson.getID())))) { - if (collection == null) { + || ((context.getCurrentUser() != null) && (context + .getCurrentUser().getID().equals(eperson.getID())))) { + Subscription newSubscription = subscriptionDAO.create(context, new Subscription()); + subscriptionParameterList.forEach(subscriptionParameter -> + newSubscription.addParameter(subscriptionParameter)); + newSubscription.setEPerson(eperson); + newSubscription.setDSpaceObject(dSpaceObject); + newSubscription.setSubscriptionType(type); + return newSubscription; + } else { + throw new AuthorizeException("Only admin or e-person themselves can subscribe"); + } + } + + @Override + public void unsubscribe(Context context, EPerson eperson, DSpaceObject dSpaceObject) + throws SQLException, AuthorizeException { + // Check authorisation. Must be administrator, or the eperson. + if (authorizeService.isAdmin(context) + || ((context.getCurrentUser() != null) && (context + .getCurrentUser().getID().equals(eperson.getID())))) { + if (dSpaceObject == null) { // Unsubscribe from all subscriptionDAO.deleteByEPerson(context, eperson); } else { - subscriptionDAO.deleteByCollectionAndEPerson(context, collection, eperson); + subscriptionDAO.deleteByDSOAndEPerson(context, dSpaceObject, eperson); log.info(LogHelper.getHeader(context, "unsubscribe", "eperson_id=" + eperson.getID() + ",collection_id=" - + collection.getID())); + + dSpaceObject.getID())); } } else { - throw new AuthorizeException( - "Only admin or e-person themselves can unsubscribe"); + throw new AuthorizeException("Only admin or e-person themselves can unsubscribe"); } } @Override - public List getSubscriptions(Context context, EPerson eperson) - throws SQLException { - return subscriptionDAO.findByEPerson(context, eperson); + public List findSubscriptionsByEPerson(Context context, EPerson eperson, Integer limit,Integer offset) + throws SQLException { + return subscriptionDAO.findByEPerson(context, eperson, limit, offset); } @Override - public List getAvailableSubscriptions(Context context) - throws SQLException { - return getAvailableSubscriptions(context, null); + public List findSubscriptionsByEPersonAndDso(Context context, EPerson eperson, + DSpaceObject dSpaceObject, + Integer limit, Integer offset) throws SQLException { + return subscriptionDAO.findByEPersonAndDso(context, eperson, dSpaceObject, limit, offset); } @Override - public List getAvailableSubscriptions(Context context, EPerson eperson) - throws SQLException { - List collections; - if (eperson != null) { + public List findAvailableSubscriptions(Context context) throws SQLException { + return findAvailableSubscriptions(context, null); + } + + @Override + public List findAvailableSubscriptions(Context context, EPerson eperson) throws SQLException { + if (Objects.nonNull(eperson)) { context.setCurrentUser(eperson); } - collections = collectionService.findAuthorized(context, null, Constants.ADD); - - return collections; + return collectionService.findAuthorized(context, null, Constants.ADD); } @Override - public boolean isSubscribed(Context context, EPerson eperson, - Collection collection) throws SQLException { - return subscriptionDAO.findByCollectionAndEPerson(context, eperson, collection) != null; + public boolean isSubscribed(Context context, EPerson eperson, DSpaceObject dSpaceObject) throws SQLException { + return subscriptionDAO.findByEPersonAndDso(context, eperson, dSpaceObject, -1, -1) != null; } @Override - public void deleteByCollection(Context context, Collection collection) throws SQLException { - subscriptionDAO.deleteByCollection(context, collection); + public void deleteByDspaceObject(Context context, DSpaceObject dSpaceObject) throws SQLException { + subscriptionDAO.deleteByDspaceObject(context, dSpaceObject); } @Override public void deleteByEPerson(Context context, EPerson ePerson) throws SQLException { subscriptionDAO.deleteByEPerson(context, ePerson); } + + @Override + public Subscription findById(Context context, int id) throws SQLException { + return subscriptionDAO.findByID(context, Subscription.class, id); + } + + @Override + public Subscription updateSubscription(Context context, Integer id, String subscriptionType, + List subscriptionParameterList) + throws SQLException { + Subscription subscriptionDB = subscriptionDAO.findByID(context, Subscription.class, id); + subscriptionDB.removeParameterList(); + subscriptionDB.setSubscriptionType(subscriptionType); + subscriptionParameterList.forEach(x -> subscriptionDB.addParameter(x)); + subscriptionDAO.save(context, subscriptionDB); + return subscriptionDB; + } + + @Override + public Subscription addSubscriptionParameter(Context context, Integer id, SubscriptionParameter subscriptionParam) + throws SQLException { + Subscription subscriptionDB = subscriptionDAO.findByID(context, Subscription.class, id); + subscriptionDB.addParameter(subscriptionParam); + subscriptionDAO.save(context, subscriptionDB); + return subscriptionDB; + } + + @Override + public Subscription removeSubscriptionParameter(Context context,Integer id, SubscriptionParameter subscriptionParam) + throws SQLException { + Subscription subscriptionDB = subscriptionDAO.findByID(context, Subscription.class, id); + subscriptionDB.removeParameter(subscriptionParam); + subscriptionDAO.save(context, subscriptionDB); + return subscriptionDB; + } + + @Override + public void deleteSubscription(Context context, Subscription subscription) throws SQLException { + subscriptionDAO.delete(context, subscription); + } + + @Override + public List findAllSubscriptionsBySubscriptionTypeAndFrequency(Context context, + String subscriptionType, String frequencyValue) throws SQLException { + return subscriptionDAO.findAllSubscriptionsBySubscriptionTypeAndFrequency(context, subscriptionType, + frequencyValue); + } + + @Override + public Long countAll(Context context) throws SQLException { + return subscriptionDAO.countAll(context); + } + + @Override + public Long countSubscriptionsByEPerson(Context context, EPerson ePerson) throws SQLException { + return subscriptionDAO.countAllByEPerson(context, ePerson); + } + + @Override + public Long countByEPersonAndDSO(Context context, EPerson ePerson, DSpaceObject dSpaceObject) + throws SQLException { + return subscriptionDAO.countAllByEPersonAndDso(context, ePerson, dSpaceObject); + } + } 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 1719888ca8..5db63740f4 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/Subscription.java +++ b/dspace-api/src/main/java/org/dspace/eperson/Subscription.java @@ -7,6 +7,9 @@ */ package org.dspace.eperson; +import java.util.ArrayList; +import java.util.List; +import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; @@ -15,10 +18,11 @@ 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 org.dspace.content.Collection; +import org.dspace.content.DSpaceObject; import org.dspace.core.Context; import org.dspace.core.ReloadableEntity; @@ -37,40 +41,78 @@ public class Subscription implements ReloadableEntity { @SequenceGenerator(name = "subscription_seq", sequenceName = "subscription_seq", allocationSize = 1) private Integer id; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "collection_id") - private Collection collection; + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "dspace_object_id") + private DSpaceObject dSpaceObject; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "eperson_id") private EPerson ePerson; /** - * Protected constructor, create object using: - * {@link org.dspace.eperson.service.SubscribeService#subscribe(Context, EPerson, Collection)} + * Represent subscription type, for example, "content" or "statistics". + * + * NOTE: Currently, in DSpace we use only one "content" */ - protected Subscription() { + @Column(name = "type") + private String subscriptionType; - } + @OneToMany(fetch = FetchType.LAZY, mappedBy = "subscription", cascade = CascadeType.ALL, orphanRemoval = true) + private List subscriptionParameterList = new ArrayList<>(); + + /** + * Protected constructor, create object using: + * {@link org.dspace.eperson.service.SubscribeService#subscribe(Context, EPerson, DSpaceObject, List, String)} + */ + protected Subscription() {} @Override public Integer getID() { return id; } - public Collection getCollection() { - return collection; + public DSpaceObject getDSpaceObject() { + return this.dSpaceObject; } - void setCollection(Collection collection) { - this.collection = collection; + void setDSpaceObject(DSpaceObject dSpaceObject) { + this.dSpaceObject = dSpaceObject; } - public EPerson getePerson() { + public EPerson getEPerson() { return ePerson; } - void setePerson(EPerson ePerson) { + public void setEPerson(EPerson ePerson) { this.ePerson = ePerson; } -} + + public String getSubscriptionType() { + return subscriptionType; + } + + public void setSubscriptionType(String subscriptionType) { + this.subscriptionType = subscriptionType; + } + + public List getSubscriptionParameterList() { + return subscriptionParameterList; + } + + public void setSubscriptionParameterList(List subscriptionList) { + this.subscriptionParameterList = subscriptionList; + } + + public void addParameter(SubscriptionParameter subscriptionParameter) { + subscriptionParameterList.add(subscriptionParameter); + subscriptionParameter.setSubscription(this); + } + + public void removeParameterList() { + subscriptionParameterList.clear(); + } + + public void removeParameter(SubscriptionParameter subscriptionParameter) { + subscriptionParameterList.remove(subscriptionParameter); + } +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/eperson/SubscriptionParameter.java b/dspace-api/src/main/java/org/dspace/eperson/SubscriptionParameter.java new file mode 100644 index 0000000000..7526535d7f --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/eperson/SubscriptionParameter.java @@ -0,0 +1,98 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +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 org.dspace.core.ReloadableEntity; + +/** + * Database entity representation of the subscription_parameter table + * SubscriptionParameter represents a frequency with which an user wants to be notified. + * + * @author Alba Aliu at atis.al + */ +@Entity +@Table(name = "subscription_parameter") +public class SubscriptionParameter implements ReloadableEntity { + + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "subscription_parameter_seq") + @SequenceGenerator(name = "subscription_parameter_seq", sequenceName = "subscription_parameter_seq", + allocationSize = 1) + @Column(name = "subscription_parameter_id", unique = true) + private Integer id; + + @ManyToOne + @JoinColumn(name = "subscription_id", nullable = false) + private Subscription subscription; + + /* + * Currently, we have only one use case for this attribute: "frequency" + */ + @Column + private String name; + + /* + * Currently, we use this attribute only with following values: "D", "W", "M". + * Where D stand for Day, W stand for Week and M stand for Month + */ + @Column + private String value; + + public SubscriptionParameter() {} + + public SubscriptionParameter(Integer id, Subscription subscription, String name, String value) { + this.id = id; + this.subscription = subscription; + this.name = name; + this.value = value; + } + + public Subscription getSubscription() { + return subscription; + } + + public void setSubscription(Subscription subscription) { + this.subscription = subscription; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + @Override + public Integer getID() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/eperson/SupervisorServiceImpl.java b/dspace-api/src/main/java/org/dspace/eperson/SupervisorServiceImpl.java deleted file mode 100644 index 64180a5e22..0000000000 --- a/dspace-api/src/main/java/org/dspace/eperson/SupervisorServiceImpl.java +++ /dev/null @@ -1,93 +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.eperson; - -import java.sql.SQLException; - -import org.dspace.authorize.AuthorizeException; -import org.dspace.authorize.ResourcePolicy; -import org.dspace.authorize.service.ResourcePolicyService; -import org.dspace.content.Item; -import org.dspace.content.WorkspaceItem; -import org.dspace.content.service.ItemService; -import org.dspace.core.Constants; -import org.dspace.core.Context; -import org.dspace.eperson.service.SupervisorService; -import org.springframework.beans.factory.annotation.Autowired; - -public class SupervisorServiceImpl implements SupervisorService { - - @Autowired(required = true) - protected ItemService itemService; - @Autowired(required = true) - protected ResourcePolicyService resourcePolicyService; - - protected SupervisorServiceImpl() { - } - - @Override - public boolean isOrder(Context context, WorkspaceItem workspaceItem, Group group) - throws SQLException { - return workspaceItem.getSupervisorGroups().contains(group); - } - - @Override - public void remove(Context context, WorkspaceItem workspaceItem, Group group) - throws SQLException, AuthorizeException { - // get the workspace item and the group from the request values - workspaceItem.getSupervisorGroups().remove(group); - - // get the item and have it remove the policies for the group - Item item = workspaceItem.getItem(); - itemService.removeGroupPolicies(context, item, group); - } - - @Override - public void add(Context context, Group group, WorkspaceItem workspaceItem, int policy) - throws SQLException, AuthorizeException { - // make a table row in the database table, and update with the relevant - // details - workspaceItem.getSupervisorGroups().add(group); - group.getSupervisedItems().add(workspaceItem); - - // If a default policy type has been requested, apply the policies using - // the DSpace API for doing so - if (policy != POLICY_NONE) { - Item item = workspaceItem.getItem(); - - // "Editor" implies READ, WRITE, ADD permissions - // "Observer" implies READ permissions - if (policy == POLICY_EDITOR) { - ResourcePolicy r = resourcePolicyService.create(context); - r.setdSpaceObject(item); - r.setGroup(group); - r.setAction(Constants.READ); - resourcePolicyService.update(context, r); - - r = resourcePolicyService.create(context); - r.setdSpaceObject(item); - r.setGroup(group); - r.setAction(Constants.WRITE); - resourcePolicyService.update(context, r); - - r = resourcePolicyService.create(context); - r.setdSpaceObject(item); - r.setGroup(group); - r.setAction(Constants.ADD); - resourcePolicyService.update(context, r); - - } else if (policy == POLICY_OBSERVER) { - ResourcePolicy r = resourcePolicyService.create(context); - r.setdSpaceObject(item); - r.setGroup(group); - r.setAction(Constants.READ); - resourcePolicyService.update(context, r); - } - } - } -} diff --git a/dspace-api/src/main/java/org/dspace/eperson/dao/SubscriptionDAO.java b/dspace-api/src/main/java/org/dspace/eperson/dao/SubscriptionDAO.java index e9f2d57059..4d762c1775 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/dao/SubscriptionDAO.java +++ b/dspace-api/src/main/java/org/dspace/eperson/dao/SubscriptionDAO.java @@ -10,7 +10,7 @@ package org.dspace.eperson.dao; import java.sql.SQLException; import java.util.List; -import org.dspace.content.Collection; +import org.dspace.content.DSpaceObject; import org.dspace.core.Context; import org.dspace.core.GenericDAO; import org.dspace.eperson.EPerson; @@ -26,17 +26,125 @@ import org.dspace.eperson.Subscription; */ public interface SubscriptionDAO extends GenericDAO { - public void deleteByCollection(Context context, Collection collection) throws SQLException; + /** + * Delete all subscription of provided dSpaceObject + * + * @param context DSpace context object + * @param dSpaceObject DSpace resource + * @throws SQLException If database error + */ + public void deleteByDspaceObject(Context context, DSpaceObject dSpaceObject) throws SQLException; - public List findByEPerson(Context context, EPerson eperson) throws SQLException; + /** + * Return a paginated list of all subscriptions of the eperson + * + * @param context DSpace context object + * @param eperson ePerson whose subscriptions want to find + * @param limit Paging limit + * @param offset The position of the first result to return + * @return + * @throws SQLException If database error + */ + public List findByEPerson(Context context, EPerson eperson, Integer limit, Integer offset) + throws SQLException; - public Subscription findByCollectionAndEPerson(Context context, EPerson eperson, Collection collection) - throws SQLException; + /** + * Return a paginated list of subscriptions related to a DSpaceObject belong to an ePerson + * + * @param context DSpace context object + * @param eperson ePerson whose subscriptions want to find + * @param dSpaceObject DSpaceObject of whom subscriptions want to find + * @param limit Paging limit + * @param offset The position of the first result to return + * @return + * @throws SQLException If database error + */ + public List findByEPersonAndDso(Context context, EPerson eperson, DSpaceObject dSpaceObject, + Integer limit, Integer offset) throws SQLException; + /** + * Delete all subscription of provided ePerson + * + * @param context DSpace context object + * @param eperson ePerson whose subscriptions want to delete + * @throws SQLException If database error + */ public void deleteByEPerson(Context context, EPerson eperson) throws SQLException; - public void deleteByCollectionAndEPerson(Context context, Collection collection, EPerson eperson) - throws SQLException; + /** + * Delete all subscriptions related to a DSpaceObject belong to an ePerson + * + * @param context DSpace context object + * @param dSpaceObject DSpaceObject of whom subscriptions want to delete + * @param eperson ePerson whose subscriptions want to delete + * @throws SQLException If database error + */ + public void deleteByDSOAndEPerson(Context context, DSpaceObject dSpaceObject, EPerson eperson) throws SQLException; + + /** + * Return a paginated list of all subscriptions ordered by ID and resourceType + * + * @param context DSpace context object + * @param resourceType Could be Collection or Community + * @param limit Paging limit + * @param offset The position of the first result to return + * @return + * @throws SQLException If database error + */ + public List findAllOrderedByIDAndResourceType(Context context, String resourceType, + Integer limit, Integer offset) throws SQLException; + + /** + * Return a paginated list of subscriptions ordered by DSpaceObject + * + * @param context DSpace context object + * @param limit Paging limit + * @param offset The position of the first result to return + * @return + * @throws SQLException If database error + */ + public List findAllOrderedByDSO(Context context, Integer limit, Integer offset) throws SQLException; + + /** + * Return a list of all subscriptions by subscriptionType and frequency + * + * @param context DSpace context object + * @param subscriptionType Could be "content" or "statistics". NOTE: in DSpace we have only "content" + * @param frequencyValue Could be "D" stand for Day, "W" stand for Week, and "M" stand for Month + * @return + * @throws SQLException If database error + */ + public List findAllSubscriptionsBySubscriptionTypeAndFrequency(Context context, + String subscriptionType, String frequencyValue) throws SQLException; + + /** + * Count all subscriptions + * + * @param context DSpace context object + * @return Total of all subscriptions + * @throws SQLException If database error + */ + public Long countAll(Context context) throws SQLException; + + /** + * Count all subscriptions belong to an ePerson + * + * @param context DSpace context object + * @param ePerson ePerson whose subscriptions want count + * @return Total of all subscriptions belong to an ePerson + * @throws SQLException If database error + */ + public Long countAllByEPerson(Context context, EPerson ePerson) throws SQLException; + + /** + * Count all subscriptions related to a DSpaceObject belong to an ePerson + * + * @param context DSpace context object + * @param ePerson ePerson whose subscriptions want count + * @param dSpaceObject DSpaceObject of whom subscriptions want count + * @return + * @throws SQLException If database error + */ + public Long countAllByEPersonAndDso(Context context, EPerson ePerson,DSpaceObject dSpaceObject) throws SQLException; - public List findAllOrderedByEPerson(Context context) throws SQLException; } diff --git a/dspace-api/src/main/java/org/dspace/eperson/dao/SubscriptionParameterDAO.java b/dspace-api/src/main/java/org/dspace/eperson/dao/SubscriptionParameterDAO.java new file mode 100644 index 0000000000..ea9c7b0bbd --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/eperson/dao/SubscriptionParameterDAO.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.eperson.dao; +import org.dspace.core.GenericDAO; +import org.dspace.eperson.SubscriptionParameter; + + +/** + * Database Access Object interface class for the SubscriptionParamter object. + * The implementation of this class is responsible for all database calls for the SubscriptionParameter object and is + * autowired by spring + * This class should only be accessed from a single service and should never be exposed outside of the API + * + * @author Alba Aliu at atis.al + */ +public interface SubscriptionParameterDAO extends GenericDAO { +} 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 6f2cb4b4fb..6c36211f31 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 @@ -9,17 +9,21 @@ package org.dspace.eperson.dao.impl; import java.sql.SQLException; import java.util.ArrayList; +import java.util.LinkedList; import java.util.List; import javax.persistence.Query; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Join; import javax.persistence.criteria.Root; -import org.dspace.content.Collection; +import org.dspace.content.DSpaceObject; import org.dspace.core.AbstractHibernateDAO; import org.dspace.core.Context; import org.dspace.eperson.EPerson; import org.dspace.eperson.Subscription; +import org.dspace.eperson.SubscriptionParameter; +import org.dspace.eperson.SubscriptionParameter_; import org.dspace.eperson.Subscription_; import org.dspace.eperson.dao.SubscriptionDAO; @@ -31,42 +35,50 @@ import org.dspace.eperson.dao.SubscriptionDAO; * @author kevinvandevelde at atmire.com */ public class SubscriptionDAOImpl extends AbstractHibernateDAO implements SubscriptionDAO { + protected SubscriptionDAOImpl() { super(); } @Override - public List findByEPerson(Context context, EPerson eperson) throws SQLException { + 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); Root subscriptionRoot = criteriaQuery.from(Subscription.class); criteriaQuery.select(subscriptionRoot); criteriaQuery.where(criteriaBuilder.equal(subscriptionRoot.get(Subscription_.ePerson), eperson)); - return list(context, criteriaQuery, false, Subscription.class, -1, -1); - + List orderList = new LinkedList<>(); + orderList.add(criteriaBuilder.asc(subscriptionRoot.get(Subscription_.dSpaceObject))); + criteriaQuery.orderBy(orderList); + return list(context, criteriaQuery, false, Subscription.class, limit, offset); } @Override - public Subscription findByCollectionAndEPerson(Context context, EPerson eperson, Collection collection) - throws SQLException { + public List findByEPersonAndDso(Context context, EPerson eperson, + DSpaceObject dSpaceObject, + Integer limit, Integer offset) throws SQLException { CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); - javax.persistence.criteria.CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Subscription.class); + javax.persistence.criteria.CriteriaQuery criteriaQuery = + getCriteriaQuery(criteriaBuilder, Subscription.class); Root subscriptionRoot = criteriaQuery.from(Subscription.class); criteriaQuery.select(subscriptionRoot); - criteriaQuery - .where(criteriaBuilder.and(criteriaBuilder.equal(subscriptionRoot.get(Subscription_.ePerson), eperson), - criteriaBuilder.equal(subscriptionRoot.get(Subscription_.collection), collection) - ) - ); - return singleResult(context, criteriaQuery); + criteriaQuery.where(criteriaBuilder.and(criteriaBuilder.equal( + subscriptionRoot.get(Subscription_.ePerson), eperson), + criteriaBuilder.equal(subscriptionRoot.get(Subscription_.dSpaceObject), dSpaceObject) + )); + List orderList = new LinkedList<>(); + orderList.add(criteriaBuilder.asc(subscriptionRoot.get(Subscription_.dSpaceObject))); + criteriaQuery.orderBy(orderList); + return list(context, criteriaQuery, false, Subscription.class, limit, offset); } @Override - public void deleteByCollection(Context context, Collection collection) throws SQLException { - String hqlQuery = "delete from Subscription where collection=:collection"; + public void deleteByDspaceObject(Context context, DSpaceObject dSpaceObject) throws SQLException { + String hqlQuery = "delete from Subscription where dSpaceObject=:dSpaceObject"; Query query = createQuery(context, hqlQuery); - query.setParameter("collection", collection); + query.setParameter("dSpaceObject", dSpaceObject); query.executeUpdate(); } @@ -79,28 +91,98 @@ public class SubscriptionDAOImpl extends AbstractHibernateDAO impl } @Override - public void deleteByCollectionAndEPerson(Context context, Collection collection, EPerson eperson) - throws SQLException { - String hqlQuery = "delete from Subscription where collection=:collection AND ePerson=:ePerson"; + public void deleteByDSOAndEPerson(Context context, DSpaceObject dSpaceObject, EPerson eperson) + throws SQLException { + String hqlQuery = "delete from Subscription where dSpaceObject=:dSpaceObject AND ePerson=:ePerson"; Query query = createQuery(context, hqlQuery); - query.setParameter("collection", collection); + query.setParameter("dSpaceObject", dSpaceObject); query.setParameter("ePerson", eperson); query.executeUpdate(); } @Override - public List findAllOrderedByEPerson(Context context) throws SQLException { - + 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"; + if (resourceType != null) { + hqlQuery = String.format(hqlQuery, resourceType); + } + Query query = createQuery(context, hqlQuery); + if (limit != -1) { + query.setMaxResults(limit); + } + if (offset != -1) { + query.setFirstResult(offset); + } + query.setHint("org.hibernate.cacheable", false); + return query.getResultList(); + } + @Override + public List findAllOrderedByDSO(Context context, Integer limit, Integer offset) throws SQLException { CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Subscription.class); Root subscriptionRoot = criteriaQuery.from(Subscription.class); criteriaQuery.select(subscriptionRoot); + List orderList = new LinkedList<>(); + orderList.add(criteriaBuilder.asc(subscriptionRoot.get(Subscription_.dSpaceObject))); + criteriaQuery.orderBy(orderList); + return list(context, criteriaQuery, false, Subscription.class, limit, offset); + } + @Override + public List findAllSubscriptionsBySubscriptionTypeAndFrequency(Context context, + String subscriptionType, String frequencyValue) throws SQLException { + CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); + CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Subscription.class); + Root subscriptionRoot = criteriaQuery.from(Subscription.class); + criteriaQuery.select(subscriptionRoot); + Join childJoin = subscriptionRoot.join("subscriptionParameterList"); + criteriaQuery.where( + criteriaBuilder.and( + criteriaBuilder.equal(subscriptionRoot.get(Subscription_.SUBSCRIPTION_TYPE), subscriptionType), + criteriaBuilder.equal(childJoin.get(SubscriptionParameter_.name), "frequency"), + criteriaBuilder.equal(childJoin.get(SubscriptionParameter_.value), frequencyValue) + )); List orderList = new ArrayList<>(1); orderList.add(criteriaBuilder.asc(subscriptionRoot.get(Subscription_.ePerson))); + orderList.add(criteriaBuilder.asc(subscriptionRoot.get(Subscription_.id))); criteriaQuery.orderBy(orderList); - - return list(context, criteriaQuery, false, Subscription.class, -1, -1); + return list(context, criteriaQuery, false, Subscription.class, 10000, -1); } + + @Override + public Long countAll(Context context) throws SQLException { + CriteriaBuilder qb = getCriteriaBuilder(context); + CriteriaQuery cq = qb.createQuery(Long.class); + cq.select(qb.count(cq.from(Subscription.class))); + Query query = this.getHibernateSession(context).createQuery(cq); + return (Long) query.getSingleResult(); + } + + @Override + public Long countAllByEPerson(Context context, EPerson ePerson) throws SQLException { + CriteriaBuilder qb = getCriteriaBuilder(context); + CriteriaQuery cq = qb.createQuery(Long.class); + Root subscriptionRoot = cq.from(Subscription.class); + cq.select(qb.count(subscriptionRoot)); + cq.where(qb.equal(subscriptionRoot.get(Subscription_.ePerson), ePerson)); + Query query = this.getHibernateSession(context).createQuery(cq); + return (Long) query.getSingleResult(); + } + + @Override + public Long countAllByEPersonAndDso(Context context, + EPerson ePerson, DSpaceObject dSpaceObject) throws SQLException { + CriteriaBuilder qb = getCriteriaBuilder(context); + CriteriaQuery cq = qb.createQuery(Long.class); + Root subscriptionRoot = cq.from(Subscription.class); + cq.select(qb.count(subscriptionRoot)); + cq.where(qb.and(qb.equal(subscriptionRoot.get(Subscription_.ePerson) + , ePerson), qb.equal(subscriptionRoot.get(Subscription_.dSpaceObject), dSpaceObject))); + Query query = this.getHibernateSession(context).createQuery(cq); + return (Long) query.getSingleResult(); + } + } \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/eperson/dao/impl/SubscriptionParameterDAOImpl.java b/dspace-api/src/main/java/org/dspace/eperson/dao/impl/SubscriptionParameterDAOImpl.java new file mode 100644 index 0000000000..37af787ed3 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/eperson/dao/impl/SubscriptionParameterDAOImpl.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.eperson.dao.impl; + +import org.dspace.core.AbstractHibernateDAO; +import org.dspace.eperson.SubscriptionParameter; +import org.dspace.eperson.dao.SubscriptionParameterDAO; + +/** + * Hibernate implementation of the Database Access Object interface class for the SubscriptionParameter object. + * This class is responsible for all database calls for the SubscriptionParameter object and is autowired by spring + * This class should never be accessed directly. + * + * @author Alba Aliu at atis.al + */ +public class SubscriptionParameterDAOImpl extends AbstractHibernateDAO + implements SubscriptionParameterDAO { + + protected SubscriptionParameterDAOImpl() { + super(); + } + +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/eperson/factory/EPersonServiceFactory.java b/dspace-api/src/main/java/org/dspace/eperson/factory/EPersonServiceFactory.java index f7ce13a8a3..b80c37f13f 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/factory/EPersonServiceFactory.java +++ b/dspace-api/src/main/java/org/dspace/eperson/factory/EPersonServiceFactory.java @@ -12,7 +12,6 @@ import org.dspace.eperson.service.EPersonService; import org.dspace.eperson.service.GroupService; import org.dspace.eperson.service.RegistrationDataService; import org.dspace.eperson.service.SubscribeService; -import org.dspace.eperson.service.SupervisorService; import org.dspace.services.factory.DSpaceServicesFactory; /** @@ -33,8 +32,6 @@ public abstract class EPersonServiceFactory { public abstract SubscribeService getSubscribeService(); - public abstract SupervisorService getSupervisorService(); - public static EPersonServiceFactory getInstance() { return DSpaceServicesFactory.getInstance().getServiceManager() .getServiceByName("ePersonServiceFactory", EPersonServiceFactory.class); diff --git a/dspace-api/src/main/java/org/dspace/eperson/factory/EPersonServiceFactoryImpl.java b/dspace-api/src/main/java/org/dspace/eperson/factory/EPersonServiceFactoryImpl.java index 33d9249b6b..c4a6cbe996 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/factory/EPersonServiceFactoryImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/factory/EPersonServiceFactoryImpl.java @@ -12,7 +12,6 @@ import org.dspace.eperson.service.EPersonService; import org.dspace.eperson.service.GroupService; import org.dspace.eperson.service.RegistrationDataService; import org.dspace.eperson.service.SubscribeService; -import org.dspace.eperson.service.SupervisorService; import org.springframework.beans.factory.annotation.Autowired; /** @@ -33,8 +32,6 @@ public class EPersonServiceFactoryImpl extends EPersonServiceFactory { private AccountService accountService; @Autowired(required = true) private SubscribeService subscribeService; - @Autowired(required = true) - private SupervisorService supervisorService; @Override public EPersonService getEPersonService() { @@ -61,8 +58,4 @@ public class EPersonServiceFactoryImpl extends EPersonServiceFactory { return subscribeService; } - @Override - public SupervisorService getSupervisorService() { - return supervisorService; - } } diff --git a/dspace-api/src/main/java/org/dspace/eperson/service/SubscribeService.java b/dspace-api/src/main/java/org/dspace/eperson/service/SubscribeService.java index 347c69bf5b..e70f40e0ed 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/service/SubscribeService.java +++ b/dspace-api/src/main/java/org/dspace/eperson/service/SubscribeService.java @@ -12,9 +12,11 @@ import java.util.List; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Collection; +import org.dspace.content.DSpaceObject; import org.dspace.core.Context; import org.dspace.eperson.EPerson; import org.dspace.eperson.Subscription; +import org.dspace.eperson.SubscriptionParameter; /** * Service interface class for the Subscription object. @@ -31,49 +33,74 @@ public interface SubscribeService { * new item appears in the collection. * * @param context DSpace context + * @param limit Number of subscriptions to return + * @param offset Offset number * @return list of Subscription objects * @throws SQLException An exception that provides information on a database access error or other errors. */ - public List findAll(Context context) throws SQLException; + public List findAll(Context context, String resourceType, Integer limit, Integer offset) + throws Exception; /** - * Subscribe an e-person to a collection. An e-mail will be sent every day a - * new item appears in the collection. - * - * @param context DSpace context - * @param eperson EPerson to subscribe - * @param collection Collection to subscribe to - * @throws SQLException An exception that provides information on a database access error or other errors. - * @throws AuthorizeException Exception indicating the current user of the context does not have permission - * to perform a particular action. + * Subscribe an EPerson to a dSpaceObject (Collection or Community). An e-mail will be sent every day a + * new item appears in the Collection or Community. + * + * @param context DSpace context object + * @param eperson EPerson to subscribe + * @param dSpaceObject DSpaceObject to subscribe + * @param subscriptionParameters list of @SubscriptionParameter + * @param subscriptionType Currently supported only "content" + * @return + * @throws SQLException An exception that provides information on a database access error or other errors. + * @throws AuthorizeException Exception indicating the current user of the context does not have permission + * to perform a particular action. */ - public void subscribe(Context context, EPerson eperson, - Collection collection) throws SQLException, AuthorizeException; + public Subscription subscribe(Context context, EPerson eperson, DSpaceObject dSpaceObject, + List subscriptionParameters, + String subscriptionType) throws SQLException, AuthorizeException; /** * Unsubscribe an e-person to a collection. Passing in null * for the collection unsubscribes the e-person from all collections they * are subscribed to. * - * @param context DSpace context - * @param eperson EPerson to unsubscribe - * @param collection Collection to unsubscribe from + * @param context DSpace context + * @param eperson EPerson to unsubscribe + * @param dSpaceObject DSpaceObject to unsubscribe from * @throws SQLException An exception that provides information on a database access error or other errors. * @throws AuthorizeException Exception indicating the current user of the context does not have permission * to perform a particular action. */ - public void unsubscribe(Context context, EPerson eperson, - Collection collection) throws SQLException, AuthorizeException; + public void unsubscribe(Context context, EPerson eperson, DSpaceObject dSpaceObject) + throws SQLException, AuthorizeException; /** * Find out which collections an e-person is subscribed to * * @param context DSpace context * @param eperson EPerson + * @param limit Number of subscriptions to return + * @param offset Offset number * @return array of collections e-person is subscribed to * @throws SQLException An exception that provides information on a database access error or other errors. */ - public List getSubscriptions(Context context, EPerson eperson) throws SQLException; + public List findSubscriptionsByEPerson(Context context, EPerson eperson, Integer limit,Integer offset) + throws SQLException; + + /** + * Find out which collections an e-person is subscribed to and related with dso + * + * @param context DSpace context + * @param eperson EPerson + * @param dSpaceObject DSpaceObject + * @param limit Number of subscriptions to return + * @param offset Offset number + * @return array of collections e-person is subscribed to and related with dso + * @throws SQLException An exception that provides information on a database access error or other errors. + */ + public List findSubscriptionsByEPersonAndDso(Context context, EPerson eperson, + DSpaceObject dSpaceObject, + Integer limit, Integer offset) throws SQLException; /** * Find out which collections the currently logged in e-person can subscribe to @@ -82,8 +109,7 @@ public interface SubscribeService { * @return array of collections the currently logged in e-person can subscribe to * @throws SQLException An exception that provides information on a database access error or other errors. */ - public List getAvailableSubscriptions(Context context) - throws SQLException; + public List findAvailableSubscriptions(Context context) throws SQLException; /** * Find out which collections an e-person can subscribe to @@ -93,29 +119,27 @@ public interface SubscribeService { * @return array of collections e-person can subscribe to * @throws SQLException An exception that provides information on a database access error or other errors. */ - public List getAvailableSubscriptions(Context context, EPerson eperson) - throws SQLException; + public List findAvailableSubscriptions(Context context, EPerson eperson) throws SQLException; /** * Is that e-person subscribed to that collection? * - * @param context DSpace context - * @param eperson find out if this e-person is subscribed - * @param collection find out if subscribed to this collection + * @param context DSpace context + * @param eperson find out if this e-person is subscribed + * @param dSpaceObject find out if subscribed to this dSpaceObject * @return true if they are subscribed * @throws SQLException An exception that provides information on a database access error or other errors. */ - public boolean isSubscribed(Context context, EPerson eperson, - Collection collection) throws SQLException; + public boolean isSubscribed(Context context, EPerson eperson, DSpaceObject dSpaceObject) throws SQLException; /** * Delete subscription by collection. * - * @param context DSpace context - * @param collection find out if subscribed to this collection + * @param context DSpace context + * @param dSpaceObject find out if subscribed to this dSpaceObject * @throws SQLException An exception that provides information on a database access error or other errors. */ - public void deleteByCollection(Context context, Collection collection) throws SQLException; + public void deleteByDspaceObject(Context context, DSpaceObject dSpaceObject) throws SQLException; /** * Delete subscription by eperson (subscriber). @@ -125,4 +149,92 @@ public interface SubscribeService { * @throws SQLException An exception that provides information on a database access error or other errors. */ public void deleteByEPerson(Context context, EPerson ePerson) throws SQLException; -} + + /** + * Finds a subscription by id + * + * @param context DSpace context + * @param id the id of subscription to be searched + * @throws SQLException An exception that provides information on a database access error or other errors. + */ + public Subscription findById(Context context, int id) throws SQLException; + + /** + * Updates a subscription by id + * + * @param context DSpace context + * @param id Integer id + * @param subscriptionParameterList List subscriptionParameterList + * @param subscriptionType type + * @throws SQLException An exception that provides information on a database access error or other errors. + */ + public Subscription updateSubscription(Context context, Integer id, String subscriptionType, + List subscriptionParameterList) throws SQLException; + + /** + * Adds a parameter to a subscription + * + * @param context DSpace context + * @param id Integer id + * @param subscriptionParameter SubscriptionParameter subscriptionParameter + * @throws SQLException An exception that provides information on a database access error or other errors. + */ + public Subscription addSubscriptionParameter(Context context,Integer id, + SubscriptionParameter subscriptionParameter) throws SQLException; + + /** + * Deletes a parameter from subscription + * + * @param context DSpace context + * @param id Integer id + * @param subscriptionParam SubscriptionParameter subscriptionParameter + * @throws SQLException An exception that provides information on a database access error or other errors. + */ + public Subscription removeSubscriptionParameter(Context context, Integer id, + SubscriptionParameter subscriptionParam) throws SQLException; + + /** + * Deletes a subscription + * + * @param context DSpace context + * @param subscription The subscription to delete + * @throws SQLException An exception that provides information on a database access error or other errors. + */ + public void deleteSubscription(Context context, Subscription subscription) throws SQLException; + + /** + * Finds all subscriptions by subscriptionType and frequency + * + * @param context DSpace context + * @param subscriptionType Could be "content" or "statistics". NOTE: in DSpace we have only "content" + * @param frequencyValue Could be "D" stand for Day, "W" stand for Week, and "M" stand for Month + * @throws SQLException An exception that provides information on a database access error or other errors. + */ + public List findAllSubscriptionsBySubscriptionTypeAndFrequency(Context context, + String subscriptionType, String frequencyValue) throws SQLException; + + /** + * Counts all subscriptions + * + * @param context DSpace context + */ + public Long countAll(Context context) throws SQLException; + + /** + * Counts all subscriptions by ePerson + * + * @param context DSpace context + * @param ePerson EPerson ePerson + */ + public Long countSubscriptionsByEPerson(Context context, EPerson ePerson) throws SQLException; + + /** + * Counts all subscriptions by ePerson and DSO + * + * @param context DSpace context + * @param ePerson EPerson ePerson + * @param dSpaceObject DSpaceObject dSpaceObject + */ + public Long countByEPersonAndDSO(Context context, EPerson ePerson, DSpaceObject dSpaceObject) throws SQLException; + +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/eperson/service/SupervisorService.java b/dspace-api/src/main/java/org/dspace/eperson/service/SupervisorService.java deleted file mode 100644 index 470c9133e5..0000000000 --- a/dspace-api/src/main/java/org/dspace/eperson/service/SupervisorService.java +++ /dev/null @@ -1,83 +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.eperson.service; - -import java.sql.SQLException; - -import org.dspace.authorize.AuthorizeException; -import org.dspace.content.WorkspaceItem; -import org.dspace.core.Context; -import org.dspace.eperson.Group; - -/** - * Class to represent the supervisor, primarily for use in applying supervisor - * activities to the database, such as setting and unsetting supervision - * orders and so forth. - * - * @author Richard Jones - * @version $Revision$ - */ -public interface SupervisorService { - - /** - * value to use for no policy set - */ - public static final int POLICY_NONE = 0; - - /** - * value to use for editor policies - */ - public static final int POLICY_EDITOR = 1; - - /** - * value to use for observer policies - */ - public static final int POLICY_OBSERVER = 2; - - /** - * finds out if there is a supervision order that matches this set - * of values - * - * @param context the context this object exists in - * @param workspaceItem the workspace item to be supervised - * @param group the group to be doing the supervising - * @return boolean true if there is an order that matches, false if not - * @throws SQLException An exception that provides information on a database access error or other errors. - */ - public boolean isOrder(Context context, WorkspaceItem workspaceItem, Group group) - throws SQLException; - - /** - * removes the requested group from the requested workspace item in terms - * of supervision. This also removes all the policies that group has - * associated with the item - * - * @param context the context this object exists in - * @param workspaceItem the ID of the workspace item - * @param group the ID of the group to be removed from the item - * @throws SQLException An exception that provides information on a database access error or other errors. - * @throws AuthorizeException Exception indicating the current user of the context does not have permission - * to perform a particular action. - */ - public void remove(Context context, WorkspaceItem workspaceItem, Group group) - throws SQLException, AuthorizeException; - - /** - * adds a supervision order to the database - * - * @param context the context this object exists in - * @param group the ID of the group which will supervise - * @param workspaceItem the ID of the workspace item to be supervised - * @param policy String containing the policy type to be used - * @throws SQLException An exception that provides information on a database access error or other errors. - * @throws AuthorizeException Exception indicating the current user of the context does not have permission - * to perform a particular action. - */ - public void add(Context context, Group group, WorkspaceItem workspaceItem, int policy) - throws SQLException, AuthorizeException; -} diff --git a/dspace-api/src/main/java/org/dspace/event/Consumer.java b/dspace-api/src/main/java/org/dspace/event/Consumer.java index 1a8b16e98a..f56efcc7ba 100644 --- a/dspace-api/src/main/java/org/dspace/event/Consumer.java +++ b/dspace-api/src/main/java/org/dspace/event/Consumer.java @@ -10,18 +10,16 @@ package org.dspace.event; import org.dspace.core.Context; /** - * Interface for content event consumers. Note that the consumer cannot tell if - * it is invoked synchronously or asynchronously; the consumer interface and - * sequence of calls is the same for both. Asynchronous consumers may see more - * consume() calls between the start and end of the event stream, if they are - * invoked asynchronously, once in a long time period, rather than synchronously - * after every Context.commit(). - * - * @version $Revision$ + * Interface for content event consumers. Note that the consumer cannot tell + * if it is invoked synchronously or asynchronously; the consumer interface + * and sequence of calls is the same for both. Asynchronous consumers may see + * more consume() calls between the start and end of the event stream, if they + * are invoked asynchronously, once in a long time period, rather than + * synchronously after every Context.commit(). */ public interface Consumer { /** - * Initialize - allocate any resources required to operate. This may include + * Allocate any resources required to operate. This may include * initializing any pooled JMS resources. Called ONCE when created by the * dispatcher pool. This should be used to set up expensive resources that * will remain for the lifetime of the consumer. @@ -31,12 +29,17 @@ public interface Consumer { public void initialize() throws Exception; /** - * Consume an event; events may get filtered at the dispatcher level, hiding - * it from the consumer. This behavior is based on the dispatcher/consumer - * configuration. Should include logic to initialize any resources required - * for a batch of events. + * Consume an event. Events may be filtered by a dispatcher, hiding them + * from the consumer. This behavior is based on the dispatcher/consumer + * configuration. Should include logic to initialize any resources + * required for a batch of events. * - * @param ctx the execution context object + *

This method must not commit the context. Committing causes + * re-dispatch of the event queue, which can result in infinite recursion + * leading to memory exhaustion as seen in + * {@link https://github.com/DSpace/DSpace/pull/8756}. + * + * @param ctx the current DSpace session * @param event the content event * @throws Exception if error */ diff --git a/dspace-api/src/main/java/org/dspace/event/package-info.java b/dspace-api/src/main/java/org/dspace/event/package-info.java new file mode 100644 index 0000000000..544dfb271a --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/event/package-info.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/ + */ +/** + * Actions which alter DSpace model objects can queue {@link Event}s, which + * are presented to {@link Consumer}s by a {@link Dispatcher}. A pool of + * {@code Dispatcher}s is managed by an {@link service.EventService}, guided + * by configuration properties {@code event.dispatcher.*}. + * + *

One must be careful not to commit the current DSpace {@code Context} + * during event dispatch. {@code commit()} triggers event dispatching, and + * doing this during event dispatch can lead to infinite recursion and + * memory exhaustion. + */ + +package org.dspace.event; 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 5e45d6324d..b0aa4aba13 100644 --- a/dspace-api/src/main/java/org/dspace/external/OpenAIRERestConnector.java +++ b/dspace-api/src/main/java/org/dspace/external/OpenAIRERestConnector.java @@ -335,7 +335,7 @@ public class OpenAIRERestConnector { /** * tokenUsage true to enable the usage of an access token * - * @param tokenUsage + * @param tokenEnabled true/false */ @Autowired(required = false) public void setTokenEnabled(boolean tokenEnabled) { diff --git a/dspace-api/src/main/java/org/dspace/external/provider/impl/LiveImportDataProvider.java b/dspace-api/src/main/java/org/dspace/external/provider/impl/LiveImportDataProvider.java index 962183fa6f..2e934462c9 100644 --- a/dspace-api/src/main/java/org/dspace/external/provider/impl/LiveImportDataProvider.java +++ b/dspace-api/src/main/java/org/dspace/external/provider/impl/LiveImportDataProvider.java @@ -57,7 +57,7 @@ public class LiveImportDataProvider extends AbstractExternalDataProvider { /** * This method set the MetadataSource for the ExternalDataProvider - * @param metadataSource {@link org.dspace.importer.external.service.components.MetadataSource} implementation used to process the input data + * @param querySource Source {@link org.dspace.importer.external.service.components.QuerySource} implementation used to process the input data */ public void setMetadataSource(QuerySource querySource) { this.querySource = querySource; 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 0efaea75e0..aa730fe2b1 100644 --- a/dspace-api/src/main/java/org/dspace/handle/HandleServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/handle/HandleServiceImpl.java @@ -9,6 +9,7 @@ package org.dspace.handle; import java.sql.SQLException; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -211,17 +212,17 @@ public class HandleServiceImpl implements HandleService { @Override public void unbindHandle(Context context, DSpaceObject dso) throws SQLException { - List handles = getInternalHandles(context, dso); - if (CollectionUtils.isNotEmpty(handles)) { - for (Handle handle : handles) { + Iterator handles = dso.getHandles().iterator(); + if (handles.hasNext()) { + while (handles.hasNext()) { + final Handle handle = handles.next(); + handles.remove(); //Only set the "resouce_id" column to null when unbinding a handle. // We want to keep around the "resource_type_id" value, so that we // can verify during a restore whether the same *type* of resource // is reusing this handle! handle.setDSpaceObject(null); - //Also remove the handle from the DSO list to keep a consistent model - dso.getHandles().remove(handle); handleDAO.save(context, handle); @@ -256,7 +257,7 @@ public class HandleServiceImpl implements HandleService { @Override public String findHandle(Context context, DSpaceObject dso) throws SQLException { - List handles = getInternalHandles(context, dso); + List handles = dso.getHandles(); if (CollectionUtils.isEmpty(handles)) { return null; } else { @@ -328,20 +329,6 @@ public class HandleServiceImpl implements HandleService { //////////////////////////////////////// // Internal methods //////////////////////////////////////// - - /** - * Return the handle for an Object, or null if the Object has no handle. - * - * @param context DSpace context - * @param dso DSpaceObject for which we require our handles - * @return The handle for object, or null if the object has no handle. - * @throws SQLException If a database error occurs - */ - protected List getInternalHandles(Context context, DSpaceObject dso) - throws SQLException { - return handleDAO.getHandlesByDSpaceObject(context, dso); - } - /** * Find the database row corresponding to handle. * 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 5a6b26da93..5aeb40bdd9 100644 --- a/dspace-api/src/main/java/org/dspace/harvest/OAIHarvester.java +++ b/dspace-api/src/main/java/org/dspace/harvest/OAIHarvester.java @@ -455,6 +455,7 @@ public class OAIHarvester { harvestRow.setHarvestStartTime(startTime); harvestRow.setHarvestMessage("Harvest from " + oaiSource + " successful"); harvestRow.setHarvestStatus(HarvestedCollection.STATUS_READY); + harvestRow.setLastHarvested(startTime); log.info( "Harvest from " + oaiSource + " successful. The process took " + timeTaken + " milliseconds. Harvested " + currentRecord + " items."); @@ -567,11 +568,7 @@ public class OAIHarvester { // Import the actual bitstreams if (harvestRow.getHarvestType() == 3) { log.info("Running ORE ingest on: " + item.getHandle()); - - List allBundles = item.getBundles(); - for (Bundle bundle : allBundles) { - itemService.removeBundle(ourContext, item, bundle); - } + itemService.removeAllBundles(ourContext, item); ORExwalk.ingest(ourContext, item, oreREM, true); } } else { 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 66e7b94a4b..b70eda960d 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/DOIIdentifierProvider.java +++ b/dspace-api/src/main/java/org/dspace/identifier/DOIIdentifierProvider.java @@ -21,6 +21,7 @@ import org.dspace.content.MetadataValue; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.logic.Filter; import org.dspace.content.logic.LogicalStatementException; +import org.dspace.content.logic.TrueFilter; import org.dspace.content.service.ItemService; import org.dspace.core.Constants; import org.dspace.core.Context; @@ -28,6 +29,7 @@ import org.dspace.identifier.doi.DOIConnector; import org.dspace.identifier.doi.DOIIdentifierException; import org.dspace.identifier.doi.DOIIdentifierNotApplicableException; import org.dspace.identifier.service.DOIService; +import org.dspace.services.factory.DSpaceServicesFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -44,6 +46,7 @@ import org.springframework.beans.factory.annotation.Autowired; *

Any identifier a method of this class returns is a string in the following format: doi:10.123/456.

* * @author Pascal-Nicolas Becker + * @author Kim Shepherd */ public class DOIIdentifierProvider extends FilteredIdentifierProvider { private static final Logger log = LoggerFactory.getLogger(DOIIdentifierProvider.class); @@ -71,16 +74,44 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { public static final String MD_SCHEMA = "dc"; public static final String DOI_ELEMENT = "identifier"; public static final String DOI_QUALIFIER = "uri"; - + // The DOI is queued for registered with the service provider public static final Integer TO_BE_REGISTERED = 1; + // The DOI is queued for reservation with the service provider public static final Integer TO_BE_RESERVED = 2; + // The DOI has been registered online public static final Integer IS_REGISTERED = 3; + // The DOI has been reserved online public static final Integer IS_RESERVED = 4; + // The DOI is reserved and requires an updated metadata record to be sent to the service provider public static final Integer UPDATE_RESERVED = 5; + // The DOI is registered and requires an updated metadata record to be sent to the service provider public static final Integer UPDATE_REGISTERED = 6; + // The DOI metadata record should be updated before performing online registration public static final Integer UPDATE_BEFORE_REGISTRATION = 7; + // The DOI will be deleted locally and marked as deleted in the DOI service provider public static final Integer TO_BE_DELETED = 8; + // The DOI has been deleted and is no longer associated with an item public static final Integer DELETED = 9; + // The DOI is created in the database and is waiting for either successful filter check on item install or + // manual intervention by an administrator to proceed to reservation or registration + public static final Integer PENDING = 10; + // The DOI is created in the database, but no more context is known + public static final Integer MINTED = 11; + + public static final String[] statusText = { + "UNKNOWN", // 0 + "TO_BE_REGISTERED", // 1 + "TO_BE_RESERVED", // 2 + "IS_REGISTERED", // 3 + "IS_RESERVED", // 4 + "UPDATE_RESERVED", // 5 + "UPDATE_REGISTERED", // 6 + "UPDATE_BEFORE_REGISTRATION", // 7 + "TO_BE_DELETED", // 8 + "DELETED", // 9 + "PENDING", // 10 + "MINTED", // 11 + }; @Autowired(required = true) protected DOIService doiService; @@ -89,8 +120,6 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { @Autowired(required = true) protected ItemService itemService; - protected Filter filterService; - /** * Empty / default constructor for Spring */ @@ -153,16 +182,6 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { this.connector = connector; } - /** - * Set the Filter to use when testing items to see if a DOI should be registered - * Spring will use this setter to set the filter from the configured property in identifier-services.xml - * @param filterService - an object implementing the org.dspace.content.logic.Filter interface - */ - @Override - public void setFilterService(Filter filterService) { - this.filterService = filterService; - } - /** * This identifier provider supports identifiers of type * {@link org.dspace.identifier.DOI}. @@ -206,7 +225,7 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { @Override public String register(Context context, DSpaceObject dso) throws IdentifierException { - return register(context, dso, false); + return register(context, dso, this.filter); } /** @@ -219,29 +238,29 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { @Override public void register(Context context, DSpaceObject dso, String identifier) throws IdentifierException { - register(context, dso, identifier, false); + register(context, dso, identifier, this.filter); } /** * Register a new DOI for a given DSpaceObject * @param context - DSpace context * @param dso - DSpaceObject identified by the new DOI - * @param skipFilter - boolean indicating whether to skip any filtering of items before performing registration + * @param filter - Logical item filter to determine whether this identifier should be registered * @throws IdentifierException */ @Override - public String register(Context context, DSpaceObject dso, boolean skipFilter) + public String register(Context context, DSpaceObject dso, Filter filter) throws IdentifierException { if (!(dso instanceof Item)) { // DOI are currently assigned only to Item return null; } - String doi = mint(context, dso, skipFilter); + String doi = mint(context, dso, filter); // register tries to reserve doi if it's not already. // So we don't have to reserve it here. - register(context, dso, doi, skipFilter); + register(context, dso, doi, filter); return doi; } @@ -250,11 +269,11 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { * @param context - DSpace context * @param dso - DSpaceObject identified by the new DOI * @param identifier - String containing the DOI to register - * @param skipFilter - boolean indicating whether to skip any filtering of items before performing registration + * @param filter - Logical item filter to determine whether this identifier should be registered * @throws IdentifierException */ @Override - public void register(Context context, DSpaceObject dso, String identifier, boolean skipFilter) + public void register(Context context, DSpaceObject dso, String identifier, Filter filter) throws IdentifierException { if (!(dso instanceof Item)) { // DOI are currently assigned only to Item @@ -265,7 +284,7 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { // search DOI in our db try { - doiRow = loadOrCreateDOI(context, dso, doi, skipFilter); + 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); @@ -277,7 +296,6 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { + "is marked as DELETED.", DOIIdentifierException.DOI_IS_DELETED); } - // Check status of DOI if (IS_REGISTERED.equals(doiRow.getStatus())) { return; } @@ -290,6 +308,7 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { log.warn("SQLException while changing status of DOI {} to be registered.", doi); throw new RuntimeException(sqle); } + } /** @@ -309,7 +328,7 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { @Override public void reserve(Context context, DSpaceObject dso, String identifier) throws IdentifierException, IllegalArgumentException { - reserve(context, dso, identifier, false); + reserve(context, dso, identifier, this.filter); } /** @@ -317,20 +336,18 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { * @param context - DSpace context * @param dso - DSpaceObject identified by this DOI * @param identifier - String containing the DOI to reserve - * @param skipFilter - boolean indicating whether to skip any filtering of items before performing reservation + * @param filter - Logical item filter to determine whether this identifier should be reserved * @throws IdentifierException * @throws IllegalArgumentException */ @Override - public void reserve(Context context, DSpaceObject dso, String identifier, boolean skipFilter) + public void reserve(Context context, DSpaceObject dso, String identifier, Filter filter) throws IdentifierException, IllegalArgumentException { String doi = doiService.formatIdentifier(identifier); DOI doiRow = null; try { - // if the doi is in our db already loadOrCreateDOI just returns. - // if it is not loadOrCreateDOI safes the doi. - doiRow = loadOrCreateDOI(context, dso, doi, skipFilter); + doiRow = loadOrCreateDOI(context, dso, doi, filter); } catch (SQLException sqle) { throw new RuntimeException(sqle); } @@ -359,7 +376,7 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { */ public void reserveOnline(Context context, DSpaceObject dso, String identifier) throws IdentifierException, IllegalArgumentException, SQLException { - reserveOnline(context, dso, identifier, false); + reserveOnline(context, dso, identifier, this.filter); } /** @@ -367,16 +384,16 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { * @param context - DSpace context * @param dso - DSpaceObject identified by this DOI * @param identifier - String containing the DOI to reserve - * @param skipFilter - skip the filters for {@link checkMintable(Context, DSpaceObject)} + * @param filter - Logical item filter to determine whether this identifier should be reserved online * @throws IdentifierException * @throws IllegalArgumentException * @throws SQLException */ - public void reserveOnline(Context context, DSpaceObject dso, String identifier, boolean skipFilter) + public void reserveOnline(Context context, DSpaceObject dso, String identifier, Filter filter) throws IdentifierException, IllegalArgumentException, SQLException { String doi = doiService.formatIdentifier(identifier); // get TableRow and ensure DOI belongs to dso regarding our db - DOI doiRow = loadOrCreateDOI(context, dso, doi, skipFilter); + DOI doiRow = loadOrCreateDOI(context, dso, doi, filter); if (DELETED.equals(doiRow.getStatus()) || TO_BE_DELETED.equals(doiRow.getStatus())) { throw new DOIIdentifierException("You tried to reserve a DOI that " @@ -402,7 +419,7 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { public void registerOnline(Context context, DSpaceObject dso, String identifier) throws IdentifierException, IllegalArgumentException, SQLException { - registerOnline(context, dso, identifier, false); + registerOnline(context, dso, identifier, this.filter); } @@ -411,18 +428,17 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { * @param context - DSpace context * @param dso - DSpaceObject identified by this DOI * @param identifier - String containing the DOI to register - * @param skipFilter - skip filters for {@link checkMintable(Context, DSpaceObject)} + * @param filter - Logical item filter to determine whether this identifier should be registered online * @throws IdentifierException * @throws IllegalArgumentException * @throws SQLException */ - public void registerOnline(Context context, DSpaceObject dso, String identifier, boolean skipFilter) + public void registerOnline(Context context, DSpaceObject dso, String identifier, Filter filter) throws IdentifierException, IllegalArgumentException, SQLException { - log.debug("registerOnline: skipFilter is " + skipFilter); String doi = doiService.formatIdentifier(identifier); // get TableRow and ensure DOI belongs to dso regarding our db - DOI doiRow = loadOrCreateDOI(context, dso, doi, skipFilter); + DOI doiRow = loadOrCreateDOI(context, dso, doi, filter); if (DELETED.equals(doiRow.getStatus()) || TO_BE_DELETED.equals(doiRow.getStatus())) { throw new DOIIdentifierException("You tried to register a DOI that " @@ -435,7 +451,7 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { } catch (DOIIdentifierException die) { // do we have to reserve DOI before we can register it? if (die.getCode() == DOIIdentifierException.RESERVE_FIRST) { - this.reserveOnline(context, dso, identifier, skipFilter); + this.reserveOnline(context, dso, identifier, filter); connector.registerDOI(context, dso, doi); } else { throw die; @@ -471,17 +487,23 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { throws IdentifierException, IllegalArgumentException, SQLException { String doi = doiService.formatIdentifier(identifier); - - boolean skipFilter = false; + // Use the default filter unless we find the object + Filter updateFilter = this.filter; 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: " + doiService.findDOIByDSpaceObject(context, dso).getDoi()); - skipFilter = true; + updateFilter = DSpaceServicesFactory.getInstance().getServiceManager().getServiceByName( + "always_true_filter", TrueFilter.class); } - DOI doiRow = loadOrCreateDOI(context, dso, doi, skipFilter); + 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); + return; + } if (DELETED.equals(doiRow.getStatus()) || TO_BE_DELETED.equals(doiRow.getStatus())) { throw new DOIIdentifierException("You tried to register a DOI that " @@ -571,19 +593,19 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { @Override public String mint(Context context, DSpaceObject dso) throws IdentifierException { - return mint(context, dso, false); + return mint(context, dso, this.filter); } /** * Mint a new DOI in DSpace - this is usually the first step of registration * @param context - DSpace context * @param dso - DSpaceObject identified by the new identifier - * @param skipFilter - boolean indicating whether to skip any filtering of items before minting. + * @param filter - Logical item filter to determine whether this identifier should be registered * @return a String containing the new identifier * @throws IdentifierException */ @Override - public String mint(Context context, DSpaceObject dso, boolean skipFilter) throws IdentifierException { + public String mint(Context context, DSpaceObject dso, Filter filter) throws IdentifierException { String doi = null; try { @@ -597,7 +619,7 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { } if (null == doi) { try { - DOI doiRow = loadOrCreateDOI(context, dso, null, skipFilter); + DOI doiRow = loadOrCreateDOI(context, dso, null, filter); doi = DOI.SCHEME + doiRow.getDoi(); } catch (SQLException e) { @@ -895,7 +917,7 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { */ protected DOI loadOrCreateDOI(Context context, DSpaceObject dso, String doiIdentifier) throws SQLException, DOIIdentifierException, IdentifierNotApplicableException { - return loadOrCreateDOI(context, dso, doiIdentifier, false); + return loadOrCreateDOI(context, dso, doiIdentifier, this.filter); } /** @@ -910,13 +932,13 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { * @param context - DSpace context * @param dso - DSpaceObject to identify * @param doiIdentifier - DOI to load or create (null to mint a new one) - * @param skipFilter - Whether or not to skip the filters for the checkMintable() check + * @param filter - Logical item filter to determine whether this identifier should be registered * @return * @throws SQLException * @throws DOIIdentifierException * @throws org.dspace.identifier.IdentifierNotApplicableException passed through. */ - protected DOI loadOrCreateDOI(Context context, DSpaceObject dso, String doiIdentifier, boolean skipFilter) + protected DOI loadOrCreateDOI(Context context, DSpaceObject dso, String doiIdentifier, Filter filter) throws SQLException, DOIIdentifierException, IdentifierNotApplicableException { DOI doi = null; @@ -954,6 +976,8 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { // doi is assigned to a DSO; is it assigned to our specific dso? // check if DOI already belongs to dso if (dso.getID().equals(doi.getDSpaceObject().getID())) { + // Before we return this, check the filter + checkMintable(context, filter, dso); return doi; } else { throw new DOIIdentifierException("Trying to create a DOI " + @@ -963,15 +987,8 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { } } - // we did not find the doi in the database or shall reassign it. Before doing so, we should check if a - // filter is in place to prevent the creation of new DOIs for certain items. - if (skipFilter) { - log.warn("loadOrCreateDOI: Skipping default item filter"); - } else { - // Find out if we're allowed to create a DOI - // throws an exception if creation of a new DOI is prohibited by a filter - checkMintable(context, dso); - } + // Check if this item is eligible for minting. An IdentifierNotApplicableException will be thrown if not. + checkMintable(context, filter, dso); // check prefix if (!doiIdentifier.startsWith(this.getPrefix() + "/")) { @@ -984,15 +1001,8 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { doi = doiService.create(context); } } else { - // We need to generate a new DOI. Before doing so, we should check if a - // filter is in place to prevent the creation of new DOIs for certain items. - if (skipFilter) { - log.warn("loadOrCreateDOI: Skipping default item filter"); - } else { - // Find out if we're allowed to create a DOI - // throws an exception if creation of a new DOI is prohibited by a filter - checkMintable(context, dso); - } + // Check if this item is eligible for minting. An IdentifierNotApplicableException will be thrown if not. + checkMintable(context, filter, dso); doi = doiService.create(context); doiIdentifier = this.getPrefix() + "/" + this.getNamespaceSeparator() + @@ -1002,7 +1012,7 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { // prepare new doiRow doi.setDoi(doiIdentifier); doi.setDSpaceObject(dso); - doi.setStatus(null); + doi.setStatus(MINTED); try { doiService.update(context, doi); } catch (SQLException e) { @@ -1102,20 +1112,32 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { /** * Checks to see if an item can have a DOI minted, using the configured logical filter * @param context + * @param filter Logical item filter to apply * @param dso The item to be evaluated * @throws DOIIdentifierNotApplicableException */ @Override - public void checkMintable(Context context, DSpaceObject dso) throws DOIIdentifierNotApplicableException { + public void checkMintable(Context context, Filter filter, DSpaceObject dso) + throws DOIIdentifierNotApplicableException { + if (filter == null) { + Filter trueFilter = DSpaceServicesFactory.getInstance().getServiceManager().getServiceByName( + "always_true_filter", TrueFilter.class); + // If a null filter was passed, and we have a good default filter to apply, apply it. + // Otherwise, set to TrueFilter which means "no filtering" + if (this.filter != null) { + filter = this.filter; + } else { + filter = trueFilter; + } + } // If the check fails, an exception will be thrown to be caught by the calling method - if (this.filterService != null && contentServiceFactory - .getDSpaceObjectService(dso).getTypeText(dso).equals("ITEM")) { + if (contentServiceFactory.getDSpaceObjectService(dso).getTypeText(dso).equals("ITEM")) { try { - boolean result = filterService.getResult(context, (Item) dso); + boolean result = filter.getResult(context, (Item) dso); log.debug("Result of filter for " + dso.getHandle() + " is " + result); if (!result) { throw new DOIIdentifierNotApplicableException("Item " + dso.getHandle() + - " was evaluated as 'false' by the item filter, not minting"); + " was evaluated as 'false' by the item filter, not minting"); } } catch (LogicalStatementException e) { log.error("Error evaluating item with logical filter: " + e.getLocalizedMessage()); @@ -1125,4 +1147,16 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { log.debug("DOI Identifier Provider: filterService is null (ie. don't prevent DOI minting)"); } } + + /** + * Checks to see if an item can have a DOI minted, using the configured logical filter + * @param context + * @param dso The item to be evaluated + * @throws DOIIdentifierNotApplicableException + */ + @Override + public void checkMintable(Context context, DSpaceObject dso) throws DOIIdentifierNotApplicableException { + checkMintable(context, this.filter, dso); + } + } \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/identifier/FilteredIdentifierProvider.java b/dspace-api/src/main/java/org/dspace/identifier/FilteredIdentifierProvider.java index e5f222ff29..c2254fa9a6 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/FilteredIdentifierProvider.java +++ b/dspace-api/src/main/java/org/dspace/identifier/FilteredIdentifierProvider.java @@ -12,8 +12,9 @@ import java.sql.SQLException; import org.dspace.content.DSpaceObject; import org.dspace.content.logic.Filter; +import org.dspace.content.logic.TrueFilter; import org.dspace.core.Context; -import org.springframework.beans.factory.annotation.Autowired; +import org.dspace.services.factory.DSpaceServicesFactory; /** * This abstract class adds extra method signatures so that implementing IdentifierProviders can @@ -24,26 +25,28 @@ import org.springframework.beans.factory.annotation.Autowired; */ public abstract class FilteredIdentifierProvider extends IdentifierProvider { - protected Filter filterService; + protected Filter filter = DSpaceServicesFactory.getInstance() + .getServiceManager().getServiceByName("always_true_filter", TrueFilter.class); /** - * Setter for spring to set the filter service from the property in configuration XML - * @param filterService - an object implementing the org.dspace.content.logic.Filter interface + * Setter for spring to set the default filter from the property in configuration XML + * @param filter - an object implementing the org.dspace.content.logic.Filter interface */ - @Autowired - public void setFilterService(Filter filterService) { - this.filterService = filterService; + public void setFilter(Filter filter) { + if (filter != null) { + this.filter = filter; + } } /** * Register a new identifier for a given DSpaceObject * @param context - DSpace context * @param dso - DSpaceObject to use for identifier registration - * @param skipFilter - boolean indicating whether to skip any filtering of items before performing registration + * @param filter - Logical item filter to determine whether this identifier should be registered * @return identifier * @throws IdentifierException */ - public abstract String register(Context context, DSpaceObject dso, boolean skipFilter) + public abstract String register(Context context, DSpaceObject dso, Filter filter) throws IdentifierException; /** @@ -51,10 +54,10 @@ public abstract class FilteredIdentifierProvider extends IdentifierProvider { * @param context - DSpace context * @param dso - DSpaceObject identified by the new identifier * @param identifier - String containing the identifier to register - * @param skipFilter - boolean indicating whether to skip any filtering of items before performing registration + * @param filter - Logical item filter to determine whether this identifier should be registered * @throws IdentifierException */ - public abstract void register(Context context, DSpaceObject dso, String identifier, boolean skipFilter) + public abstract void register(Context context, DSpaceObject dso, String identifier, Filter filter) throws IdentifierException; /** @@ -62,23 +65,23 @@ public abstract class FilteredIdentifierProvider extends IdentifierProvider { * @param context - DSpace context * @param dso - DSpaceObject identified by this identifier * @param identifier - String containing the identifier to reserve - * @param skipFilter - boolean indicating whether to skip any filtering of items before performing reservation + * @param filter - Logical item filter to determine whether this identifier should be reserved * @throws IdentifierException * @throws IllegalArgumentException * @throws SQLException */ - public abstract void reserve(Context context, DSpaceObject dso, String identifier, boolean skipFilter) + public abstract void reserve(Context context, DSpaceObject dso, String identifier, Filter filter) throws IdentifierException, IllegalArgumentException, SQLException; /** * Mint a new identifier in DSpace - this is usually the first step of registration * @param context - DSpace context * @param dso - DSpaceObject identified by the new identifier - * @param skipFilter - boolean indicating whether to skip any filtering of items before minting. + * @param filter - Logical item filter to determine whether this identifier should be registered * @return a String containing the new identifier * @throws IdentifierException */ - public abstract String mint(Context context, DSpaceObject dso, boolean skipFilter) throws IdentifierException; + public abstract String mint(Context context, DSpaceObject dso, Filter filter) throws IdentifierException; /** * Check configured item filters to see if this identifier is allowed to be minted @@ -88,5 +91,13 @@ public abstract class FilteredIdentifierProvider extends IdentifierProvider { */ public abstract void checkMintable(Context context, DSpaceObject dso) throws IdentifierException; + /** + * Check configured item filters to see if this identifier is allowed to be minted + * @param context - DSpace context + * @param filter - Logical item filter + * @param dso - DSpaceObject to be inspected + * @throws IdentifierException + */ + public abstract void checkMintable(Context context, Filter filter, DSpaceObject dso) throws IdentifierException; } diff --git a/dspace-api/src/main/java/org/dspace/identifier/IdentifierServiceImpl.java b/dspace-api/src/main/java/org/dspace/identifier/IdentifierServiceImpl.java index d0b6e4417e..b98aea24fa 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/IdentifierServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/identifier/IdentifierServiceImpl.java @@ -10,6 +10,7 @@ package org.dspace.identifier; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; +import java.util.Map; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; @@ -17,6 +18,7 @@ import org.apache.logging.log4j.Logger; import org.dspace.authorize.AuthorizeException; import org.dspace.content.DSpaceObject; import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.content.logic.Filter; import org.dspace.core.Context; import org.dspace.handle.service.HandleService; import org.dspace.identifier.service.IdentifierService; @@ -44,7 +46,6 @@ public class IdentifierServiceImpl implements IdentifierService { protected HandleService handleService; protected IdentifierServiceImpl() { - } @Autowired(required = true) @@ -98,7 +99,7 @@ public class IdentifierServiceImpl implements IdentifierService { @Override public void register(Context context, DSpaceObject dso) - throws AuthorizeException, SQLException, IdentifierException { + throws AuthorizeException, SQLException, IdentifierException { //We need to commit our context because one of the providers might require the handle created above // Next resolve all other services for (IdentifierProvider service : providers) { @@ -112,11 +113,99 @@ public class IdentifierServiceImpl implements IdentifierService { contentServiceFactory.getDSpaceObjectService(dso).update(context, dso); } + @Override + public void register(Context context, DSpaceObject dso, Class type, Filter filter) + throws AuthorizeException, SQLException, IdentifierException { + boolean registered = false; + // Iterate all services and register identifiers as appropriate + for (IdentifierProvider service : providers) { + if (service.supports(type)) { + try { + if (service instanceof FilteredIdentifierProvider) { + FilteredIdentifierProvider filteredService = (FilteredIdentifierProvider)service; + filteredService.register(context, dso, filter); + } else { + service.register(context, dso); + } + registered = true; + } catch (IdentifierNotApplicableException e) { + log.warn("Identifier not registered (inapplicable): " + e.getMessage()); + } + } + } + if (!registered) { + throw new IdentifierException("Cannot register identifier: Didn't " + + "find a provider that supports this identifier."); + } + // Update our item / collection / community + contentServiceFactory.getDSpaceObjectService(dso).update(context, dso); + } + + @Override + public void register(Context context, DSpaceObject dso, Class type) + throws AuthorizeException, SQLException, IdentifierException { + boolean registered = false; + // Iterate all services and register identifiers as appropriate + for (IdentifierProvider service : providers) { + if (service.supports(type)) { + try { + service.register(context, dso); + registered = true; + } catch (IdentifierNotApplicableException e) { + log.warn("Identifier not registered (inapplicable): " + e.getMessage()); + } + } + } + if (!registered) { + throw new IdentifierException("Cannot register identifier: Didn't " + + "find a provider that supports this identifier."); + } + // Update our item / collection / community + contentServiceFactory.getDSpaceObjectService(dso).update(context, dso); + } + + @Override + public void register(Context context, DSpaceObject dso, Map, Filter> typeFilters) + throws AuthorizeException, SQLException, IdentifierException { + // Iterate all services and register identifiers as appropriate + for (IdentifierProvider service : providers) { + try { + // If the service supports filtering, look through the map and the first supported class + // we find, set the filter and break. If no filter was seen for this type, just let the provider + // use its own implementation. + if (service instanceof FilteredIdentifierProvider) { + FilteredIdentifierProvider filteredService = (FilteredIdentifierProvider)service; + Filter filter = null; + for (Class type : typeFilters.keySet()) { + if (filteredService.supports(type)) { + filter = typeFilters.get(type); + break; + } + } + if (filter != null) { + // Pass the found filter to the provider + filteredService.register(context, dso, filter); + } else { + // Let the provider use the default filter / behaviour + filteredService.register(context, dso); + } + } else { + service.register(context, dso); + } + } catch (IdentifierNotApplicableException e) { + log.warn("Identifier not registered (inapplicable): " + e.getMessage()); + } + } + // Update our item / collection / community + contentServiceFactory.getDSpaceObjectService(dso).update(context, dso); + } + + + @Override public void register(Context context, DSpaceObject object, String identifier) throws AuthorizeException, SQLException, IdentifierException { - //We need to commit our context because one of the providers might require the handle created above - // Next resolve all other services + // Iterate all services and register identifiers as appropriate boolean registered = false; for (IdentifierProvider service : providers) { if (service.supports(identifier)) { @@ -132,7 +221,7 @@ public class IdentifierServiceImpl implements IdentifierService { throw new IdentifierException("Cannot register identifier: Didn't " + "find a provider that supports this identifier."); } - //Update our item / collection / community + // pdate our item / collection / community contentServiceFactory.getDSpaceObjectService(object).update(context, object); } 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 a864b4be4b..4ca186eaab 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/VersionedDOIIdentifierProvider.java +++ b/dspace-api/src/main/java/org/dspace/identifier/VersionedDOIIdentifierProvider.java @@ -18,6 +18,7 @@ import org.dspace.authorize.AuthorizeException; import org.dspace.content.DSpaceObject; import org.dspace.content.Item; import org.dspace.content.MetadataValue; +import org.dspace.content.logic.Filter; import org.dspace.core.Context; import org.dspace.identifier.doi.DOIConnector; import org.dspace.identifier.doi.DOIIdentifierException; @@ -49,7 +50,12 @@ public class VersionedDOIIdentifierProvider extends DOIIdentifierProvider { protected VersionHistoryService versionHistoryService; @Override - public String mint(Context context, DSpaceObject dso) + public String mint(Context context, DSpaceObject dso) throws IdentifierException { + return mint(context, dso, this.filter); + } + + @Override + public String mint(Context context, DSpaceObject dso, Filter filter) throws IdentifierException { if (!(dso instanceof Item)) { throw new IdentifierException("Currently only Items are supported for DOIs."); @@ -60,7 +66,7 @@ public class VersionedDOIIdentifierProvider extends DOIIdentifierProvider { try { history = versionHistoryService.findByItem(context, item); } catch (SQLException ex) { - throw new RuntimeException("A problem occured while accessing the database.", ex); + throw new RuntimeException("A problem occurred while accessing the database.", ex); } String doi = null; @@ -70,7 +76,7 @@ public class VersionedDOIIdentifierProvider extends DOIIdentifierProvider { return doi; } } catch (SQLException ex) { - log.error("Error while attemping to retrieve information about a DOI for " + log.error("Error while attempting to retrieve information about a DOI for " + contentServiceFactory.getDSpaceObjectService(dso).getTypeText(dso) + " with ID " + dso.getID() + ".", ex); throw new RuntimeException("Error while attempting to retrieve " @@ -79,6 +85,9 @@ public class VersionedDOIIdentifierProvider extends DOIIdentifierProvider { + " with ID " + dso.getID() + ".", ex); } + // Make a call to the filter here to throw an exception instead of carrying on with removal + creation + checkMintable(context, filter, dso); + // check whether we have a DOI in the metadata and if we have to remove it String metadataDOI = getDOIOutOfObject(dso); if (metadataDOI != null) { @@ -111,7 +120,7 @@ public class VersionedDOIIdentifierProvider extends DOIIdentifierProvider { // ensure DOI exists in our database as well and return. // this also checks that the doi is not assigned to another dso already. try { - loadOrCreateDOI(context, dso, versionedDOI); + loadOrCreateDOI(context, dso, versionedDOI, filter); } catch (SQLException ex) { log.error( "A problem with the database connection occurd while processing DOI " + versionedDOI + ".", ex); @@ -125,9 +134,9 @@ public class VersionedDOIIdentifierProvider extends DOIIdentifierProvider { if (history != null) { // versioning is currently supported for items only // if we have a history, we have a item - doi = makeIdentifierBasedOnHistory(context, dso, history); + doi = makeIdentifierBasedOnHistory(context, dso, history, filter); } else { - doi = loadOrCreateDOI(context, dso, null).getDoi(); + doi = loadOrCreateDOI(context, dso, null, filter).getDoi(); } } catch (SQLException ex) { log.error("SQLException while creating a new DOI: ", ex); @@ -136,11 +145,31 @@ public class VersionedDOIIdentifierProvider extends DOIIdentifierProvider { log.error("AuthorizationException while creating a new DOI: ", ex); throw new IdentifierException(ex); } + return doi.startsWith(DOI.SCHEME) ? doi : DOI.SCHEME + doi; + } + + @Override + public void register(Context context, DSpaceObject dso, String identifier) throws IdentifierException { + register(context, dso, identifier, this.filter); + } + + @Override + public String register(Context context, DSpaceObject dso, Filter filter) + throws IdentifierException { + if (!(dso instanceof Item)) { + // DOIs are currently assigned only to Items + return null; + } + + String doi = mint(context, dso, filter); + + register(context, dso, doi, filter); + return doi; } @Override - public void register(Context context, DSpaceObject dso, String identifier) + public void register(Context context, DSpaceObject dso, String identifier, Filter filter) throws IdentifierException { if (!(dso instanceof Item)) { throw new IdentifierException("Currently only Items are supported for DOIs."); @@ -148,7 +177,7 @@ public class VersionedDOIIdentifierProvider extends DOIIdentifierProvider { Item item = (Item) dso; if (StringUtils.isEmpty(identifier)) { - identifier = mint(context, dso); + identifier = mint(context, dso, filter); } String doiIdentifier = doiService.formatIdentifier(identifier); @@ -156,10 +185,10 @@ public class VersionedDOIIdentifierProvider extends DOIIdentifierProvider { // search DOI in our db try { - doi = loadOrCreateDOI(context, dso, doiIdentifier); + doi = loadOrCreateDOI(context, dso, doiIdentifier, filter); } catch (SQLException ex) { - log.error("Error in databse connection: " + ex.getMessage(), ex); - throw new RuntimeException("Error in database conncetion.", ex); + log.error("Error in database connection: " + ex.getMessage(), ex); + throw new RuntimeException("Error in database connection.", ex); } if (DELETED.equals(doi.getStatus()) || @@ -220,8 +249,14 @@ public class VersionedDOIIdentifierProvider extends DOIIdentifierProvider { return doiPostfix; } - // Should never return null! protected String makeIdentifierBasedOnHistory(Context context, DSpaceObject dso, VersionHistory history) + throws AuthorizeException, SQLException, DOIIdentifierException, IdentifierNotApplicableException { + return makeIdentifierBasedOnHistory(context, dso, history, this.filter); + } + + // Should never return null! + protected String makeIdentifierBasedOnHistory(Context context, DSpaceObject dso, VersionHistory history, + Filter filter) throws AuthorizeException, SQLException, DOIIdentifierException, IdentifierNotApplicableException { // Mint foreach new version an identifier like: 12345/100.versionNumber // use the bare handle (g.e. 12345/100) for the first version. @@ -244,6 +279,9 @@ public class VersionedDOIIdentifierProvider extends DOIIdentifierProvider { } if (previousVersionDOI == null) { + // Before continuing with any new DOI creation, apply the filter + checkMintable(context, filter, dso); + // We need to generate a new DOI. DOI doi = doiService.create(context); @@ -269,7 +307,7 @@ public class VersionedDOIIdentifierProvider extends DOIIdentifierProvider { String.valueOf(versionHistoryService.getVersion(context, history, item).getVersionNumber())); } - loadOrCreateDOI(context, dso, identifier); + loadOrCreateDOI(context, dso, identifier, filter); return identifier; } 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 7705fd2b57..0fac326ca1 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/VersionedHandleIdentifierProviderWithCanonicalHandles.java +++ b/dspace-api/src/main/java/org/dspace/identifier/VersionedHandleIdentifierProviderWithCanonicalHandles.java @@ -306,6 +306,7 @@ public class VersionedHandleIdentifierProviderWithCanonicalHandles extends Ident public DSpaceObject resolve(Context context, String identifier, String... attributes) { // We can do nothing with this, return null try { + identifier = handleService.parseHandle(identifier); return handleService.resolveToObject(context, identifier); } catch (IllegalStateException | SQLException e) { log.error(LogHelper.getHeader(context, "Error while resolving handle to item", "handle: " + identifier), @@ -426,6 +427,19 @@ public class VersionedHandleIdentifierProviderWithCanonicalHandles extends Ident } } + DSpaceObject itemWithCanonicalHandle = handleService.resolveToObject(context, canonical); + if (itemWithCanonicalHandle != null) { + if (itemWithCanonicalHandle.getID() != previous.getItem().getID()) { + log.warn("The previous version's item (" + previous.getItem().getID() + + ") does not match with the item containing handle " + canonical + + " (" + itemWithCanonicalHandle.getID() + ")"); + } + // Move the original handle from whatever item it's on to the newest version + handleService.modifyHandleDSpaceObject(context, canonical, dso); + } else { + handleService.createHandle(context, dso, canonical); + } + // add a new Identifier for this item: 12345/100.x String idNew = canonical + DOT + version.getVersionNumber(); //Make sure we don't have an old handle hanging around (if our previous version was deleted in the workspace) diff --git a/dspace-api/src/main/java/org/dspace/identifier/doi/DOIConsumer.java b/dspace-api/src/main/java/org/dspace/identifier/doi/DOIConsumer.java index 654d275d87..33ef058e16 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/doi/DOIConsumer.java +++ b/dspace-api/src/main/java/org/dspace/identifier/doi/DOIConsumer.java @@ -7,22 +7,32 @@ */ package org.dspace.identifier.doi; +import java.sql.SQLException; + import org.apache.logging.log4j.Logger; import org.dspace.content.DSpaceObject; import org.dspace.content.Item; import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.content.logic.Filter; +import org.dspace.content.logic.FilterUtils; import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.event.Consumer; import org.dspace.event.Event; +import org.dspace.identifier.DOI; import org.dspace.identifier.DOIIdentifierProvider; import org.dspace.identifier.IdentifierException; -import org.dspace.identifier.IdentifierNotFoundException; +import org.dspace.identifier.IdentifierNotApplicableException; +import org.dspace.identifier.factory.IdentifierServiceFactory; +import org.dspace.identifier.service.DOIService; +import org.dspace.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.utils.DSpace; import org.dspace.workflow.factory.WorkflowServiceFactory; /** * @author Pascal-Nicolas Becker (p dot becker at tu hyphen berlin dot de) + * @author Kim Shepherd */ public class DOIConsumer implements Consumer { /** @@ -30,12 +40,15 @@ public class DOIConsumer implements Consumer { */ private static Logger log = org.apache.logging.log4j.LogManager.getLogger(DOIConsumer.class); + ConfigurationService configurationService; + @Override public void initialize() throws Exception { // nothing to do // we can ask spring to give as a properly setuped instance of // DOIIdentifierProvider. Doing so we don't have to configure it and // can load it in consume method as this is not very expensive. + configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); } @@ -62,36 +75,72 @@ public class DOIConsumer implements Consumer { return; } Item item = (Item) dso; - - if (ContentServiceFactory.getInstance().getWorkspaceItemService().findByItem(ctx, item) != null - || WorkflowServiceFactory.getInstance().getWorkflowItemService().findByItem(ctx, item) != null) { - // ignore workflow and workspace items, DOI will be minted when item is installed - return; + DOIIdentifierProvider provider = new DSpace().getSingletonService(DOIIdentifierProvider.class); + boolean inProgress = (ContentServiceFactory.getInstance().getWorkspaceItemService().findByItem(ctx, item) + != null || WorkflowServiceFactory.getInstance().getWorkflowItemService().findByItem(ctx, item) != null); + boolean identifiersInSubmission = configurationService.getBooleanProperty("identifiers.submission.register", + false); + DOIService doiService = IdentifierServiceFactory.getInstance().getDOIService(); + Filter workspaceFilter = null; + if (identifiersInSubmission) { + workspaceFilter = FilterUtils.getFilterFromConfiguration("identifiers.submission.filter.workspace"); } - DOIIdentifierProvider provider = new DSpace().getSingletonService( - DOIIdentifierProvider.class); - - String doi = null; + if (inProgress && !identifiersInSubmission) { + // ignore workflow and workspace items, DOI will be minted and updated when item is installed + // UNLESS special pending filter is set + return; + } + DOI doi = null; try { - doi = provider.lookup(ctx, dso); - } catch (IdentifierNotFoundException ex) { + doi = doiService.findDOIByDSpaceObject(ctx, dso); + } catch (SQLException ex) { // nothing to do here, next if clause will stop us from processing // items without dois. } if (doi == null) { - log.debug("DOIConsumer cannot handles items without DOIs, skipping: " - + event.toString()); - return; - } - try { - provider.updateMetadata(ctx, dso, doi); - } catch (IllegalArgumentException ex) { - // should not happen, as we got the DOI from the DOIProvider - log.warn("DOIConsumer caught an IdentifierException.", ex); - } catch (IdentifierException ex) { - log.warn("DOIConsumer cannot update metadata for Item with ID " - + item.getID() + " and DOI " + doi + ".", ex); + // No DOI. The only time something should be minted is if we have enabled submission reg'n and + // it passes the workspace filter. We also need to update status to PENDING straight after. + if (inProgress) { + provider.mint(ctx, dso, workspaceFilter); + DOI newDoi = doiService.findDOIByDSpaceObject(ctx, dso); + if (newDoi != null) { + newDoi.setStatus(DOIIdentifierProvider.PENDING); + doiService.update(ctx, newDoi); + } + } else { + log.debug("DOIConsumer cannot handles items without DOIs, skipping: " + event.toString()); + } + } else { + // If in progress, we can also switch PENDING and MINTED status depending on the latest filter + // evaluation + if (inProgress) { + try { + // Check the filter + provider.checkMintable(ctx, workspaceFilter, dso); + // If we made it here, the existing doi should be back to PENDING + if (DOIIdentifierProvider.MINTED.equals(doi.getStatus())) { + doi.setStatus(DOIIdentifierProvider.PENDING); + } + } catch (IdentifierNotApplicableException e) { + // Set status to MINTED if configured to downgrade existing DOIs + if (configurationService + .getBooleanProperty("identifiers.submission.strip_pending_during_submission", true)) { + doi.setStatus(DOIIdentifierProvider.MINTED); + } + } + doiService.update(ctx, doi); + } else { + try { + provider.updateMetadata(ctx, dso, doi.getDoi()); + } catch (IllegalArgumentException ex) { + // should not happen, as we got the DOI from the DOIProvider + log.warn("DOIConsumer caught an IdentifierException.", ex); + } catch (IdentifierException ex) { + log.warn("DOIConsumer cannot update metadata for Item with ID " + + item.getID() + " and DOI " + doi + ".", ex); + } + } } } 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 e0e0da9440..088e2b1cbc 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 @@ -30,6 +30,9 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.content.DSpaceObject; import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.content.logic.Filter; +import org.dspace.content.logic.FilterUtils; +import org.dspace.content.logic.TrueFilter; import org.dspace.content.service.ItemService; import org.dspace.core.Constants; import org.dspace.core.Context; @@ -61,7 +64,8 @@ public class DOIOrganiser { protected ItemService itemService; protected DOIService doiService; protected ConfigurationService configurationService; - protected boolean skipFilter; + // This filter will override the default provider filter / behaviour + protected Filter filter; /** * Constructor to be called within the main() method @@ -76,7 +80,8 @@ public class DOIOrganiser { this.itemService = ContentServiceFactory.getInstance().getItemService(); this.doiService = IdentifierServiceFactory.getInstance().getDOIService(); this.configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); - this.skipFilter = false; + this.filter = DSpaceServicesFactory.getInstance().getServiceManager().getServiceByName( + "always_true_filter", TrueFilter.class); } /** @@ -121,12 +126,13 @@ public class DOIOrganiser { "Perform online metadata update for all identifiers queued for metadata update."); options.addOption("d", "delete-all", false, "Perform online deletion for all identifiers queued for deletion."); - options.addOption("q", "quiet", false, "Turn the command line output off."); - options.addOption(null, "skip-filter", false, - "Skip the configured item filter when registering or reserving."); + Option filterDoi = Option.builder().optionalArg(true).longOpt("filter").hasArg().argName("filterName") + .desc("Use the specified filter name instead of the provider's filter. Defaults to a special " + + "'always true' filter to force operations").build(); + options.addOption(filterDoi); Option registerDoi = Option.builder() .longOpt("register-doi") @@ -203,10 +209,12 @@ public class DOIOrganiser { } DOIService doiService = IdentifierServiceFactory.getInstance().getDOIService(); - // Should we skip the filter? - if (line.hasOption("skip-filter")) { - System.out.println("Skipping the item filter"); - organiser.skipFilter = true; + // Do we get a filter? + if (line.hasOption("filter")) { + String filter = line.getOptionValue("filter"); + if (null != filter) { + organiser.filter = FilterUtils.getFilterFromConfiguration(filter); + } } if (line.hasOption('s')) { @@ -394,19 +402,18 @@ public class DOIOrganiser { /** * Register DOI with the provider * @param doiRow - doi to register - * @param skipFilter - whether filters should be skipped before registration + * @param filter - logical item filter to override * @throws SQLException * @throws DOIIdentifierException */ - public void register(DOI doiRow, boolean skipFilter) throws SQLException, DOIIdentifierException { + public void register(DOI doiRow, Filter filter) throws SQLException, DOIIdentifierException { DSpaceObject dso = doiRow.getDSpaceObject(); if (Constants.ITEM != dso.getType()) { throw new IllegalArgumentException("Currenty DSpace supports DOIs for Items only."); } try { - provider.registerOnline(context, dso, - DOI.SCHEME + doiRow.getDoi()); + provider.registerOnline(context, dso, DOI.SCHEME + doiRow.getDoi(), filter); if (!quiet) { System.out.println("This identifier: " @@ -466,29 +473,23 @@ public class DOIOrganiser { } /** - * Register DOI with the provider, always applying (ie. never skipping) any configured filters + * Register DOI with the provider * @param doiRow - doi to register * @throws SQLException * @throws DOIIdentifierException */ public void register(DOI doiRow) throws SQLException, DOIIdentifierException { - if (this.skipFilter) { - System.out.println("Skipping the filter for " + doiRow.getDoi()); - } - register(doiRow, this.skipFilter); + register(doiRow, this.filter); } /** - * Reserve DOI with the provider, always applying (ie. never skipping) any configured filters + * Reserve DOI with the provider, * @param doiRow - doi to reserve * @throws SQLException * @throws DOIIdentifierException */ public void reserve(DOI doiRow) { - if (this.skipFilter) { - System.out.println("Skipping the filter for " + doiRow.getDoi()); - } - reserve(doiRow, this.skipFilter); + reserve(doiRow, this.filter); } /** @@ -497,14 +498,14 @@ public class DOIOrganiser { * @throws SQLException * @throws DOIIdentifierException */ - public void reserve(DOI doiRow, boolean skipFilter) { + public void reserve(DOI doiRow, Filter filter) { DSpaceObject dso = doiRow.getDSpaceObject(); if (Constants.ITEM != dso.getType()) { throw new IllegalArgumentException("Currently DSpace supports DOIs for Items only."); } try { - provider.reserveOnline(context, dso, DOI.SCHEME + doiRow.getDoi(), skipFilter); + provider.reserveOnline(context, dso, DOI.SCHEME + doiRow.getDoi(), filter); if (!quiet) { System.out.println("This identifier : " + DOI.SCHEME + doiRow.getDoi() + " is successfully reserved."); @@ -699,7 +700,7 @@ public class DOIOrganiser { //Check if this Item has an Identifier, mint one if it doesn't if (null == doiRow) { - doi = provider.mint(context, dso, this.skipFilter); + doi = provider.mint(context, dso, this.filter); doiRow = doiService.findByDoi(context, doi.substring(DOI.SCHEME.length())); return doiRow; @@ -723,7 +724,7 @@ public class DOIOrganiser { doiRow = doiService.findDOIByDSpaceObject(context, dso); if (null == doiRow) { - doi = provider.mint(context, dso, this.skipFilter); + doi = provider.mint(context, dso, this.filter); doiRow = doiService.findByDoi(context, doi.substring(DOI.SCHEME.length())); } diff --git a/dspace-api/src/main/java/org/dspace/identifier/service/IdentifierService.java b/dspace-api/src/main/java/org/dspace/identifier/service/IdentifierService.java index 64eee1dfcf..23005b6575 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/service/IdentifierService.java +++ b/dspace-api/src/main/java/org/dspace/identifier/service/IdentifierService.java @@ -9,9 +9,11 @@ package org.dspace.identifier.service; import java.sql.SQLException; import java.util.List; +import java.util.Map; import org.dspace.authorize.AuthorizeException; import org.dspace.content.DSpaceObject; +import org.dspace.content.logic.Filter; import org.dspace.core.Context; import org.dspace.identifier.Identifier; import org.dspace.identifier.IdentifierException; @@ -103,6 +105,52 @@ public interface IdentifierService { */ void register(Context context, DSpaceObject dso) throws AuthorizeException, SQLException, IdentifierException; + /** + * + * Register identifiers for a DSO, with a map of logical filters for each Identifier class to apply + * at the time of local registration. + * + * @param context The relevant DSpace Context. + * @param dso DSpace object to be registered + * @param typeFilters If a service supports a given Identifier implementation, apply the associated filter + * @throws AuthorizeException if authorization error + * @throws SQLException if database error + * @throws IdentifierException if identifier error + */ + void register(Context context, DSpaceObject dso, Map, Filter> typeFilters) + throws AuthorizeException, SQLException, IdentifierException; + + /** + * + * Register identifier(s) for the given DSO just with providers that support that Identifier class, and + * apply the given filter if that provider extends FilteredIdentifierProvider + * + * @param context The relevant DSpace Context. + * @param dso DSpace object to be registered + * @param type Type of identifier to register + * @param filter If a service supports a given Identifier implementation, apply this specific filter + * @throws AuthorizeException if authorization error + * @throws SQLException if database error + * @throws IdentifierException if identifier error + */ + void register(Context context, DSpaceObject dso, Class type, Filter filter) + throws AuthorizeException, SQLException, IdentifierException; + + /** + * + * Register identifier(s) for the given DSO just with providers that support that Identifier class, and + * apply the given filter if that provider extends FilteredIdentifierProvider + * + * @param context The relevant DSpace Context. + * @param dso DSpace object to be registered + * @param type Type of identifier to register + * @throws AuthorizeException if authorization error + * @throws SQLException if database error + * @throws IdentifierException if identifier error + */ + void register(Context context, DSpaceObject dso, Class type) + throws AuthorizeException, SQLException, IdentifierException; + /** * Used to Register a specific Identifier (for example a Handle, hdl:1234.5/6). * The provider is responsible for detecting and processing the appropriate diff --git a/dspace-api/src/main/java/org/dspace/iiif/IIIFApiQueryServiceImpl.java b/dspace-api/src/main/java/org/dspace/iiif/IIIFApiQueryServiceImpl.java index 04a08a7781..7c6336ed3c 100644 --- a/dspace-api/src/main/java/org/dspace/iiif/IIIFApiQueryServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/iiif/IIIFApiQueryServiceImpl.java @@ -23,8 +23,7 @@ import org.dspace.iiif.util.IIIFSharedUtils; /** - * Queries the configured IIIF server for image dimensions. Used for - * formats that cannot be easily read using ImageIO (jpeg 2000). + * Queries the configured IIIF image server via the Image API. * * @author Michael Spalti mspalti@willamette.edu */ 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 445dfedebd..0014088c86 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 @@ -105,10 +105,10 @@ public class BibtexImportMetadataSourceServiceImpl extends AbstractPlainMetadata /** - * Retrieve the MetadataFieldMapping containing the mapping between RecordType + * Set the MetadataFieldMapping containing the mapping between RecordType * (in this case PlainMetadataSourceDto.class) and Metadata * - * @return The configured MetadataFieldMapping + * @param metadataFieldMap The configured MetadataFieldMapping */ @Override @SuppressWarnings("unchecked") 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 new file mode 100644 index 0000000000..f8540307b9 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/datacite/DataCiteFieldMapping.java @@ -0,0 +1,38 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.importer.external.datacite; + +import java.util.Map; +import javax.annotation.Resource; + +import org.dspace.importer.external.metadatamapping.AbstractMetadataFieldMapping; + +/** + * An implementation of {@link AbstractMetadataFieldMapping} + * Responsible for defining the mapping of the datacite metadatum fields on the DSpace metadatum fields + * + * @author Pasquale Cavallo (pasquale.cavallo at 4science dot it) + * @author Florian Gantner (florian.gantner@uni-bamberg.de) + */ +public class DataCiteFieldMapping extends AbstractMetadataFieldMapping { + + /** + * Defines which metadatum is mapped on which metadatum. Note that while the key must be unique it + * only matters here for postprocessing of the value. The mapped MetadatumContributor has full control over + * what metadatafield is generated. + * + * @param metadataFieldMap The map containing the link between retrieve metadata and metadata that will be set to + * the item. + */ + @Override + @Resource(name = "dataciteMetadataFieldMap") + public void setMetadataFieldMap(Map metadataFieldMap) { + super.setMetadataFieldMap(metadataFieldMap); + } + +} 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 new file mode 100644 index 0000000000..a11f2bc247 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/datacite/DataCiteImportMetadataSourceServiceImpl.java @@ -0,0 +1,168 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.datacite; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import javax.el.MethodNotFoundException; + +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.content.Item; +import org.dspace.importer.external.datamodel.ImportRecord; +import org.dspace.importer.external.datamodel.Query; +import org.dspace.importer.external.exception.MetadataSourceException; +import org.dspace.importer.external.liveimportclient.service.LiveImportClient; +import org.dspace.importer.external.service.AbstractImportMetadataSourceService; +import org.dspace.importer.external.service.DoiCheck; +import org.dspace.importer.external.service.components.QuerySource; +import org.dspace.services.ConfigurationService; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Implements a data source for querying Datacite + * Mainly copied from CrossRefImportMetadataSourceServiceImpl. + * + * optional Affiliation informations are not part of the API request. + * 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) + * @author Florian Gantner (florian.gantner@uni-bamberg.de) + * + */ +public class DataCiteImportMetadataSourceServiceImpl + extends AbstractImportMetadataSourceService implements QuerySource { + private final static Logger log = LogManager.getLogger(); + + @Autowired + private LiveImportClient liveImportClient; + + @Autowired + private ConfigurationService configurationService; + + @Override + public String getImportSource() { + return "datacite"; + } + + @Override + public void init() throws Exception { + } + + @Override + public ImportRecord getRecord(String recordId) throws MetadataSourceException { + Collection records = getRecords(recordId, 0, 1); + if (records.size() == 0) { + return null; + } + return records.stream().findFirst().get(); + } + + @Override + public int getRecordsCount(String query) throws MetadataSourceException { + Collection records = getRecords(query, 0, -1); + return records == null ? 0 : records.size(); + } + + @Override + public int getRecordsCount(Query query) throws MetadataSourceException { + String id = getID(query.toString()); + return getRecordsCount(StringUtils.isBlank(id) ? query.toString() : id); + } + + + @Override + public Collection getRecords(String query, int start, int count) throws MetadataSourceException { + List records = new ArrayList<>(); + 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); + 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"); + return records; + } + JsonNode dataNode = jsonNode.at("/data"); + if (dataNode.isArray()) { + Iterator iterator = dataNode.iterator(); + while (iterator.hasNext()) { + JsonNode singleDoiNode = iterator.next(); + String json = singleDoiNode.at("/attributes").toString(); + records.add(transformSourceRecords(json)); + } + } else { + String json = dataNode.at("/attributes").toString(); + records.add(transformSourceRecords(json)); + } + + return records; + } + + private JsonNode convertStringJsonToJsonNode(String json) { + try { + return new ObjectMapper().readTree(json); + } catch (JsonProcessingException e) { + log.error("Unable to process json response.", e); + } + return null; + } + + @Override + public Collection getRecords(Query query) throws MetadataSourceException { + String id = getID(query.toString()); + return getRecords(StringUtils.isBlank(id) ? query.toString() : id, 0, -1); + } + + @Override + public ImportRecord getRecord(Query query) throws MetadataSourceException { + String id = getID(query.toString()); + return getRecord(StringUtils.isBlank(id) ? query.toString() : id); + } + + @Override + public Collection findMatchingRecords(Query query) throws MetadataSourceException { + String id = getID(query.toString()); + return getRecords(StringUtils.isBlank(id) ? query.toString() : id, 0, -1); + } + + + @Override + public Collection findMatchingRecords(Item item) throws MetadataSourceException { + throw new MethodNotFoundException("This method is not implemented for DataCite"); + } + + public String getID(String query) { + if (DoiCheck.isDoi(query)) { + return query; + } + // Workaround for encoded slashes. + if (query.contains("%252F")) { + query = query.replace("%252F", "/"); + } + if (DoiCheck.isDoi(query)) { + return query; + } + return StringUtils.EMPTY; + } +} diff --git a/dspace-api/src/main/java/org/dspace/importer/external/liveimportclient/service/LiveImportClient.java b/dspace-api/src/main/java/org/dspace/importer/external/liveimportclient/service/LiveImportClient.java index 829b5ed2de..a1132cda9c 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/liveimportclient/service/LiveImportClient.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/liveimportclient/service/LiveImportClient.java @@ -21,7 +21,7 @@ public interface LiveImportClient { * * @param timeout The connect timeout in milliseconds * @param URL URL - * @param requestParams This map contains the parameters to be included in the request. + * @param params This map contains the parameters to be included in the request. * Each parameter will be added to the url?(key=value) * @return The response in String type converted from InputStream */ diff --git a/dspace-api/src/main/java/org/dspace/importer/external/liveimportclient/service/LiveImportClientImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/liveimportclient/service/LiveImportClientImpl.java index 81a6631127..1a8a7a7861 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/liveimportclient/service/LiveImportClientImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/liveimportclient/service/LiveImportClientImpl.java @@ -60,7 +60,8 @@ public class LiveImportClientImpl implements LiveImportClient { requestConfigBuilder.setConnectionRequestTimeout(timeout); RequestConfig defaultRequestConfig = requestConfigBuilder.build(); - method = new HttpGet(buildUrl(URL, params.get(URI_PARAMETERS))); + String uri = buildUrl(URL, params.get(URI_PARAMETERS)); + method = new HttpGet(uri); method.setConfig(defaultRequestConfig); Map headerParams = params.get(HEADER_PARAMETERS); @@ -71,7 +72,9 @@ public class LiveImportClientImpl implements LiveImportClient { } configureProxy(method, defaultRequestConfig); - + if (log.isDebugEnabled()) { + log.debug("Performing GET request to \"" + uri + "\"..."); + } HttpResponse httpResponse = httpClient.execute(method); if (isNotSuccessfull(httpResponse)) { throw new RuntimeException("The request failed with: " + getStatusCode(httpResponse) + " code, reason= " @@ -98,7 +101,8 @@ public class LiveImportClientImpl implements LiveImportClient { Builder requestConfigBuilder = RequestConfig.custom(); RequestConfig defaultRequestConfig = requestConfigBuilder.build(); - method = new HttpPost(buildUrl(URL, params.get(URI_PARAMETERS))); + String uri = buildUrl(URL, params.get(URI_PARAMETERS)); + method = new HttpPost(uri); method.setConfig(defaultRequestConfig); if (StringUtils.isNotBlank(entry)) { method.setEntity(new StringEntity(entry)); @@ -106,7 +110,9 @@ public class LiveImportClientImpl implements LiveImportClient { setHeaderParams(method, params); configureProxy(method, defaultRequestConfig); - + if (log.isDebugEnabled()) { + log.debug("Performing POST request to \"" + uri + "\"..." ); + } HttpResponse httpResponse = httpClient.execute(method); if (isNotSuccessfull(httpResponse)) { throw new RuntimeException(); @@ -185,4 +191,4 @@ public class LiveImportClientImpl implements LiveImportClient { this.httpClient = httpClient; } -} \ No newline at end of file +} 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 00b414c485..e32f45a4d5 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 @@ -156,7 +156,7 @@ public class EpoIdMetadataContributor implements MetadataContributor { * Depending on the retrieved node (using the query), different types of values will be added to the MetadatumDTO * list * - * @param t A class to retrieve metadata from. + * @param element A class to retrieve metadata from. * @return a collection of import records. Only the identifier of the found records may be put in the record. */ @Override 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 f739980220..590fc63283 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 @@ -118,7 +118,7 @@ public class SimpleJsonPathMetadataContributor implements MetadataContributor implements MetadataContributor int j = 0; // Use the first dcDate that has been formatted (Config should go from most specific to most lenient) - while (j < dateFormatsToAttempt.size() && dcDate == null) { + while (j < dateFormatsToAttempt.size()) { String dateFormat = dateFormatsToAttempt.get(j); try { SimpleDateFormat formatter = new SimpleDateFormat(dateFormat); Date date = formatter.parse(dateString); dcDate = new DCDate(date); + values.add(metadataFieldMapping.toDCValue(field, formatter.format(date))); + break; } catch (ParseException e) { // Multiple dateformats can be configured, we don't want to print the entire stacktrace every // time one of those formats fails. @@ -136,9 +138,7 @@ public class PubmedDateMetadatumContributor implements MetadataContributor } j++; } - if (dcDate != null) { - values.add(metadataFieldMapping.toDCValue(field, dcDate.toString())); - } else { + if (dcDate == null) { log.info( "Failed parsing " + dateString + ", check " + "the configured dataformats in config/spring/api/pubmed-integration.xml"); diff --git a/dspace-api/src/main/java/org/dspace/importer/external/pubmed/service/PubmedImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/pubmed/service/PubmedImportMetadataSourceServiceImpl.java index b30ea22ca4..933d6b1446 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/pubmed/service/PubmedImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/pubmed/service/PubmedImportMetadataSourceServiceImpl.java @@ -292,7 +292,14 @@ public class PubmedImportMetadataSourceServiceImpl extends AbstractImportMetadat int countAttempt = 0; while (StringUtils.isBlank(response) && countAttempt <= attempt) { countAttempt++; + + long time = System.currentTimeMillis() - lastRequest; + if ((time) < interRequestTime) { + Thread.sleep(interRequestTime - time); + } + response = liveImportClient.executeHttpGetRequest(1000, uriBuilder.toString(), params); + lastRequest = System.currentTimeMillis(); } if (StringUtils.isBlank(response)) { @@ -316,7 +323,13 @@ public class PubmedImportMetadataSourceServiceImpl extends AbstractImportMetadat countAttempt = 0; while (StringUtils.isBlank(response2) && countAttempt <= attempt) { countAttempt++; + long time = System.currentTimeMillis() - lastRequest; + if ((time) < interRequestTime) { + Thread.sleep(interRequestTime - time); + } response2 = liveImportClient.executeHttpGetRequest(1000, uriBuilder2.toString(), params2); + + lastRequest = System.currentTimeMillis(); } if (StringUtils.isBlank(response2)) { @@ -418,7 +431,13 @@ public class PubmedImportMetadataSourceServiceImpl extends AbstractImportMetadat int countAttempt = 0; while (StringUtils.isBlank(response) && countAttempt <= attempt) { countAttempt++; + long time = System.currentTimeMillis() - lastRequest; + if ((time) < interRequestTime) { + Thread.sleep(interRequestTime - time); + } + response = liveImportClient.executeHttpGetRequest(1000, uriBuilder.toString(), params); + lastRequest = System.currentTimeMillis(); } if (StringUtils.isBlank(response)) { @@ -441,7 +460,12 @@ public class PubmedImportMetadataSourceServiceImpl extends AbstractImportMetadat countAttempt = 0; while (StringUtils.isBlank(response2) && countAttempt <= attempt) { countAttempt++; + long time = System.currentTimeMillis() - lastRequest; + if ((time) < interRequestTime) { + Thread.sleep(interRequestTime - time); + } response2 = liveImportClient.executeHttpGetRequest(1000, uriBuilder2.toString(), params2); + lastRequest = System.currentTimeMillis(); } if (StringUtils.isBlank(response2)) { @@ -501,4 +525,4 @@ public class PubmedImportMetadataSourceServiceImpl extends AbstractImportMetadat this.urlSearch = urlSearch; } -} \ No newline at end of file +} 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 2574e187df..1f460c19e6 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 @@ -126,10 +126,10 @@ public class RisImportMetadataSourceServiceImpl extends AbstractPlainMetadataSou } /** - * Retrieve the MetadataFieldMapping containing the mapping between RecordType + * Set the MetadataFieldMapping containing the mapping between RecordType * (in this case PlainMetadataSourceDto.class) and Metadata * - * @return The configured MetadataFieldMapping + * @param metadataFieldMap The configured MetadataFieldMapping */ @Override @SuppressWarnings("unchecked") 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 019cf33177..5d83b9a7cc 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 @@ -42,7 +42,7 @@ public abstract class AbstractPlainMetadataSource /** * Set the file extensions supported by this metadata service * - * @param supportedExtensionsthe file extensions (xml,txt,...) supported by this service + * @param supportedExtensions the file extensions (xml,txt,...) supported by this service */ public void setSupportedExtensions(List supportedExtensions) { this.supportedExtensions = supportedExtensions; @@ -57,7 +57,7 @@ public abstract class AbstractPlainMetadataSource * Return a list of ImportRecord constructed from input file. This list is based on * the results retrieved from the file (InputStream) parsed through abstract method readData * - * @param InputStream The inputStream of the file + * @param is The inputStream of the file * @return A list of {@link ImportRecord} * @throws FileSourceException if, for any reason, the file is not parsable */ @@ -76,7 +76,7 @@ public abstract class AbstractPlainMetadataSource * the result retrieved from the file (InputStream) parsed through abstract method * "readData" implementation * - * @param InputStream The inputStream of the file + * @param is The inputStream of the file * @return An {@link ImportRecord} matching the file content * @throws FileSourceException if, for any reason, the file is not parsable * @throws FileMultipleOccurencesException if the file contains more than one entry 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 38632a1a2b..29801433e3 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 @@ -183,6 +183,7 @@ public abstract class AbstractRemoteMetadataSource { log.warn("Error in trying operation " + operationId + " " + retry + " " + warning + ", retrying !", e); } finally { + this.lastRequest = System.currentTimeMillis(); lock.unlock(); } @@ -262,5 +263,7 @@ public abstract class AbstractRemoteMetadataSource { */ public abstract void init() throws Exception; - + public void setInterRequestTime(final long interRequestTime) { + this.interRequestTime = interRequestTime; + } } diff --git a/dspace-api/src/main/java/org/dspace/importer/external/service/components/FileSource.java b/dspace-api/src/main/java/org/dspace/importer/external/service/components/FileSource.java index 5bef0984df..13c81d1516 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/service/components/FileSource.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/service/components/FileSource.java @@ -30,7 +30,7 @@ public interface FileSource extends MetadataSource { /** * Return a list of ImportRecord constructed from input file. * - * @param InputStream The inputStream of the file + * @param inputStream The inputStream of the file * @return A list of {@link ImportRecord} * @throws FileSourceException if, for any reason, the file is not parsable */ @@ -40,7 +40,7 @@ public interface FileSource extends MetadataSource { /** * Return an ImportRecord constructed from input file. * - * @param InputStream The inputStream of the file + * @param inputStream The inputStream of the file * @return An {@link ImportRecord} matching the file content * @throws FileSourceException if, for any reason, the file is not parsable * @throws FileMultipleOccurencesException if the file contains more than one entry diff --git a/dspace-api/src/main/java/org/dspace/license/CCLicenseConnectorServiceImpl.java b/dspace-api/src/main/java/org/dspace/license/CCLicenseConnectorServiceImpl.java index 619227432d..cdecadba52 100644 --- a/dspace-api/src/main/java/org/dspace/license/CCLicenseConnectorServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/license/CCLicenseConnectorServiceImpl.java @@ -106,7 +106,7 @@ public class CCLicenseConnectorServiceImpl implements CCLicenseConnectorService, for (String license : licenses) { - String licenseUri = ccLicenseUrl + "/license/" + license; + String licenseUri = ccLicenseUrl + "/license/" + license + "?locale=" + language; HttpGet licenseHttpGet = new HttpGet(licenseUri); try (CloseableHttpResponse response = client.execute(licenseHttpGet)) { CCLicense ccLicense = retrieveLicenseObject(license, response); diff --git a/dspace-api/src/main/java/org/dspace/license/CreativeCommonsServiceImpl.java b/dspace-api/src/main/java/org/dspace/license/CreativeCommonsServiceImpl.java index 96f110c101..c9c8127d18 100644 --- a/dspace-api/src/main/java/org/dspace/license/CreativeCommonsServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/license/CreativeCommonsServiceImpl.java @@ -430,9 +430,10 @@ public class CreativeCommonsServiceImpl implements CreativeCommonsService, Initi } - private void addLicenseField(Context context, Item item, String field, String value) throws SQLException { + private void addLicenseField(Context context, Item item, String field, String language, String value) + throws SQLException { String[] params = splitField(field); - itemService.addMetadata(context, item, params[0], params[1], params[2], params[3], value); + itemService.addMetadata(context, item, params[0], params[1], params[2], language, value); } @@ -605,7 +606,10 @@ public class CreativeCommonsServiceImpl implements CreativeCommonsService, Initi } } - updateJurisdiction(fullParamMap); + // Replace the jurisdiction unless default value is set to none + if (!"none".equals(jurisdiction)) { + updateJurisdiction(fullParamMap); + } return fullParamMap; } @@ -688,12 +692,12 @@ public class CreativeCommonsServiceImpl implements CreativeCommonsService, Initi String uriField = getCCField("uri"); String nameField = getCCField("name"); - addLicenseField(context, item, uriField, licenseUri); + addLicenseField(context, item, uriField, null, licenseUri); if (configurationService.getBooleanProperty("cc.submit.addbitstream")) { setLicenseRDF(context, item, fetchLicenseRDF(doc)); } if (configurationService.getBooleanProperty("cc.submit.setname")) { - addLicenseField(context, item, nameField, licenseName); + addLicenseField(context, item, nameField, "en", licenseName); } } 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 33edea112e..07a79384c7 100644 --- a/dspace-api/src/main/java/org/dspace/orcid/OrcidHistory.java +++ b/dspace-api/src/main/java/org/dspace/orcid/OrcidHistory.java @@ -79,6 +79,8 @@ public class OrcidHistory implements ReloadableEntity { /** * A description of the synchronized resource. */ + @Lob + @Type(type = "org.hibernate.type.TextType") @Column(name = "description") private String description; @@ -87,7 +89,7 @@ public class OrcidHistory implements ReloadableEntity { * the owner itself. */ @Lob - @Type(type = "org.dspace.storage.rdbms.hibernate.DatabaseAwareLobType") + @Type(type = "org.hibernate.type.TextType") @Column(name = "metadata") private String metadata; @@ -102,7 +104,7 @@ public class OrcidHistory implements ReloadableEntity { * The response message incoming from ORCID. */ @Lob - @Type(type = "org.dspace.storage.rdbms.hibernate.DatabaseAwareLobType") + @Type(type = "org.hibernate.type.TextType") @Column(name = "response_message") 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 4794e89008..65b66cd20c 100644 --- a/dspace-api/src/main/java/org/dspace/orcid/OrcidQueue.java +++ b/dspace-api/src/main/java/org/dspace/orcid/OrcidQueue.java @@ -64,6 +64,8 @@ public class OrcidQueue implements ReloadableEntity { /** * A description of the resource to be synchronized. */ + @Lob + @Type(type = "org.hibernate.type.TextType") @Column(name = "description") private String description; @@ -87,7 +89,7 @@ public class OrcidQueue implements ReloadableEntity { */ @Lob @Column(name = "metadata") - @Type(type = "org.dspace.storage.rdbms.hibernate.DatabaseAwareLobType") + @Type(type = "org.hibernate.type.TextType") private String metadata; /** 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 15cc1b2fc6..eab3ba460c 100644 --- a/dspace-api/src/main/java/org/dspace/scripts/Process.java +++ b/dspace-api/src/main/java/org/dspace/scripts/Process.java @@ -21,6 +21,7 @@ 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; @@ -35,6 +36,7 @@ import org.dspace.content.ProcessStatus; import org.dspace.core.ReloadableEntity; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; +import org.hibernate.annotations.Type; /** * This class is the DB Entity representation of the Process object to be stored in the Database @@ -68,6 +70,8 @@ public class Process implements ReloadableEntity { @Enumerated(EnumType.STRING) private ProcessStatus processStatus; + @Lob + @Type(type = "org.hibernate.type.TextType") @Column(name = "parameters") private String parameters; @@ -225,15 +229,15 @@ public class Process implements ReloadableEntity { } /** - * This method sets the special groups associated with the Process. + * This method will return the special groups associated with the Process. */ public List getGroups() { return groups; } /** - * This method will return special groups associated with the Process. - * @return The special groups of this process. + * This method sets the special groups associated with the Process. + * @param groups The special groups of this process. */ public void setGroups(List groups) { this.groups = groups; 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 4eb7cdbbc1..c8a7812a51 100644 --- a/dspace-api/src/main/java/org/dspace/scripts/ScriptServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/scripts/ScriptServiceImpl.java @@ -8,6 +8,7 @@ package org.dspace.scripts; import java.lang.reflect.InvocationTargetException; +import java.util.Comparator; import java.util.List; import java.util.stream.Collectors; @@ -36,7 +37,9 @@ public class ScriptServiceImpl implements ScriptService { @Override public List getScriptConfigurations(Context context) { return serviceManager.getServicesByType(ScriptConfiguration.class).stream().filter( - scriptConfiguration -> scriptConfiguration.isAllowedToExecute(context)).collect(Collectors.toList()); + scriptConfiguration -> scriptConfiguration.isAllowedToExecute(context)) + .sorted(Comparator.comparing(ScriptConfiguration::getName)) + .collect(Collectors.toList()); } @Override 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 4b2ae94e75..c192a25594 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/SolrLoggerServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/statistics/SolrLoggerServiceImpl.java @@ -17,9 +17,12 @@ import java.io.UnsupportedEncodingException; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; +import java.net.URI; import java.net.URLEncoder; import java.net.UnknownHostException; import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.nio.file.Paths; import java.sql.SQLException; import java.text.DateFormat; import java.text.ParseException; @@ -174,6 +177,19 @@ public class SolrLoggerServiceImpl implements SolrLoggerService, InitializingBea @Override public void afterPropertiesSet() throws Exception { + statisticsCoreURL = configurationService.getProperty("solr-statistics.server"); + + if (null != statisticsCoreURL) { + Path statisticsPath = Paths.get(new URI(statisticsCoreURL).getPath()); + statisticsCoreBase = statisticsPath + .getName(statisticsPath.getNameCount() - 1) + .toString(); + } else { + log.warn("Unable to find solr-statistics.server parameter in DSpace configuration. This is required for " + + "sharding statistics."); + statisticsCoreBase = null; + } + solr = solrStatisticsCore.getSolr(); // Read in the file so we don't have to do it all the time @@ -197,7 +213,7 @@ public class SolrLoggerServiceImpl implements SolrLoggerService, InitializingBea @Override public void postView(DSpaceObject dspaceObject, HttpServletRequest request, EPerson currentUser) { - if (solr == null || locationService == null) { + if (solr == null) { return; } initSolrYearCores(); @@ -238,7 +254,7 @@ public class SolrLoggerServiceImpl implements SolrLoggerService, InitializingBea @Override public void postView(DSpaceObject dspaceObject, String ip, String userAgent, String xforwardedfor, EPerson currentUser) { - if (solr == null || locationService == null) { + if (solr == null) { return; } initSolrYearCores(); 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 b1b31c0fe1..e45ce163ed 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 @@ -16,6 +16,7 @@ import org.apache.commons.cli.DefaultParser; import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.Options; import org.apache.logging.log4j.Logger; +import org.apache.tools.ant.Project; import org.apache.tools.ant.taskdefs.Get; import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.statistics.factory.StatisticsServiceFactory; @@ -136,6 +137,7 @@ public class StatisticsClient { URL url = new URL(value); Get get = new Get(); + get.setProject(new Project()); get.setDest(new File(spiders, url.getHost() + url.getPath().replace("/", "-"))); get.setSrc(url); get.setUseTimestamp(true); 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 0bd71088da..977b5b7b32 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 @@ -17,6 +17,7 @@ import java.util.Map; import java.util.UUID; import javax.annotation.Nullable; +import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections4.MapUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -224,25 +225,62 @@ public class BitstreamStorageServiceImpl implements BitstreamStorageService, Ini @Override public void cleanup(boolean deleteDbRecords, boolean verbose) throws SQLException, IOException, AuthorizeException { Context context = new Context(Context.Mode.BATCH_EDIT); - int commitCounter = 0; + + int offset = 0; + int limit = 100; + + int cleanedBitstreamCount = 0; + + int deletedBitstreamCount = bitstreamService.countDeletedBitstreams(context); + System.out.println("Found " + deletedBitstreamCount + " deleted bistream to cleanup"); try { context.turnOffAuthorisationSystem(); - List storage = bitstreamService.findDeletedBitstreams(context); - for (Bitstream bitstream : storage) { - UUID bid = bitstream.getID(); - Map wantedMetadata = new HashMap(); - wantedMetadata.put("size_bytes", null); - wantedMetadata.put("modified", null); - Map receivedMetadata = this.getStore(bitstream.getStoreNumber()).about(bitstream, wantedMetadata); + while (cleanedBitstreamCount < deletedBitstreamCount) { + + List storage = bitstreamService.findDeletedBitstreams(context, limit, offset); + + if (CollectionUtils.isEmpty(storage)) { + break; + } + + for (Bitstream bitstream : storage) { + UUID bid = bitstream.getID(); + Map wantedMetadata = new HashMap(); + wantedMetadata.put("size_bytes", null); + wantedMetadata.put("modified", null); + Map receivedMetadata = this.getStore(bitstream.getStoreNumber()).about(bitstream, wantedMetadata); - // Make sure entries which do not exist are removed - if (MapUtils.isEmpty(receivedMetadata)) { - log.debug("bitstore.about is empty, so file is not present"); + // Make sure entries which do not exist are removed + if (MapUtils.isEmpty(receivedMetadata)) { + log.debug("bitstore.about is empty, so file is not present"); + if (deleteDbRecords) { + log.debug("deleting record"); + if (verbose) { + System.out.println(" - Deleting bitstream information (ID: " + bid + ")"); + } + checksumHistoryService.deleteByBitstream(context, bitstream); + if (verbose) { + System.out.println(" - Deleting bitstream record from database (ID: " + bid + ")"); + } + bitstreamService.expunge(context, bitstream); + } + context.uncacheEntity(bitstream); + continue; + } + + // This is a small chance that this is a file which is + // being stored -- get it next time. + if (isRecent(Long.valueOf(receivedMetadata.get("modified").toString()))) { + log.debug("file is recent"); + context.uncacheEntity(bitstream); + continue; + } + if (deleteDbRecords) { - log.debug("deleting record"); + log.debug("deleting db record"); if (verbose) { System.out.println(" - Deleting bitstream information (ID: " + bid + ")"); } @@ -252,60 +290,42 @@ public class BitstreamStorageServiceImpl implements BitstreamStorageService, Ini } bitstreamService.expunge(context, bitstream); } + + if (isRegisteredBitstream(bitstream.getInternalId())) { + context.uncacheEntity(bitstream); + continue; // do not delete registered bitstreams + } + + + // Since versioning allows for multiple bitstreams, check if the internal + // identifier isn't used on + // another place + if (bitstreamService.findDuplicateInternalIdentifier(context, bitstream).isEmpty()) { + this.getStore(bitstream.getStoreNumber()).remove(bitstream); + + String message = ("Deleted bitstreamID " + bid + ", internalID " + bitstream.getInternalId()); + if (log.isDebugEnabled()) { + log.debug(message); + } + if (verbose) { + System.out.println(message); + } + } + context.uncacheEntity(bitstream); - continue; } - // This is a small chance that this is a file which is - // being stored -- get it next time. - if (isRecent(Long.valueOf(receivedMetadata.get("modified").toString()))) { - log.debug("file is recent"); - context.uncacheEntity(bitstream); - continue; + // Commit actual changes to DB after dispatch events + System.out.print("Performing incremental commit to the database..."); + context.commit(); + System.out.println(" Incremental commit done!"); + + cleanedBitstreamCount = cleanedBitstreamCount + storage.size(); + + if (!deleteDbRecords) { + offset = offset + limit; } - if (deleteDbRecords) { - log.debug("deleting db record"); - if (verbose) { - System.out.println(" - Deleting bitstream information (ID: " + bid + ")"); - } - checksumHistoryService.deleteByBitstream(context, bitstream); - if (verbose) { - System.out.println(" - Deleting bitstream record from database (ID: " + bid + ")"); - } - bitstreamService.expunge(context, bitstream); - } - - if (isRegisteredBitstream(bitstream.getInternalId())) { - context.uncacheEntity(bitstream); - continue; // do not delete registered bitstreams - } - - - // Since versioning allows for multiple bitstreams, check if the internal identifier isn't used on - // another place - if (bitstreamService.findDuplicateInternalIdentifier(context, bitstream).isEmpty()) { - this.getStore(bitstream.getStoreNumber()).remove(bitstream); - - String message = ("Deleted bitstreamID " + bid + ", internalID " + bitstream.getInternalId()); - if (log.isDebugEnabled()) { - log.debug(message); - } - if (verbose) { - System.out.println(message); - } - } - - // Make sure to commit our outstanding work every 100 - // iterations. Otherwise you risk losing the entire transaction - // if we hit an exception, which isn't useful at all for large - // amounts of bitstreams. - commitCounter++; - if (commitCounter % 100 == 0) { - context.dispatchEvents(); - } - - context.uncacheEntity(bitstream); } System.out.print("Committing changes to the database..."); 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 70eabbcc80..2bad0ac012 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 @@ -62,6 +62,8 @@ import org.springframework.beans.factory.annotation.Autowired; public class S3BitStoreService extends BaseBitStoreService { protected static final String DEFAULT_BUCKET_PREFIX = "dspace-asset-"; + // Prefix indicating a registered bitstream + protected final String REGISTERED_FLAG = "-R"; /** * log4j log */ @@ -246,6 +248,10 @@ public class S3BitStoreService extends BaseBitStoreService { @Override public InputStream get(Bitstream bitstream) throws IOException { String key = getFullKey(bitstream.getInternalId()); + // Strip -R from bitstream key if it's registered + if (isRegisteredBitstream(key)) { + key = key.substring(REGISTERED_FLAG.length()); + } try { S3Object object = s3Service.getObject(new GetObjectRequest(bucketName, key)); return (object != null) ? object.getObjectContent() : null; @@ -312,6 +318,10 @@ public class S3BitStoreService extends BaseBitStoreService { @Override public Map about(Bitstream bitstream, Map attrs) throws IOException { String key = getFullKey(bitstream.getInternalId()); + // If this is a registered bitstream, strip the -R prefix before retrieving + if (isRegisteredBitstream(key)) { + key = key.substring(REGISTERED_FLAG.length()); + } try { ObjectMetadata objectMetadata = s3Service.getObjectMetadata(bucketName, key); if (objectMetadata != null) { @@ -410,13 +420,13 @@ public class S3BitStoreService extends BaseBitStoreService { * @param sInternalId * @return Computed Relative path */ - private String getRelativePath(String sInternalId) { + public String getRelativePath(String sInternalId) { BitstreamStorageService bitstreamStorageService = StorageServiceFactory.getInstance() .getBitstreamStorageService(); String sIntermediatePath = StringUtils.EMPTY; if (bitstreamStorageService.isRegisteredBitstream(sInternalId)) { - sInternalId = sInternalId.substring(2); + sInternalId = sInternalId.substring(REGISTERED_FLAG.length()); } else { sInternalId = sanitizeIdentifier(sInternalId); sIntermediatePath = getIntermediatePath(sInternalId); @@ -587,4 +597,14 @@ public class S3BitStoreService extends BaseBitStoreService { store.get(id); */ } + + /** + * Is this a registered bitstream? (not stored via this service originally) + * @param internalId + * @return + */ + public boolean isRegisteredBitstream(String internalId) { + return internalId.startsWith(REGISTERED_FLAG); + } + } 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 1a690afd86..89010a7308 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 @@ -26,6 +26,7 @@ import java.util.regex.Pattern; import javax.sql.DataSource; import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.dspace.core.Context; @@ -37,6 +38,7 @@ import org.dspace.workflow.factory.WorkflowServiceFactory; import org.flywaydb.core.Flyway; import org.flywaydb.core.api.FlywayException; import org.flywaydb.core.api.MigrationInfo; +import org.flywaydb.core.api.MigrationVersion; import org.flywaydb.core.api.callback.Callback; import org.flywaydb.core.api.configuration.FluentConfiguration; import org.flywaydb.core.internal.info.MigrationInfoDumper; @@ -73,7 +75,6 @@ public class DatabaseUtils { // Types of databases supported by DSpace. See getDbType() public static final String DBMS_POSTGRES = "postgres"; - public static final String DBMS_ORACLE = "oracle"; public static final String DBMS_H2 = "h2"; // Name of the table that Flyway uses for its migration history @@ -93,7 +94,7 @@ public class DatabaseUtils { // Usage checks if (argv.length < 1) { System.out.println("\nDatabase action argument is missing."); - System.out.println("Valid actions: 'test', 'info', 'migrate', 'repair', 'validate', " + + System.out.println("Valid actions: 'test', 'info', 'migrate', 'repair', 'skip', 'validate', " + "'update-sequences' or 'clean'"); System.out.println("\nOr, type 'database help' for more information.\n"); System.exit(1); @@ -111,280 +112,337 @@ public class DatabaseUtils { // *before* any other Flyway commands can be run. This is a safety check. FlywayUpgradeUtils.upgradeFlywayTable(flyway, dataSource.getConnection()); - // "test" = Test Database Connection - if (argv[0].equalsIgnoreCase("test")) { - // Try to connect to the database - System.out.println("\nAttempting to connect to database"); - try (Connection connection = dataSource.getConnection()) { - System.out.println("Connected successfully!"); + // Determine action param passed to "./dspace database" + switch (argv[0].toLowerCase(Locale.ENGLISH)) { + // "test" = Test Database Connection + case "test": + // Try to connect to the database + System.out.println("\nAttempting to connect to database"); + try (Connection connection = dataSource.getConnection()) { + System.out.println("Connected successfully!"); - // Print basic database connection information - printDBInfo(connection); + // Print basic database connection information + printDBInfo(connection); - // Print any database warnings/errors found (if any) - boolean issueFound = printDBIssues(connection); + // Print any database warnings/errors found (if any) + boolean issueFound = printDBIssues(connection); - // If issues found, exit with an error status (even if connection succeeded). - if (issueFound) { - System.exit(1); - } else { - System.exit(0); - } - } catch (SQLException sqle) { - System.err.println("\nError running 'test': "); - System.err.println(" - " + sqle); - System.err.println("\nPlease see the DSpace documentation for assistance.\n"); - sqle.printStackTrace(System.err); - System.exit(1); - } - } else if (argv[0].equalsIgnoreCase("info") || argv[0].equalsIgnoreCase("status")) { - try (Connection connection = dataSource.getConnection()) { - // Print basic Database info - printDBInfo(connection); - - // Get info table from Flyway - System.out.println("\n" + MigrationInfoDumper.dumpToAsciiTable(flyway.info().all())); - - // If Flyway is NOT yet initialized, also print the determined version information - // NOTE: search is case sensitive, as flyway table name is ALWAYS lowercase, - // See: http://flywaydb.org/documentation/faq.html#case-sensitive - if (!tableExists(connection, flyway.getConfiguration().getTable(), true)) { - System.out - .println("\nNOTE: This database is NOT yet initialized for auto-migrations (via Flyway)."); - // Determine which version of DSpace this looks like - String dbVersion = determineDBVersion(connection); - if (dbVersion != null) { - System.out - .println("\nYour database looks to be compatible with DSpace version " + dbVersion); - System.out.println( - "All upgrades *after* version " + dbVersion + " will be run during the next migration" + - "."); - System.out.println("\nIf you'd like to upgrade now, simply run 'dspace database migrate'."); - } - } - - // Print any database warnings/errors found (if any) - boolean issueFound = printDBIssues(connection); - - // If issues found, exit with an error status - if (issueFound) { - System.exit(1); - } else { - System.exit(0); - } - } catch (SQLException e) { - System.err.println("Info exception:"); - e.printStackTrace(System.err); - System.exit(1); - } - } else if (argv[0].equalsIgnoreCase("migrate")) { - try (Connection connection = dataSource.getConnection()) { - System.out.println("\nDatabase URL: " + connection.getMetaData().getURL()); - - // "migrate" allows for an OPTIONAL second argument (only one may be specified): - // - "ignored" = Also run any previously "ignored" migrations during the migration - // - "force" = Even if no pending migrations exist, still run a migration to trigger callbacks. - // - [version] = ONLY run migrations up to a specific DSpace version (ONLY FOR TESTING) - if (argv.length == 2) { - if (argv[1].equalsIgnoreCase("ignored")) { - System.out.println( - "Migrating database to latest version AND running previously \"Ignored\" " + - "migrations... (Check logs for details)"); - // Update the database to latest version, but set "outOfOrder=true" - // This will ensure any old migrations in the "ignored" state are now run - updateDatabase(dataSource, connection, null, true); - } else if (argv[1].equalsIgnoreCase("force")) { - updateDatabase(dataSource, connection, null, false, true); + // If issues found, exit with an error status (even if connection succeeded). + if (issueFound) { + System.exit(1); } else { - // Otherwise, we assume "argv[1]" is a valid migration version number - // This is only for testing! Never specify for Production! + System.exit(0); + } + } catch (SQLException sqle) { + System.err.println("\nError running 'test': "); + System.err.println(" - " + sqle); + System.err.println("\nPlease see the DSpace documentation for assistance.\n"); + sqle.printStackTrace(System.err); + System.exit(1); + } + break; + // "info" and "status" are identical and provide database info + case "info": + case "status": + try (Connection connection = dataSource.getConnection()) { + // Print basic Database info + printDBInfo(connection); + + // Get info table from Flyway + System.out.println("\n" + MigrationInfoDumper.dumpToAsciiTable(flyway.info().all())); + + // If Flyway is NOT yet initialized, also print the determined version information + // NOTE: search is case sensitive, as flyway table name is ALWAYS lowercase, + // See: http://flywaydb.org/documentation/faq.html#case-sensitive + if (!tableExists(connection, flyway.getConfiguration().getTable(), true)) { + System.out + .println("\nNOTE: This database is NOT yet initialized for auto-migrations " + + "(via Flyway)."); + // Determine which version of DSpace this looks like + String dbVersion = determineDBVersion(connection); + if (dbVersion != null) { + System.out + .println("\nYour database looks to be compatible with DSpace version " + dbVersion); + System.out.println( + "All upgrades *after* version " + dbVersion + " will be run during the next " + + "migration."); + System.out.println("\nIf you'd like to upgrade now, simply run 'dspace database " + + "migrate'."); + } + } + + // Print any database warnings/errors found (if any) + boolean issueFound = printDBIssues(connection); + + // If issues found, exit with an error status + if (issueFound) { + System.exit(1); + } else { + System.exit(0); + } + } catch (SQLException e) { + System.err.println("Info exception:"); + e.printStackTrace(System.err); + System.exit(1); + } + break; + // "migrate" = Run all pending database migrations + case "migrate": + try (Connection connection = dataSource.getConnection()) { + System.out.println("\nDatabase URL: " + connection.getMetaData().getURL()); + + // "migrate" allows for an OPTIONAL second argument (only one may be specified): + // - "ignored" = Also run any previously "ignored" migrations during the migration + // - "force" = Even if no pending migrations exist, still run migrate to trigger callbacks. + // - [version] = ONLY run migrations up to a specific DSpace version (ONLY FOR TESTING) + if (argv.length == 2) { + if (argv[1].equalsIgnoreCase("ignored")) { + System.out.println( + "Migrating database to latest version AND running previously \"Ignored\" " + + "migrations... (Check logs for details)"); + // Update the database to latest version, but set "outOfOrder=true" + // This will ensure any old migrations in the "ignored" state are now run + updateDatabase(dataSource, connection, null, true); + } else if (argv[1].equalsIgnoreCase("force")) { + updateDatabase(dataSource, connection, null, false, true); + } else { + // Otherwise, we assume "argv[1]" is a valid migration version number + // This is only for testing! Never specify for Production! + String migrationVersion = argv[1]; + BufferedReader input = new BufferedReader( + new InputStreamReader(System.in, StandardCharsets.UTF_8)); + + System.out.println( + "You've specified to migrate your database ONLY to version " + migrationVersion + + " ..."); + System.out.println( + "\nWARNING: In this mode, we DISABLE all callbacks, which means that you will " + + "need to manually update registries and manually run a reindex. This is " + + "because you are attempting to use an OLD version (" + migrationVersion + ") " + + "Database with a newer DSpace API. NEVER do this in a PRODUCTION scenario. " + + "The resulting database is only useful for migration testing.\n"); + + System.out.print( + "Are you SURE you only want to migrate your database to version " + + migrationVersion + "? [y/n]: "); + String choiceString = input.readLine(); + input.close(); + + if (choiceString.equalsIgnoreCase("y")) { + System.out.println( + "Migrating database ONLY to version " + migrationVersion + " ... " + + "(Check logs for details)"); + // Update the database, to the version specified. + updateDatabase(dataSource, connection, migrationVersion, false); + } else { + System.out.println("No action performed."); + } + } + } else { + System.out.println("Migrating database to latest version... " + + "(Check dspace logs for details)"); + updateDatabase(dataSource, connection); + } + System.out.println("Done."); + System.exit(0); + } catch (SQLException e) { + System.err.println("Migration exception:"); + e.printStackTrace(System.err); + System.exit(1); + } + break; + // "repair" = Run Flyway repair script + case "repair": + try (Connection connection = dataSource.getConnection();) { + System.out.println("\nDatabase URL: " + connection.getMetaData().getURL()); + System.out.println( + "Attempting to repair any previously failed migrations (or mismatched checksums) via " + + "FlywayDB... (Check dspace logs for details)"); + flyway.repair(); + System.out.println("Done."); + System.exit(0); + } catch (SQLException | FlywayException e) { + System.err.println("Repair exception:"); + e.printStackTrace(System.err); + System.exit(1); + } + break; + // "skip" = Skip a specific Flyway migration (by telling Flyway it succeeded) + case "skip": + try { + // "skip" requires a migration version to skip. Only that exact version will be skipped. + if (argv.length == 2) { String migrationVersion = argv[1]; + BufferedReader input = new BufferedReader( - new InputStreamReader(System.in, StandardCharsets.UTF_8)); - + new InputStreamReader(System.in, StandardCharsets.UTF_8)); System.out.println( - "You've specified to migrate your database ONLY to version " + migrationVersion + " " + + "You've specified to SKIP the migration with version='" + migrationVersion + "' " + "..."); - System.out.println( - "\nWARNING: In this mode, we DISABLE all callbacks, which means that you will need " + - "to manually update registries and manually run a reindex. This is because you " + - "are attempting to use an OLD version (" + migrationVersion + ") Database with " + - "a newer DSpace API. NEVER do this in a PRODUCTION scenario. The resulting " + - "database is only useful for migration testing.\n"); - System.out.print( - "Are you SURE you only want to migrate your database to version " + migrationVersion - + "? [y/n]: "); + "\nWARNING: You should only skip migrations which are no longer required or have " + + "become obsolete. Skipping a REQUIRED migration may result in DSpace failing " + + "to startup or function properly. Are you sure you want to SKIP the " + + "migration with version '" + migrationVersion + "'? [y/n]: "); String choiceString = input.readLine(); input.close(); if (choiceString.equalsIgnoreCase("y")) { System.out.println( - "Migrating database ONLY to version " + migrationVersion + " ... (Check logs for " + - "details)"); - // Update the database, to the version specified. - updateDatabase(dataSource, connection, migrationVersion, false); - } else { - System.out.println("No action performed."); + "Attempting to skip migration with version " + migrationVersion + " " + + "... (Check logs for details)"); + skipMigration(dataSource, migrationVersion); + } + } else { + System.out.println("The 'skip' command REQUIRES a version to be specified. " + + "Only that single migration will be skipped. For the list " + + "of migration versions use the 'info' command."); + } + } catch (IOException e) { + System.err.println("Exception when attempting to skip migration:"); + e.printStackTrace(System.err); + System.exit(1); + } + break; + // "validate" = Run Flyway validation to check for database errors/issues + case "validate": + try (Connection connection = dataSource.getConnection();) { + System.out.println("\nDatabase URL: " + connection.getMetaData().getURL()); + System.out + .println("Attempting to validate database status (and migration checksums) via " + + "FlywayDB..."); + flyway.validate(); + System.out.println("No errors thrown. Validation succeeded. (Check dspace logs for more " + + "details)"); + System.exit(0); + } catch (SQLException | FlywayException e) { + System.err.println("Validation exception:"); + e.printStackTrace(System.err); + System.exit(1); + } + break; + // "clean" = Run Flyway clean script + case "clean": + // If clean is disabled, return immediately + if (flyway.getConfiguration().isCleanDisabled()) { + System.out.println( + "\nWARNING: 'clean' command is currently disabled, as it is dangerous to run in " + + "Production scenarios!"); + System.out.println( + "\nIn order to run a 'clean' you first must enable it in your DSpace config by " + + "specifying 'db.cleanDisabled=false'.\n"); + System.exit(1); + } + + try (Connection connection = dataSource.getConnection()) { + String dbType = getDbType(connection); + + // Not all Postgres user accounts will be able to run a 'clean', + // as only 'superuser' accounts can remove the 'pgcrypto' extension. + if (dbType.equals(DBMS_POSTGRES)) { + // Check if database user has permissions suitable to run a clean + if (!PostgresUtils.checkCleanPermissions(connection)) { + String username = connection.getMetaData().getUserName(); + // Exit immediately, providing a descriptive error message + System.out.println( + "\nERROR: The database user '" + username + "' does not have sufficient " + + "privileges to run a 'database clean' (via Flyway)."); + System.out.println( + "\nIn order to run a 'clean', the database user MUST have 'superuser' privileges"); + System.out.println( + "OR the '" + PostgresUtils.PGCRYPTO + "' extension must be installed in a " + + "separate schema (see documentation)."); + System.out.println( + "\nOptionally, you could also manually remove the '" + PostgresUtils.PGCRYPTO + + "' extension first (DROP EXTENSION " + PostgresUtils.PGCRYPTO + + " CASCADE;), then rerun the 'clean'"); + System.exit(1); } } - } else { - System.out.println("Migrating database to latest version... (Check dspace logs for details)"); - updateDatabase(dataSource, connection); - } - System.out.println("Done."); - System.exit(0); - } catch (SQLException e) { - System.err.println("Migration exception:"); - e.printStackTrace(System.err); - System.exit(1); - } - } else if (argv[0].equalsIgnoreCase("repair")) { - // "repair" = Run Flyway repair script - try (Connection connection = dataSource.getConnection();) { - System.out.println("\nDatabase URL: " + connection.getMetaData().getURL()); - System.out.println( - "Attempting to repair any previously failed migrations (or mismatched checksums) via " + - "FlywayDB... (Check dspace logs for details)"); - flyway.repair(); - System.out.println("Done."); - System.exit(0); - } catch (SQLException | FlywayException e) { - System.err.println("Repair exception:"); - e.printStackTrace(System.err); - System.exit(1); - } - } else if (argv[0].equalsIgnoreCase("validate")) { - // "validate" = Run Flyway validation to check for database errors/issues + BufferedReader input = new BufferedReader(new InputStreamReader(System.in, + StandardCharsets.UTF_8)); - try (Connection connection = dataSource.getConnection();) { - System.out.println("\nDatabase URL: " + connection.getMetaData().getURL()); - System.out - .println("Attempting to validate database status (and migration checksums) via FlywayDB..."); - flyway.validate(); - System.out.println("No errors thrown. Validation succeeded. (Check dspace logs for more details)"); - System.exit(0); - } catch (SQLException | FlywayException e) { - System.err.println("Validation exception:"); - e.printStackTrace(System.err); - System.exit(1); - } - } else if (argv[0].equalsIgnoreCase("clean")) { - // "clean" = Run Flyway clean script - - // If clean is disabled, return immediately - if (flyway.getConfiguration().isCleanDisabled()) { - System.out.println( - "\nWARNING: 'clean' command is currently disabled, as it is dangerous to run in Production " + - "scenarios!"); - System.out.println( - "\nIn order to run a 'clean' you first must enable it in your DSpace config by specifying 'db" + - ".cleanDisabled=false'.\n"); - System.exit(1); - } - - try (Connection connection = dataSource.getConnection()) { - String dbType = getDbType(connection); - - // Not all Postgres user accounts will be able to run a 'clean', - // as only 'superuser' accounts can remove the 'pgcrypto' extension. - if (dbType.equals(DBMS_POSTGRES)) { - // Check if database user has permissions suitable to run a clean - if (!PostgresUtils.checkCleanPermissions(connection)) { - String username = connection.getMetaData().getUserName(); - // Exit immediately, providing a descriptive error message + System.out.println("\nDatabase URL: " + connection.getMetaData().getURL()); + System.out + .println("\nWARNING: ALL DATA AND TABLES IN YOUR DATABASE WILL BE PERMANENTLY DELETED.\n"); + System.out.println("There is NO turning back from this action. Backup your DB before " + + "continuing."); + if (dbType.equals(DBMS_POSTGRES)) { System.out.println( - "\nERROR: The database user '" + username + "' does not have sufficient privileges to" + - " run a 'database clean' (via Flyway)."); - System.out.println( - "\nIn order to run a 'clean', the database user MUST have 'superuser' privileges"); - System.out.println( - "OR the '" + PostgresUtils.PGCRYPTO + "' extension must be installed in a separate " + - "schema (see documentation)."); - System.out.println( - "\nOptionally, you could also manually remove the '" + PostgresUtils.PGCRYPTO + "' " + - "extension first (DROP EXTENSION " + PostgresUtils.PGCRYPTO + " CASCADE;), then " + - "rerun the 'clean'"); - System.exit(1); + "\nPOSTGRES WARNING: the '" + PostgresUtils.PGCRYPTO + "' extension will be dropped " + + "if it is in the same schema as the DSpace database.\n"); } - } + System.out.print("Do you want to PERMANENTLY DELETE everything from your database? [y/n]: "); + String choiceString = input.readLine(); + input.close(); - BufferedReader input = new BufferedReader(new InputStreamReader(System.in, StandardCharsets.UTF_8)); - - System.out.println("\nDatabase URL: " + connection.getMetaData().getURL()); - System.out - .println("\nWARNING: ALL DATA AND TABLES IN YOUR DATABASE WILL BE PERMANENTLY DELETED.\n"); - System.out.println("There is NO turning back from this action. Backup your DB before continuing."); - if (dbType.equals(DBMS_ORACLE)) { - System.out.println("\nORACLE WARNING: your RECYCLEBIN will also be PURGED.\n"); - } else if (dbType.equals(DBMS_POSTGRES)) { - System.out.println( - "\nPOSTGRES WARNING: the '" + PostgresUtils.PGCRYPTO + "' extension will be dropped if it" + - " is in the same schema as the DSpace database.\n"); - } - System.out.print("Do you want to PERMANENTLY DELETE everything from your database? [y/n]: "); - String choiceString = input.readLine(); - input.close(); - - if (choiceString.equalsIgnoreCase("y")) { - System.out.println("Scrubbing database clean... (Check dspace logs for details)"); - cleanDatabase(flyway, dataSource); - System.out.println("Done."); - System.exit(0); - } else { - System.out.println("No action performed."); - } - } catch (SQLException e) { - System.err.println("Clean exception:"); - e.printStackTrace(System.err); - System.exit(1); - } - } else if (argv[0].equalsIgnoreCase("update-sequences")) { - try (Connection connection = dataSource.getConnection()) { - String dbType = getDbType(connection); - String sqlfile = "org/dspace/storage/rdbms/sqlmigration/" + dbType + - "/update-sequences.sql"; - InputStream sqlstream = DatabaseUtils.class.getClassLoader().getResourceAsStream(sqlfile); - if (sqlstream != null) { - String s = IOUtils.toString(sqlstream, "UTF-8"); - if (!s.isEmpty()) { - System.out.println("Running " + sqlfile); - connection.createStatement().execute(s); - System.out.println("update-sequences complete"); + if (choiceString.equalsIgnoreCase("y")) { + System.out.println("Scrubbing database clean... (Check dspace logs for details)"); + cleanDatabase(flyway, dataSource); + System.out.println("Done."); + System.exit(0); } else { - System.err.println(sqlfile + " contains no SQL to execute"); + System.out.println("No action performed."); } - } else { - System.err.println(sqlfile + " not found"); + } catch (SQLException e) { + System.err.println("Clean exception:"); + e.printStackTrace(System.err); + System.exit(1); } - } - } else { - System.out.println("\nUsage: database [action]"); - System.out.println("Valid actions: 'test', 'info', 'migrate', 'repair', " + - "'update-sequences' or 'clean'"); - System.out.println( - " - test = Performs a test connection to database to " + - "validate connection settings"); - System.out.println( - " - info / status = Describe basic info/status about database, including validating the " + - "compatibility of this database"); - System.out.println( - " - migrate = Migrate the database to the latest version"); - System.out.println( - " - repair = Attempt to repair any previously failed database " + - "migrations or checksum mismatches (via Flyway repair)"); - System.out.println( - " - validate = Validate current database's migration status (via Flyway validate), " + - "validating all migration checksums."); - System.out.println( - " - update-sequences = Update database sequences after running AIP ingest."); - System.out.println( - " - clean = DESTROY all data and tables in database " + - "(WARNING there is no going back!). " + - "Requires 'db.cleanDisabled=false' setting in config."); - System.out.println(""); - System.exit(0); + break; + // "update-sequences" = Run DSpace's "update-sequences.sql" script + case "update-sequences": + try (Connection connection = dataSource.getConnection()) { + String dbType = getDbType(connection); + String sqlfile = "org/dspace/storage/rdbms/sqlmigration/" + dbType + + "/update-sequences.sql"; + InputStream sqlstream = DatabaseUtils.class.getClassLoader().getResourceAsStream(sqlfile); + if (sqlstream != null) { + String s = IOUtils.toString(sqlstream, StandardCharsets.UTF_8); + if (!s.isEmpty()) { + System.out.println("Running " + sqlfile); + connection.createStatement().execute(s); + System.out.println("update-sequences complete"); + } else { + System.err.println(sqlfile + " contains no SQL to execute"); + } + } else { + System.err.println(sqlfile + " not found"); + } + } + break; + // default = show help information + default: + System.out.println("\nUsage: database [action]"); + System.out.println("Valid actions: 'test', 'info', 'migrate', 'repair', 'skip', " + + "'validate', 'update-sequences' or 'clean'"); + System.out.println( + " - test = Performs a test connection to database to " + + "validate connection settings"); + System.out.println( + " - info / status = Describe basic info/status about database, including validating the " + + "compatibility of this database"); + System.out.println( + " - migrate = Migrate the database to the latest version"); + System.out.println( + " - repair = Attempt to repair any previously failed database " + + "migrations or checksum mismatches (via Flyway repair)"); + System.out.println( + " - skip [version] = Skip a single, pending or ignored migration, " + + "ensuring it never runs."); + System.out.println( + " - validate = Validate current database's migration status (via Flyway validate), " + + "validating all migration checksums."); + System.out.println( + " - update-sequences = Update database sequences after running AIP ingest."); + System.out.println( + " - clean = DESTROY all data and tables in database " + + "(WARNING there is no going back!). " + + "Requires 'db.cleanDisabled=false' setting in config."); + System.out.println(""); + System.exit(0); + break; } } catch (Exception e) { @@ -406,11 +464,10 @@ public class DatabaseUtils { DatabaseMetaData meta = connection.getMetaData(); String dbType = getDbType(connection); System.out.println("\nDatabase Type: " + dbType); - if (dbType.equals(DBMS_ORACLE)) { - System.out.println("===================================="); - System.out.println("WARNING: Oracle support is deprecated!"); - System.out.println("See https://github.com/DSpace/DSpace/issues/8214"); - System.out.println("====================================="); + if (!dbType.equals(DBMS_POSTGRES) && !dbType.equals(DBMS_H2)) { + System.err.println("===================================="); + System.err.println("ERROR: Database type " + dbType + " is UNSUPPORTED!"); + System.err.println("====================================="); } System.out.println("Database URL: " + meta.getURL()); System.out.println("Database Schema: " + getSchemaName(connection)); @@ -545,10 +602,6 @@ public class DatabaseUtils { String dbType = getDbType(connection); connection.close(); - if (dbType.equals(DBMS_ORACLE)) { - log.warn("ORACLE SUPPORT IS DEPRECATED! See https://github.com/DSpace/DSpace/issues/8214"); - } - // Determine location(s) where Flyway will load all DB migrations ArrayList scriptLocations = new ArrayList<>(); @@ -786,6 +839,89 @@ public class DatabaseUtils { } } + /** + * Skips the given migration by marking it as "successful" in the Flyway table. This ensures + * the given migration will never be run again. + *

+ * WARNING: Skipping a required migration can result in unexpected errors. Make sure the migration is + * not required (or obsolete) before skipping it. + * @param dataSource current DataSource + * @param skipVersion version of migration to skip + * @throws SQLException if error occurs + */ + private static synchronized void skipMigration(DataSource dataSource, + String skipVersion) throws SQLException { + if (null == dataSource) { + throw new SQLException("The datasource is a null reference -- cannot continue."); + } + + try (Connection connection = dataSource.getConnection()) { + // Setup Flyway API against our database + FluentConfiguration flywayConfiguration = setupFlyway(dataSource); + + // In order to allow for skipping "Ignored" migrations, we MUST set "outOfOrder=true". + // (Otherwise Ignored migrations never appear in the pending list) + flywayConfiguration.outOfOrder(true); + + // Initialized Flyway object based on this configuration + Flyway flyway = flywayConfiguration.load(); + + // Find the migration we are skipping in the list of pending migrations + boolean foundMigration = false; + for (MigrationInfo migration : flyway.info().pending()) { + // If this migration matches our "skipVersion" + if (migration.getVersion().equals(MigrationVersion.fromVersion(skipVersion))) { + foundMigration = true; + System.out.println("Found migration matching version='" + skipVersion + "'. " + + "Changing state to 'Success' in order to skip it."); + + PreparedStatement statement = null; + try { + // Create SQL Insert which will log this migration as having already been run. + String INSERT_SQL = "INSERT INTO " + FLYWAY_TABLE + " " + + "(" + + "installed_rank, version, description, type, script, " + + "checksum, installed_by, execution_time, success" + + ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"; + statement = connection.prepareStatement(INSERT_SQL); + // installed_rank + statement.setInt(1, getNextFlywayInstalledRank(flyway)); + // version + statement.setString(2, migration.getVersion().getVersion()); + // description + statement.setString(3, migration.getDescription()); + // type + statement.setString(4, migration.getType().toString()); + // script + statement.setString(5, migration.getScript()); + // checksum + statement.setInt(6, migration.getChecksum()); + // installed_by + statement.setString(7, getDBUserName(connection)); + // execution_time is set to zero as we didn't really execute it + statement.setInt(8, 0); + // success=true tells Flyway this migration no longer needs to be run. + statement.setBoolean(9, true); + + // Run the INSERT + statement.executeUpdate(); + } finally { + if (statement != null && !statement.isClosed()) { + statement.close(); + } + } + } + } + if (!foundMigration) { + System.err.println("Could not find migration to skip! " + + "No 'Pending' or 'Ignored' migrations match version='" + skipVersion + "'"); + } + } catch (FlywayException fe) { + // If any FlywayException (Runtime) is thrown, change it to a SQLException + throw new SQLException("Flyway error occurred", fe); + } + } + /** * Clean the existing database, permanently removing all data and tables *

@@ -802,26 +938,6 @@ public class DatabaseUtils { // First, run Flyway's clean command on database. // For MOST database types, this takes care of everything flyway.clean(); - - try (Connection connection = dataSource.getConnection()) { - // Get info about which database type we are using - String dbType = getDbType(connection); - - // If this is Oracle, the only way to entirely clean the database - // is to also purge the "Recyclebin". See: - // http://docs.oracle.com/cd/B19306_01/server.102/b14200/statements_9018.htm - if (dbType.equals(DBMS_ORACLE)) { - PreparedStatement statement = null; - try { - statement = connection.prepareStatement("PURGE RECYCLEBIN"); - statement.executeQuery(); - } finally { - if (statement != null && !statement.isClosed()) { - statement.close(); - } - } - } - } } catch (FlywayException fe) { // If any FlywayException (Runtime) is thrown, change it to a SQLException throw new SQLException("Flyway clean error occurred", fe); @@ -1070,11 +1186,6 @@ public class DatabaseUtils { // We need to filter by schema in PostgreSQL schemaFilter = true; break; - case DBMS_ORACLE: - // Oracle specific query for a sequence owned by our current DSpace user - // NOTE: No need to filter by schema for Oracle, as Schema = User - sequenceSQL = "SELECT COUNT(1) FROM user_sequences WHERE sequence_name=?"; - break; case DBMS_H2: // In H2, sequences are listed in the "information_schema.sequences" table // SEE: http://www.h2database.com/html/grammar.html#information_schema @@ -1178,11 +1289,6 @@ public class DatabaseUtils { // For PostgreSQL, the default schema is named "public" // See: http://www.postgresql.org/docs/9.0/static/ddl-schemas.html schema = "public"; - } else if (dbType.equals(DBMS_ORACLE)) { - // For Oracle, default schema is actually the user account - // See: http://stackoverflow.com/a/13341390 - DatabaseMetaData meta = connection.getMetaData(); - schema = meta.getUserName(); } else { // For H2 (in memory), there is no such thing as a schema schema = null; @@ -1192,6 +1298,34 @@ public class DatabaseUtils { return schema; } + /** + * Get the Database User Name in use by this Connection. + * + * @param connection Current Database Connection + * @return User name as a string, or "null" if cannot be determined or unspecified + * @throws SQLException An exception that provides information on a database access error or other errors. + */ + public static String getDBUserName(Connection connection) + throws SQLException { + String username = null; + + // Try to get the schema from the DB connection itself. + // As long as the Database driver supports JDBC4.1, there should be a getSchema() method + // If this method is unimplemented or doesn't exist, it will throw an exception (likely an AbstractMethodError) + try { + username = connection.getMetaData().getUserName(); + } catch (Exception | AbstractMethodError e) { + // ignore + } + + // If we don't know our schema, let's try the schema in the DSpace configuration + if (StringUtils.isBlank(username)) { + username = canonicalize(connection, DSpaceServicesFactory.getInstance().getConfigurationService() + .getProperty("db.username")); + } + return username; + } + /** * Return the canonical name for a database identifier based on whether this * database defaults to storing identifiers in uppercase or lowercase. @@ -1380,8 +1514,6 @@ public class DatabaseUtils { String dbms_lc = prodName.toLowerCase(Locale.ROOT); if (dbms_lc.contains("postgresql")) { return DBMS_POSTGRES; - } else if (dbms_lc.contains("oracle")) { - return DBMS_ORACLE; } else if (dbms_lc.contains("h2")) { // Used for unit testing only return DBMS_H2; @@ -1443,4 +1575,22 @@ public class DatabaseUtils { } return null; } + + /** + * Determine next valid "installed_rank" value from Flyway, based on the "installed_rank" of the + * last applied migration. + * @param flyway currently loaded Flyway + * @return next installed rank value + */ + private static int getNextFlywayInstalledRank(Flyway flyway) throws FlywayException { + // Load all applied migrations + MigrationInfo[] appliedMigrations = flyway.info().applied(); + // If no applied migrations, throw an error. + // This should never happen, but this would mean Flyway is not installed or initialized + if (ArrayUtils.isEmpty(appliedMigrations)) { + throw new FlywayException("Cannot determine next 'installed_rank' as no applied migrations exist"); + } + // Find the last migration in the list, and increment its "installed_rank" by one. + return appliedMigrations[appliedMigrations.length - 1].getInstalledRank() + 1; + } } diff --git a/dspace-api/src/main/java/org/dspace/storage/rdbms/hibernate/DatabaseAwareLobType.java b/dspace-api/src/main/java/org/dspace/storage/rdbms/hibernate/DatabaseAwareLobType.java deleted file mode 100644 index 95939f9902..0000000000 --- a/dspace-api/src/main/java/org/dspace/storage/rdbms/hibernate/DatabaseAwareLobType.java +++ /dev/null @@ -1,57 +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.storage.rdbms.hibernate; - -import org.apache.commons.lang.StringUtils; -import org.dspace.services.ConfigurationService; -import org.dspace.services.factory.DSpaceServicesFactory; -import org.hibernate.type.AbstractSingleColumnStandardBasicType; -import org.hibernate.type.descriptor.java.StringTypeDescriptor; -import org.hibernate.type.descriptor.sql.ClobTypeDescriptor; -import org.hibernate.type.descriptor.sql.LongVarcharTypeDescriptor; -import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; - -/** - * A Hibernate @Type used to properly support the CLOB in both Postgres and Oracle. - * PostgreSQL doesn't have a CLOB type, instead it's a TEXT field. - * Normally, you'd use org.hibernate.type.TextType to support TEXT, but that won't work for Oracle. - * https://github.com/hibernate/hibernate-orm/blob/5.6/hibernate-core/src/main/java/org/hibernate/type/TextType.java - * - * This Type checks if we are using PostgreSQL. - * If so, it configures Hibernate to map CLOB to LongVarChar (same as org.hibernate.type.TextType) - * If not, it uses default CLOB (which works for other databases). - */ -public class DatabaseAwareLobType extends AbstractSingleColumnStandardBasicType { - - public static final DatabaseAwareLobType INSTANCE = new DatabaseAwareLobType(); - - public DatabaseAwareLobType() { - super( getDbDescriptor(), StringTypeDescriptor.INSTANCE ); - } - - public static SqlTypeDescriptor getDbDescriptor() { - if ( isPostgres() ) { - return LongVarcharTypeDescriptor.INSTANCE; - } else { - return ClobTypeDescriptor.DEFAULT; - } - } - - private static boolean isPostgres() { - ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); - String dbDialect = configurationService.getProperty("db.dialect"); - - return StringUtils.containsIgnoreCase(dbDialect, "PostgreSQL"); - } - - @Override - public String getName() { - return "database_aware_lob"; - } -} - 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 842fc15e16..f0c4e4e179 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 @@ -78,13 +78,6 @@ public class MigrationUtils { constraintName += "_" + StringUtils.lowerCase(constraintSuffix); cascade = true; break; - case "oracle": - // In Oracle, constraints are listed in the USER_CONS_COLUMNS table - constraintNameSQL = "SELECT CONSTRAINT_NAME " + - "FROM USER_CONS_COLUMNS " + - "WHERE TABLE_NAME = ? AND COLUMN_NAME = ?"; - cascade = true; - break; case "h2": // In H2, column constraints are listed in the "INFORMATION_SCHEMA.KEY_COLUMN_USAGE" table constraintNameSQL = "SELECT DISTINCT CONSTRAINT_NAME " + @@ -160,9 +153,6 @@ public class MigrationUtils { case "postgresql": dropTableSQL = "DROP TABLE IF EXISTS " + tableName + " CASCADE"; break; - case "oracle": - dropTableSQL = "DROP TABLE " + tableName + " CASCADE CONSTRAINTS"; - break; case "h2": dropTableSQL = "DROP TABLE IF EXISTS " + tableName + " CASCADE"; break; @@ -208,9 +198,6 @@ public class MigrationUtils { case "postgresql": dropSequenceSQL = "DROP SEQUENCE IF EXISTS " + sequenceName; break; - case "oracle": - dropSequenceSQL = "DROP SEQUENCE " + sequenceName; - break; case "h2": dropSequenceSQL = "DROP SEQUENCE IF EXISTS " + sequenceName; break; @@ -256,9 +243,6 @@ public class MigrationUtils { case "postgresql": dropViewSQL = "DROP VIEW IF EXISTS " + viewName + " CASCADE"; break; - case "oracle": - dropViewSQL = "DROP VIEW " + viewName + " CASCADE CONSTRAINTS"; - break; case "h2": dropViewSQL = "DROP VIEW IF EXISTS " + viewName + " CASCADE"; break; 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 56c5b474d9..758e745ddc 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 @@ -19,10 +19,9 @@ import org.flywaydb.core.api.migration.Context; * of the "community" table. This is necessary for the upgrade from 1.3 to 1.4 *

* This class was created because the names of database constraints differs based - * on the type of database (Postgres vs. Oracle vs. H2). As such, it becomes difficult + * on the type of database (Postgres vs. H2). As such, it becomes difficult * to write simple SQL which will work for multiple database types (especially - * since unit tests require H2 and the syntax for H2 is different from either - * Oracle or Postgres). + * 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: 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 6d82055e53..37100a17f9 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 @@ -19,10 +19,9 @@ import org.flywaydb.core.api.migration.Context; * from 1.5 to 1.6 *

* This class was created because the names of database constraints differs based - * on the type of database (Postgres vs. Oracle vs. H2). As such, it becomes difficult + * on the type of database (Postgres vs. H2). As such, it becomes difficult * to write simple SQL which will work for multiple database types (especially - * since unit tests require H2 and the syntax for H2 is different from either - * Oracle or Postgres). + * 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: 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 ea72d99b6e..8e2be91127 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 @@ -20,10 +20,9 @@ import org.flywaydb.core.api.migration.Context; * this column must be renamed to "resource_id". *

* This class was created because the names of database constraints differs based - * on the type of database (Postgres vs. Oracle vs. H2). As such, it becomes difficult + * on the type of database (Postgres vs. H2). As such, it becomes difficult * to write simple SQL which will work for multiple database types (especially - * since unit tests require H2 and the syntax for H2 is different from either - * Oracle or Postgres). + * 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: 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 b3306a9fc9..0361e68053 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 @@ -67,8 +67,6 @@ public class V5_0_2014_11_04__Enable_XMLWorkflow_Migration String dbFileLocation = null; if (dbtype.toLowerCase().contains("postgres")) { dbFileLocation = "postgres"; - } else if (dbtype.toLowerCase().contains("oracle")) { - dbFileLocation = "oracle"; } else if (dbtype.toLowerCase().contains("h2")) { dbFileLocation = "h2"; } diff --git a/dspace-api/src/main/java/org/dspace/storage/rdbms/xmlworkflow/V6_0_2015_09_01__DS_2701_Enable_XMLWorkflow_Migration.java b/dspace-api/src/main/java/org/dspace/storage/rdbms/xmlworkflow/V6_0_2015_09_01__DS_2701_Enable_XMLWorkflow_Migration.java index 9aa0f4877c..4c1cf33653 100644 --- a/dspace-api/src/main/java/org/dspace/storage/rdbms/xmlworkflow/V6_0_2015_09_01__DS_2701_Enable_XMLWorkflow_Migration.java +++ b/dspace-api/src/main/java/org/dspace/storage/rdbms/xmlworkflow/V6_0_2015_09_01__DS_2701_Enable_XMLWorkflow_Migration.java @@ -46,8 +46,6 @@ public class V6_0_2015_09_01__DS_2701_Enable_XMLWorkflow_Migration extends BaseJ String dbFileLocation = null; if (dbtype.toLowerCase().contains("postgres")) { dbFileLocation = "postgres"; - } else if (dbtype.toLowerCase().contains("oracle")) { - dbFileLocation = "oracle"; } else if (dbtype.toLowerCase().contains("h2")) { dbFileLocation = "h2"; } diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/ContentGenerator.java b/dspace-api/src/main/java/org/dspace/subscriptions/ContentGenerator.java new file mode 100644 index 0000000000..a913f2504a --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/subscriptions/ContentGenerator.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.subscriptions; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.apache.commons.lang.StringUtils.EMPTY; + +import java.io.ByteArrayOutputStream; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.dspace.content.Item; +import org.dspace.content.crosswalk.StreamDisseminationCrosswalk; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.dspace.core.Email; +import org.dspace.core.I18nUtil; +import org.dspace.discovery.IndexableObject; +import org.dspace.eperson.EPerson; +import org.dspace.subscriptions.service.SubscriptionGenerator; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Implementation class of SubscriptionGenerator + * which will handle the logic of sending the emails + * in case of 'content' subscriptionType + */ +@SuppressWarnings("rawtypes") +public class ContentGenerator implements SubscriptionGenerator { + + private final Logger log = LogManager.getLogger(ContentGenerator.class); + + @SuppressWarnings("unchecked") + private Map entityType2Disseminator = new HashMap(); + + @Autowired + private ItemService itemService; + + @Override + public void notifyForSubscriptions(Context context, EPerson ePerson, + List indexableComm, + List indexableColl) { + try { + if (Objects.nonNull(ePerson)) { + Locale supportedLocale = I18nUtil.getEPersonLocale(ePerson); + Email email = Email.getEmail(I18nUtil.getEmailFilename(supportedLocale, "subscriptions_content")); + email.addRecipient(ePerson.getEmail()); + email.addArgument(generateBodyMail(context, indexableComm)); + email.addArgument(generateBodyMail(context, indexableColl)); + email.send(); + } + } catch (Exception e) { + log.error(e.getMessage(), e); + log.warn("Cannot email user eperson_id: {} eperson_email: {}", ePerson::getID, ePerson::getEmail); + } + } + + private String generateBodyMail(Context context, List indexableObjects) { + try { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + out.write("\n".getBytes(UTF_8)); + if (indexableObjects.size() > 0) { + for (IndexableObject indexableObject : indexableObjects) { + out.write("\n".getBytes(UTF_8)); + Item item = (Item) indexableObject.getIndexedObject(); + String entityType = itemService.getEntityTypeLabel(item); + Optional.ofNullable(entityType2Disseminator.get(entityType)) + .orElseGet(() -> entityType2Disseminator.get("Item")) + .disseminate(context, item, out); + } + return out.toString(); + } else { + out.write("No items".getBytes(UTF_8)); + } + return out.toString(); + } catch (Exception e) { + log.error(e.getMessage(), e); + } + return EMPTY; + } + + public void setEntityType2Disseminator(Map entityType2Disseminator) { + this.entityType2Disseminator = entityType2Disseminator; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotification.java b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotification.java new file mode 100644 index 0000000000..b429ecbd46 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotification.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.subscriptions; + +import java.sql.SQLException; +import java.util.Objects; +import java.util.UUID; + +import org.apache.commons.cli.ParseException; +import org.apache.commons.lang3.StringUtils; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.FrequencyType; +import org.dspace.eperson.factory.EPersonServiceFactory; +import org.dspace.scripts.DSpaceRunnable; +import org.dspace.utils.DSpace; + +/** + * Implementation of {@link DSpaceRunnable} to find subscribed objects and send notification mails about them + * + * @author alba aliu + */ +public class SubscriptionEmailNotification + extends DSpaceRunnable> { + + private Context context; + private SubscriptionEmailNotificationService subscriptionEmailNotificationService; + + @Override + @SuppressWarnings("unchecked") + public SubscriptionEmailNotificationConfiguration getScriptConfiguration() { + return new DSpace().getServiceManager().getServiceByName("subscription-send", + SubscriptionEmailNotificationConfiguration.class); + } + + @Override + public void setup() throws ParseException { + this.subscriptionEmailNotificationService = new DSpace().getServiceManager().getServiceByName( + SubscriptionEmailNotificationServiceImpl.class.getName(), SubscriptionEmailNotificationServiceImpl.class); + } + + @Override + public void internalRun() throws Exception { + assignCurrentUserInContext(); + assignSpecialGroupsInContext(); + String frequencyOption = commandLine.getOptionValue("f"); + if (StringUtils.isBlank(frequencyOption)) { + throw new IllegalArgumentException("Option --frequency (-f) must be set"); + } + + if (!FrequencyType.isSupportedFrequencyType(frequencyOption)) { + throw new IllegalArgumentException( + "Option f must be one of following values D(Day), W(Week) or M(Month)"); + } + subscriptionEmailNotificationService.perform(getContext(), handler, "content", frequencyOption); + } + + private void assignCurrentUserInContext() throws SQLException { + context = new Context(); + UUID uuid = getEpersonIdentifier(); + if (Objects.nonNull(uuid)) { + EPerson ePerson = EPersonServiceFactory.getInstance().getEPersonService().find(context, uuid); + context.setCurrentUser(ePerson); + } + } + + private void assignSpecialGroupsInContext() throws SQLException { + for (UUID uuid : handler.getSpecialGroups()) { + context.setSpecialGroup(uuid); + } + } + + public SubscriptionEmailNotificationService getSubscriptionEmailNotificationService() { + return subscriptionEmailNotificationService; + } + + public void setSubscriptionEmailNotificationService(SubscriptionEmailNotificationService notificationService) { + this.subscriptionEmailNotificationService = notificationService; + } + + public Context getContext() { + return context; + } + + public void setContext(Context context) { + this.context = context; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationCli.java b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationCli.java new file mode 100644 index 0000000000..338e7ff0e1 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationCli.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.subscriptions; + +/** + * Extension of {@link SubscriptionEmailNotification} for CLI. + */ +public class SubscriptionEmailNotificationCli extends SubscriptionEmailNotification { + +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationCliScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationCliScriptConfiguration.java new file mode 100644 index 0000000000..f0eb2fd5c8 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationCliScriptConfiguration.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.subscriptions; + +/** + * Extension of {@link SubscriptionEmailNotificationCli} for CLI. + */ +public class SubscriptionEmailNotificationCliScriptConfiguration + extends SubscriptionEmailNotificationConfiguration { + +} diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationConfiguration.java b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationConfiguration.java new file mode 100644 index 0000000000..52685b563d --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationConfiguration.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.subscriptions; + +import java.sql.SQLException; +import java.util.Objects; + +import org.apache.commons.cli.Options; +import org.dspace.authorize.AuthorizeServiceImpl; +import org.dspace.core.Context; +import org.dspace.scripts.DSpaceRunnable; +import org.dspace.scripts.configuration.ScriptConfiguration; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Implementation of {@link DSpaceRunnable} to find subscribed objects and send notification mails about them + */ +public class SubscriptionEmailNotificationConfiguration extends ScriptConfiguration { + + private Class dspaceRunnableClass; + + @Autowired + private AuthorizeServiceImpl authorizeService; + + @Override + public boolean isAllowedToExecute(Context context) { + try { + return authorizeService.isAdmin(context); + } catch (SQLException e) { + throw new RuntimeException("SQLException occurred when checking if the current user is an admin", e); + } + } + + @Override + public Options getOptions() { + if (Objects.isNull(options)) { + Options options = new Options(); + options.addOption("f", "frequency", true, + "Subscription frequency. Valid values include: D (Day), W (Week) and M (Month)"); + options.getOption("f").setRequired(true); + super.options = options; + } + return options; + } + + @Override + public Class getDspaceRunnableClass() { + return dspaceRunnableClass; + } + + @Override + public void setDspaceRunnableClass(Class dspaceRunnableClass) { + this.dspaceRunnableClass = dspaceRunnableClass; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationService.java b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationService.java new file mode 100644 index 0000000000..9527223509 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationService.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.subscriptions; + +import java.util.Set; + +import org.dspace.core.Context; +import org.dspace.scripts.handler.DSpaceRunnableHandler; + +/** + * Service interface class for the subscription e-mail notification services + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com) + */ +public interface SubscriptionEmailNotificationService { + + /** + * Performs sending of e-mails to subscribers by frequency value and SubscriptionType + * + * @param context DSpace context object + * @param handler Applicable DSpaceRunnableHandler + * @param subscriptionType Currently supported only "content" + * @param frequency Valid values include: D (Day), W (Week) and M (Month) + */ + public void perform(Context context, DSpaceRunnableHandler handler, String subscriptionType, String frequency); + + /** + * returns a set of supported SubscriptionTypes + */ + public Set getSupportedSubscriptionTypes(); + +} diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationServiceImpl.java b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationServiceImpl.java new file mode 100644 index 0000000000..8fb01cd36e --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationServiceImpl.java @@ -0,0 +1,172 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.subscriptions; + +import static org.dspace.core.Constants.COLLECTION; +import static org.dspace.core.Constants.COMMUNITY; +import static org.dspace.core.Constants.READ; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.DSpaceObject; +import org.dspace.content.Item; +import org.dspace.core.Context; +import org.dspace.discovery.IndexableObject; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.Subscription; +import org.dspace.eperson.service.SubscribeService; +import org.dspace.scripts.DSpaceRunnable; +import org.dspace.scripts.handler.DSpaceRunnableHandler; +import org.dspace.subscriptions.service.DSpaceObjectUpdates; +import org.dspace.subscriptions.service.SubscriptionGenerator; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Implementation of {@link DSpaceRunnable} to find subscribed objects and send notification mails about them + * + * @author alba aliu + */ +public class SubscriptionEmailNotificationServiceImpl implements SubscriptionEmailNotificationService { + + private static final Logger log = LogManager.getLogger(SubscriptionEmailNotificationServiceImpl.class); + + private Map contentUpdates = new HashMap<>(); + @SuppressWarnings("rawtypes") + private Map subscriptionType2generators = new HashMap<>(); + + @Autowired + private AuthorizeService authorizeService; + @Autowired + private SubscribeService subscribeService; + + @SuppressWarnings("rawtypes") + public SubscriptionEmailNotificationServiceImpl(Map contentUpdates, + Map subscriptionType2generators) { + this.contentUpdates = contentUpdates; + this.subscriptionType2generators = subscriptionType2generators; + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + public void perform(Context context, DSpaceRunnableHandler handler, String subscriptionType, String frequency) { + List communityItems = new ArrayList<>(); + List collectionsItems = new ArrayList<>(); + try { + List subscriptions = + findAllSubscriptionsBySubscriptionTypeAndFrequency(context, subscriptionType, frequency); + // Here is verified if SubscriptionType is "content" Or "statistics" as them are configured + if (subscriptionType2generators.keySet().contains(subscriptionType)) { + // the list of the person who has subscribed + int iterator = 0; + for (Subscription subscription : subscriptions) { + DSpaceObject dSpaceObject = subscription.getDSpaceObject(); + EPerson ePerson = subscription.getEPerson(); + + if (!authorizeService.authorizeActionBoolean(context, ePerson, dSpaceObject, READ, true)) { + iterator++; + continue; + } + + if (dSpaceObject.getType() == COMMUNITY) { + List indexableCommunityItems = contentUpdates + .get(Community.class.getSimpleName().toLowerCase()) + .findUpdates(context, dSpaceObject, frequency); + communityItems.addAll(getItems(context, ePerson, indexableCommunityItems)); + } else if (dSpaceObject.getType() == COLLECTION) { + List indexableCollectionItems = contentUpdates + .get(Collection.class.getSimpleName().toLowerCase()) + .findUpdates(context, dSpaceObject, frequency); + collectionsItems.addAll(getItems(context, ePerson, indexableCollectionItems)); + } else { + log.warn("found an invalid DSpace Object type ({}) among subscriptions to send", + dSpaceObject.getType()); + continue; + } + + if (iterator < subscriptions.size() - 1) { + // as the subscriptions are ordered by eperson id, so we send them by ePerson + if (ePerson.equals(subscriptions.get(iterator + 1).getEPerson())) { + iterator++; + continue; + } else { + subscriptionType2generators.get(subscriptionType) + .notifyForSubscriptions(context, ePerson, communityItems, collectionsItems); + communityItems.clear(); + collectionsItems.clear(); + } + } else { + //in the end of the iteration + subscriptionType2generators.get(subscriptionType) + .notifyForSubscriptions(context, ePerson, communityItems, collectionsItems); + } + iterator++; + } + } else { + throw new IllegalArgumentException("Currently this SubscriptionType:" + subscriptionType + + " is not supported!"); + } + } catch (Exception e) { + log.error(e.getMessage(), e); + handler.handleException(e); + context.abort(); + } + } + + @SuppressWarnings("rawtypes") + private List getItems(Context context, EPerson ePerson, List indexableItems) + throws SQLException { + List items = new ArrayList(); + for (IndexableObject indexableitem : indexableItems) { + Item item = (Item) indexableitem.getIndexedObject(); + if (authorizeService.authorizeActionBoolean(context, ePerson, item, READ, true)) { + items.add(indexableitem); + } + } + return items; + } + + /** + * Return all Subscriptions by subscriptionType and frequency ordered by ePerson ID + * if there are none it returns an empty list + * + * @param context DSpace context + * @param subscriptionType Could be "content" or "statistics". NOTE: in DSpace we have only "content" + * @param frequency Could be "D" stand for Day, "W" stand for Week, and "M" stand for Month + * @return + */ + private List findAllSubscriptionsBySubscriptionTypeAndFrequency(Context context, + String subscriptionType, String frequency) { + try { + return subscribeService.findAllSubscriptionsBySubscriptionTypeAndFrequency(context, subscriptionType, + frequency) + .stream() + .sorted(Comparator.comparing(s -> s.getEPerson().getID())) + .collect(Collectors.toList()); + } catch (SQLException e) { + log.error(e.getMessage(), e); + } + return new ArrayList(); + } + + @Override + public Set getSupportedSubscriptionTypes() { + return subscriptionType2generators.keySet(); + } + +} diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/objectupdates/CollectionUpdates.java b/dspace-api/src/main/java/org/dspace/subscriptions/objectupdates/CollectionUpdates.java new file mode 100644 index 0000000000..12d056f368 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/subscriptions/objectupdates/CollectionUpdates.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.subscriptions.objectupdates; + +import java.util.List; + +import org.dspace.content.DSpaceObject; +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.eperson.FrequencyType; +import org.dspace.subscriptions.service.DSpaceObjectUpdates; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Class which will be used to find + * all collection objects updated related with subscribed DSO + * + * @author Alba Aliu + */ +public class CollectionUpdates implements DSpaceObjectUpdates { + + @Autowired + private SearchService searchService; + + @Override + @SuppressWarnings("rawtypes") + public List findUpdates(Context context, DSpaceObject dSpaceObject, String frequency) + throws SearchServiceException { + DiscoverQuery discoverQuery = new DiscoverQuery(); + getDefaultFilterQueries().stream().forEach(fq -> discoverQuery.addFilterQueries(fq)); + discoverQuery.addFilterQueries("location.coll:(" + dSpaceObject.getID() + ")"); + discoverQuery.addFilterQueries("lastModified:" + FrequencyType.findLastFrequency(frequency)); + DiscoverResult discoverResult = searchService.search(context, discoverQuery); + return discoverResult.getIndexableObjects(); + } + +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/objectupdates/CommunityUpdates.java b/dspace-api/src/main/java/org/dspace/subscriptions/objectupdates/CommunityUpdates.java new file mode 100644 index 0000000000..0ae80d287a --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/subscriptions/objectupdates/CommunityUpdates.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.subscriptions.objectupdates; + +import java.util.List; + +import org.dspace.content.DSpaceObject; +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.eperson.FrequencyType; +import org.dspace.subscriptions.service.DSpaceObjectUpdates; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Class which will be used to find + * all community objects updated related with subscribed DSO + * + * @author Alba Aliu + */ +public class CommunityUpdates implements DSpaceObjectUpdates { + + @Autowired + private SearchService searchService; + + @Override + @SuppressWarnings("rawtypes") + public List findUpdates(Context context, DSpaceObject dSpaceObject, String frequency) + throws SearchServiceException { + DiscoverQuery discoverQuery = new DiscoverQuery(); + getDefaultFilterQueries().stream().forEach(fq -> discoverQuery.addFilterQueries(fq)); + discoverQuery.addFilterQueries("location.comm:(" + dSpaceObject.getID() + ")"); + discoverQuery.addFilterQueries("lastModified:" + FrequencyType.findLastFrequency(frequency)); + DiscoverResult discoverResult = searchService.search(context, discoverQuery); + return discoverResult.getIndexableObjects(); + } + +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/service/DSpaceObjectUpdates.java b/dspace-api/src/main/java/org/dspace/subscriptions/service/DSpaceObjectUpdates.java new file mode 100644 index 0000000000..ec09b2a45f --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/subscriptions/service/DSpaceObjectUpdates.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.subscriptions.service; + +import java.util.Arrays; +import java.util.List; + +import org.dspace.content.DSpaceObject; +import org.dspace.content.Item; +import org.dspace.core.Context; +import org.dspace.discovery.IndexableObject; +import org.dspace.discovery.SearchServiceException; + +/** + * Interface class which will be used to find all objects updated related with subscribed DSO + * + * @author Alba Aliu + */ +public interface DSpaceObjectUpdates { + + /** + * Send an email to some addresses, concerning a Subscription, using a given dso. + * + * @param context current DSpace session. + */ + @SuppressWarnings("rawtypes") + public List findUpdates(Context context, DSpaceObject dSpaceObject, String frequency) + throws SearchServiceException; + + default List getDefaultFilterQueries() { + return Arrays.asList("search.resourcetype:" + Item.class.getSimpleName(), + "-discoverable:" + false, + "-withdrawn:" + true); + } + +} diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/service/SubscriptionGenerator.java b/dspace-api/src/main/java/org/dspace/subscriptions/service/SubscriptionGenerator.java new file mode 100644 index 0000000000..1790513b9b --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/subscriptions/service/SubscriptionGenerator.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.subscriptions.service; + +import java.util.List; + +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; + +/** + * Interface Class which will be used to send email notifications to ePerson + * containing information for all list of objects. + * + * @author Alba Aliu + */ +public interface SubscriptionGenerator { + + public void notifyForSubscriptions(Context c, EPerson ePerson, List comm, List coll); + +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/supervision/SupervisionOrder.java b/dspace-api/src/main/java/org/dspace/supervision/SupervisionOrder.java new file mode 100644 index 0000000000..52d5dacb74 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/supervision/SupervisionOrder.java @@ -0,0 +1,78 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +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 org.dspace.content.Item; +import org.dspace.core.Context; +import org.dspace.core.ReloadableEntity; +import org.dspace.eperson.Group; +import org.dspace.supervision.service.SupervisionOrderService; + +/** + * Database entity representation of the supervision_orders table + * + * @author Mohamed Eskander (mohamed.eskander at 4science dot it) + */ +@Entity +@Table(name = "supervision_orders") +public class SupervisionOrder implements ReloadableEntity { + + @Id + @Column(name = "id") + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "supervision_orders_seq") + @SequenceGenerator(name = "supervision_orders_seq", sequenceName = "supervision_orders_seq", allocationSize = 1) + private Integer id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "item_id") + private Item item; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "eperson_group_id") + private Group group; + + /** + * Protected constructor, create object using: + * {@link SupervisionOrderService#create(Context, Item, Group)} + */ + protected SupervisionOrder() { + + } + + @Override + public Integer getID() { + return id; + } + + public Item getItem() { + return item; + } + + public void setItem(Item item) { + this.item = item; + } + + public Group getGroup() { + return group; + } + + public void setGroup(Group group) { + this.group = group; + } +} diff --git a/dspace-api/src/main/java/org/dspace/supervision/SupervisionOrderServiceImpl.java b/dspace-api/src/main/java/org/dspace/supervision/SupervisionOrderServiceImpl.java new file mode 100644 index 0000000000..21a54f085f --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/supervision/SupervisionOrderServiceImpl.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.supervision; + +import java.sql.SQLException; +import java.util.List; + +import org.apache.commons.collections4.CollectionUtils; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.Item; +import org.dspace.content.service.ItemService; +import org.dspace.core.Constants; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.Group; +import org.dspace.eperson.service.GroupService; +import org.dspace.event.Event; +import org.dspace.supervision.dao.SupervisionOrderDao; +import org.dspace.supervision.service.SupervisionOrderService; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Implementation of {@link SupervisionOrderService} + * + * @author Mohamed Eskander (mohamed.eskander at 4science dot it) + */ +public class SupervisionOrderServiceImpl implements SupervisionOrderService { + + @Autowired(required = true) + private SupervisionOrderDao supervisionDao; + + @Autowired(required = true) + private GroupService groupService; + + @Autowired(required = true) + private ItemService itemService; + + protected SupervisionOrderServiceImpl() { + + } + + @Override + public SupervisionOrder create(Context context) throws SQLException, AuthorizeException { + return supervisionDao.create(context, new SupervisionOrder()); + } + + @Override + public SupervisionOrder find(Context context, int id) throws SQLException { + return supervisionDao.findByID(context, SupervisionOrder.class, id); + } + + @Override + public void update(Context context, SupervisionOrder supervisionOrder) + throws SQLException, AuthorizeException { + supervisionDao.save(context, supervisionOrder); + } + + @Override + public void update(Context context, List supervisionOrders) + throws SQLException, AuthorizeException { + if (CollectionUtils.isNotEmpty(supervisionOrders)) { + for (SupervisionOrder supervisionOrder : supervisionOrders) { + supervisionDao.save(context, supervisionOrder); + } + } + } + + @Override + public void delete(Context context, SupervisionOrder supervisionOrder) throws SQLException, AuthorizeException { + supervisionDao.delete(context, supervisionOrder); + } + + @Override + public SupervisionOrder create(Context context, Item item, Group group) throws SQLException { + SupervisionOrder supervisionOrder = new SupervisionOrder(); + supervisionOrder.setItem(item); + supervisionOrder.setGroup(group); + SupervisionOrder supOrder = supervisionDao.create(context, supervisionOrder); + context.addEvent(new Event(Event.MODIFY, Constants.ITEM, item.getID(), null, + itemService.getIdentifiers(context, item))); + return supOrder; + } + + @Override + public List findAll(Context context) throws SQLException { + return supervisionDao.findAll(context, SupervisionOrder.class); + } + + @Override + public List findByItem(Context context, Item item) throws SQLException { + return supervisionDao.findByItem(context, item); + } + + @Override + public SupervisionOrder findByItemAndGroup(Context context, Item item, Group group) throws SQLException { + return supervisionDao.findByItemAndGroup(context, item, group); + } + + @Override + public boolean isSupervisor(Context context, EPerson ePerson, Item item) throws SQLException { + List supervisionOrders = findByItem(context, item); + + if (CollectionUtils.isEmpty(supervisionOrders)) { + return false; + } + + return supervisionOrders + .stream() + .map(SupervisionOrder::getGroup) + .anyMatch(group -> isMember(context, ePerson, group)); + } + + private boolean isMember(Context context, EPerson ePerson, Group group) { + try { + return groupService.isMember(context, ePerson, group); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + +} diff --git a/dspace-api/src/main/java/org/dspace/supervision/dao/SupervisionOrderDao.java b/dspace-api/src/main/java/org/dspace/supervision/dao/SupervisionOrderDao.java new file mode 100644 index 0000000000..2dd5dad12a --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/supervision/dao/SupervisionOrderDao.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.supervision.dao; + +import java.sql.SQLException; +import java.util.List; + +import org.dspace.content.Item; +import org.dspace.core.Context; +import org.dspace.core.GenericDAO; +import org.dspace.eperson.Group; +import org.dspace.supervision.SupervisionOrder; + +/** + * Database Access Object interface class for the SupervisionOrder object. + * + * The implementation of this class is responsible for all database calls for the SupervisionOrder object + * and is autowired by spring + * + * @author Mohamed Eskander (mohamed.eskander at 4science dot it) + */ +public interface SupervisionOrderDao extends GenericDAO { + + /** + * find all Supervision Orders related to the item + * + * @param context The DSpace context + * @param item the item + * @return the Supervision Orders related to the item + * @throws SQLException If something goes wrong in the database + */ + List findByItem(Context context, Item item) throws SQLException; + + /** + * find the Supervision Order related to the item and group + * + * @param context The DSpace context + * @param item the item + * @param group the group + * @return the Supervision Order related to the item and group + * @throws SQLException If something goes wrong in the database + */ + SupervisionOrder findByItemAndGroup(Context context, Item item, Group group) throws SQLException; + +} 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 new file mode 100644 index 0000000000..09cd0841e7 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/supervision/dao/impl/SupervisionOrderDaoImpl.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.supervision.dao.impl; + +import java.sql.SQLException; +import java.util.List; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Root; + +import org.dspace.content.Item; +import org.dspace.core.AbstractHibernateDAO; +import org.dspace.core.Context; +import org.dspace.eperson.Group; +import org.dspace.supervision.SupervisionOrder; +import org.dspace.supervision.SupervisionOrder_; +import org.dspace.supervision.dao.SupervisionOrderDao; + +/** + * Hibernate implementation of the Database Access Object interface class for the SupervisionOrder object. + * This class is responsible for all database calls for the SupervisionOrder object + * and is autowired by spring + * + * @author Mohamed Eskander (mohamed.eskander at 4science dot it) + */ +public class SupervisionOrderDaoImpl extends AbstractHibernateDAO implements SupervisionOrderDao { + + @Override + public List findByItem(Context context, Item item) throws SQLException { + CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); + CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, SupervisionOrder.class); + + Root supervisionOrderRoot = criteriaQuery.from(SupervisionOrder.class); + criteriaQuery.select(supervisionOrderRoot); + criteriaQuery.where(criteriaBuilder.equal(supervisionOrderRoot.get(SupervisionOrder_.item), item)); + + return list(context, criteriaQuery, false, SupervisionOrder.class, -1, -1); + } + + @Override + public SupervisionOrder findByItemAndGroup(Context context, Item item, Group group) throws SQLException { + CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); + CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, SupervisionOrder.class); + + Root supervisionOrderRoot = criteriaQuery.from(SupervisionOrder.class); + criteriaQuery.select(supervisionOrderRoot); + criteriaQuery.where(criteriaBuilder.and( + criteriaBuilder.equal(supervisionOrderRoot.get(SupervisionOrder_.item), item), + criteriaBuilder.equal(supervisionOrderRoot.get(SupervisionOrder_.group), group) + )); + + return singleResult(context, criteriaQuery); + } +} diff --git a/dspace-api/src/main/java/org/dspace/supervision/enumeration/SupervisionOrderType.java b/dspace-api/src/main/java/org/dspace/supervision/enumeration/SupervisionOrderType.java new file mode 100644 index 0000000000..4f6b888d60 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/supervision/enumeration/SupervisionOrderType.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.supervision.enumeration; + + +/** + * This Enum holds a representation of all the possible supervision order types + *

+ * OBSERVER: grant READ permission to the supervised item + * EDITOR: grant READ and WRITE permissions to the supervised item + * NONE: no grants + *

+ * + * @author Mohamed Eskander (mohamed.eskander at 4science dot it) + */ +public enum SupervisionOrderType { + OBSERVER, + NONE, + EDITOR; + + public static boolean invalid(String type) { + try { + SupervisionOrderType.valueOf(type); + return false; + } catch (IllegalArgumentException ignored) { + return true; + } + } +} diff --git a/dspace-api/src/main/java/org/dspace/supervision/factory/SupervisionOrderServiceFactory.java b/dspace-api/src/main/java/org/dspace/supervision/factory/SupervisionOrderServiceFactory.java new file mode 100644 index 0000000000..8577ee8b16 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/supervision/factory/SupervisionOrderServiceFactory.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.supervision.factory; + +import org.dspace.services.factory.DSpaceServicesFactory; +import org.dspace.supervision.service.SupervisionOrderService; + +/** + * Abstract factory to get services for the supervision package, + * use SupervisionOrderServiceFactory.getInstance() to retrieve an implementation + * + * @author Mohamed Eskander (mohamed.eskander at 4science dot it) + */ +public abstract class SupervisionOrderServiceFactory { + + public abstract SupervisionOrderService getSupervisionOrderService(); + + public static SupervisionOrderServiceFactory getInstance() { + return DSpaceServicesFactory.getInstance() + .getServiceManager() + .getServiceByName("supervisionOrderServiceFactory", + SupervisionOrderServiceFactory.class); + } +} diff --git a/dspace-api/src/main/java/org/dspace/supervision/factory/SupervisionOrderServiceFactoryImpl.java b/dspace-api/src/main/java/org/dspace/supervision/factory/SupervisionOrderServiceFactoryImpl.java new file mode 100644 index 0000000000..407a79c689 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/supervision/factory/SupervisionOrderServiceFactoryImpl.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.supervision.factory; + +import org.dspace.supervision.service.SupervisionOrderService; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Factory implementation to get services for the supervision package, + * use SupervisionOrderServiceFactory.getInstance() to retrieve an implementation + * + * @author Mohamed Eskander (mohamed.eskander at 4science dot it) + */ +public class SupervisionOrderServiceFactoryImpl extends SupervisionOrderServiceFactory { + + @Autowired(required = true) + private SupervisionOrderService supervisionOrderService; + + @Override + public SupervisionOrderService getSupervisionOrderService() { + return supervisionOrderService; + } +} diff --git a/dspace-api/src/main/java/org/dspace/supervision/service/SupervisionOrderService.java b/dspace-api/src/main/java/org/dspace/supervision/service/SupervisionOrderService.java new file mode 100644 index 0000000000..0a3b6dae4b --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/supervision/service/SupervisionOrderService.java @@ -0,0 +1,80 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.supervision.service; + +import java.sql.SQLException; +import java.util.List; + +import org.dspace.content.Item; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.Group; +import org.dspace.service.DSpaceCRUDService; +import org.dspace.supervision.SupervisionOrder; + +/** + * Service interface class for the SupervisionOrder object. + * + * @author Mohamed Eskander (mohamed.eskander at 4science dot it) + */ +public interface SupervisionOrderService extends DSpaceCRUDService { + + /** + * Creates a new SupervisionOrder + * + * @param context The DSpace context + * @param item the item + * @param group the group + * @return the created Supervision Order on item and group + * @throws SQLException If something goes wrong in the database + */ + SupervisionOrder create(Context context, Item item, Group group) throws SQLException; + + /** + * Find all supervision orders currently stored + * + * @param context The DSpace context + * @return all Supervision Orders + * @throws SQLException If something goes wrong in the database + */ + List findAll(Context context) throws SQLException; + + /** + * Find all supervision orders for a given Item + * + * @param context The DSpace context + * @param item the item + * @return all Supervision Orders related to the item + * @throws SQLException If something goes wrong in the database + */ + List findByItem(Context context, Item item) throws SQLException; + + /** + * + * Find a supervision order depending on given Item and Group + * + * @param context The DSpace context + * @param item the item + * @param group the group + * @return the Supervision Order of the item and group + * @throws SQLException If something goes wrong in the database + */ + SupervisionOrder findByItemAndGroup(Context context, Item item, Group group) throws SQLException; + + /** + * + * Checks if an EPerson is supervisor of an Item + * + * @param context The DSpace context + * @param ePerson the ePerson to be checked + * @param item the item + * @return true if the ePerson is a supervisor of the item + * @throws SQLException If something goes wrong in the database + */ + boolean isSupervisor(Context context, EPerson ePerson, Item item) throws SQLException; +} 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 7dcebcc09f..9342cb8b39 100644 --- a/dspace-api/src/main/java/org/dspace/util/SolrUpgradePre6xStatistics.java +++ b/dspace-api/src/main/java/org/dspace/util/SolrUpgradePre6xStatistics.java @@ -447,7 +447,7 @@ public class SolrUpgradePre6xStatistics { runReport(); logTime(false); for (int processed = updateRecords(MIGQUERY); (processed != 0) - && (numProcessed < numRec); processed = updateRecords(MIGQUERY)) { + && (numProcessed <= numRec); processed = updateRecords(MIGQUERY)) { printTime(numProcessed, false); batchUpdateStats(); if (context.getCacheSize() > CACHE_LIMIT) { @@ -696,4 +696,4 @@ public class SolrUpgradePre6xStatistics { return null; } -} \ No newline at end of file +} diff --git a/dspace-api/src/main/java/org/dspace/workflow/WorkflowService.java b/dspace-api/src/main/java/org/dspace/workflow/WorkflowService.java index 716b6cabd3..613c5821bc 100644 --- a/dspace-api/src/main/java/org/dspace/workflow/WorkflowService.java +++ b/dspace-api/src/main/java/org/dspace/workflow/WorkflowService.java @@ -18,6 +18,7 @@ import org.dspace.core.Context; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; import org.dspace.xmlworkflow.WorkflowConfigurationException; +import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; /** * Service interface class for the WorkflowService framework. @@ -100,6 +101,9 @@ public interface WorkflowService { String rejection_message) throws SQLException, AuthorizeException, IOException; + public void restartWorkflow(Context context, XmlWorkflowItem wi, EPerson decliner, String provenance) + throws SQLException, AuthorizeException, IOException, WorkflowException; + public String getMyDSpaceLink(); public void deleteCollection(Context context, Collection collection) diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/Role.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/Role.java index bfc5654cdd..5b5ba5c1d3 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/Role.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/Role.java @@ -41,6 +41,9 @@ public class Role implements BeanNameAware { @Autowired private WorkflowItemRoleService workflowItemRoleService; + // Whether or not to delete temporary group made attached to the WorkflowItemRole for this role in AutoAssignAction + private boolean deleteTemporaryGroup = false; + private String id; private String name; private String description; @@ -153,4 +156,17 @@ public class Role implements BeanNameAware { public void setInternal(boolean internal) { isInternal = internal; } + + public boolean isDeleteTemporaryGroup() { + return deleteTemporaryGroup; + } + + /** + * Setter for config that indicated whether or not to delete temporary group made attached to the + * WorkflowItemRole for this role in AutoAssignAction + * @param deleteTemporaryGroup + */ + public void setDeleteTemporaryGroup(boolean deleteTemporaryGroup) { + this.deleteTemporaryGroup = deleteTemporaryGroup; + } } 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 90f180ec87..da7910da29 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/XmlWorkflowServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/XmlWorkflowServiceImpl.java @@ -1076,6 +1076,53 @@ public class XmlWorkflowServiceImpl implements XmlWorkflowService { return wsi; } + @Override + public void restartWorkflow(Context context, XmlWorkflowItem wi, EPerson decliner, String provenance) + throws SQLException, AuthorizeException, IOException, WorkflowException { + if (!authorizeService.isAdmin(context)) { + throw new AuthorizeException("You must be an admin to restart a workflow"); + } + context.turnOffAuthorisationSystem(); + + // rejection provenance + Item myitem = wi.getItem(); + + // Here's what happened + String provDescription = + provenance + " Declined by " + getEPersonName(decliner) + " on " + DCDate.getCurrent().toString() + + " (GMT) "; + + // Add to item as a DC field + itemService + .addMetadata(context, myitem, MetadataSchemaEnum.DC.getName(), + "description", "provenance", "en", provDescription); + + //Clear any workflow schema related metadata + itemService + .clearMetadata(context, myitem, WorkflowRequirementsService.WORKFLOW_SCHEMA, Item.ANY, Item.ANY, Item.ANY); + + itemService.update(context, myitem); + + // remove policy for controller + removeUserItemPolicies(context, myitem, decliner); + revokeReviewerPolicies(context, myitem); + + // convert into personal workspace + WorkspaceItem wsi = returnToWorkspace(context, wi); + + // Because of issue of xmlWorkflowItemService not realising wfi wrapper has been deleted + context.commit(); + wsi = context.reloadEntity(wsi); + + log.info(LogHelper.getHeader(context, "decline_workflow", "workflow_item_id=" + + wi.getID() + "item_id=" + wi.getItem().getID() + "collection_id=" + wi.getCollection().getID() + + "eperson_id=" + decliner.getID())); + + // Restart workflow + this.startWithoutNotify(context, wsi); + context.restoreAuthSystemState(); + } + /** * Return the workflow item to the workspace of the submitter. The workflow * item is removed, and a workspace item created. 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 0aabfab057..1cfa33b121 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 @@ -14,10 +14,15 @@ import java.util.List; import javax.servlet.http.HttpServletRequest; import org.dspace.authorize.AuthorizeException; +import org.dspace.content.DCDate; +import org.dspace.content.MetadataSchemaEnum; +import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.content.service.ItemService; import org.dspace.core.Context; import org.dspace.workflow.WorkflowException; import org.dspace.xmlworkflow.RoleMembers; import org.dspace.xmlworkflow.WorkflowConfigurationException; +import org.dspace.xmlworkflow.factory.XmlWorkflowServiceFactory; import org.dspace.xmlworkflow.state.Step; import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; @@ -37,6 +42,8 @@ public abstract class Action { private WorkflowActionConfig parent; private static final String ERROR_FIELDS_ATTRIBUTE = "dspace.workflow.error_fields"; + private List advancedOptions = new ArrayList<>(); + private List advancedInfo = new ArrayList<>(); /** * Called when a workflow item becomes eligible for this Action. @@ -192,4 +199,58 @@ public abstract class Action { //save updated list setErrorFields(request, errorFields); } + + /** + * Returns a list of advanced options that the user can select at this action + * @return A list of advanced options of this action, resulting in the next step of the workflow + */ + protected List getAdvancedOptions() { + return advancedOptions; + } + + /** + * Returns true if this Action has advanced options, false if it doesn't + * @return true if there are advanced options, false otherwise + */ + protected boolean isAdvanced() { + return !getAdvancedOptions().isEmpty(); + } + + /** + * Returns a list of advanced info required by the advanced options + * @return A list of advanced info required by the advanced options + */ + protected List getAdvancedInfo() { + return advancedInfo; + } + + + /** + * 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 wfi Workflow item we're adding workflow accept provenance on + */ + public void addApprovedProvenance(Context c, XmlWorkflowItem wfi) throws SQLException, AuthorizeException { + ItemService itemService = ContentServiceFactory.getInstance().getItemService(); + + //Add the provenance for the accept + String now = DCDate.getCurrent().toString(); + + // Get user's name + email address + String usersName = + XmlWorkflowServiceFactory.getInstance().getXmlWorkflowService().getEPersonName(c.getCurrentUser()); + + String provDescription = getProvenanceStartId() + " Approved for entry into archive by " + usersName + " on " + + now + " (GMT) "; + + // Add to item as a DC field + c.turnOffAuthorisationSystem(); + itemService.addMetadata(c, wfi.getItem(), MetadataSchemaEnum.DC.getName(), "description", "provenance", "en", + provDescription); + itemService.update(c, wfi.getItem()); + c.restoreAuthSystemState(); + } + } diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/ActionAdvancedInfo.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/ActionAdvancedInfo.java new file mode 100644 index 0000000000..b49fdb34f8 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/ActionAdvancedInfo.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.xmlworkflow.state.actions; + +/** + * Interface for the shared properties of an 'advancedInfo' section of an advanced workflow {@link Action} + * Implementations of this class will define the specific fields per action that will need to be defined/configured + * to pass along this info to REST endpoint + */ +public abstract class ActionAdvancedInfo { + + protected String type; + protected String id; + + protected final static String TYPE_PREFIX = "action_info_"; + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = TYPE_PREFIX + type; + } + + public String getId() { + return id; + } + + /** + * Setter for the Action id to be set. + * This is an MD5 hash of the type and the stringified properties of the advanced info + * + * @param type The type of this Action to be included in the MD5 hash + */ + protected abstract void generateId(String type); + +} diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/WorkflowActionConfig.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/WorkflowActionConfig.java index 1dc61888b1..3475b04c74 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/WorkflowActionConfig.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/WorkflowActionConfig.java @@ -69,4 +69,28 @@ public class WorkflowActionConfig { return this.processingAction.getOptions(); } + /** + * Returns a list of advanced options this user has on this action, resulting in the next step of the workflow + * @return A list of advanced options of this action, resulting in the next step of the workflow + */ + public List getAdvancedOptions() { + return this.processingAction.getAdvancedOptions(); + } + + /** + * Returns a boolean depending on whether this action has advanced options + * @return The boolean indicating whether this action has advanced options + */ + public boolean isAdvanced() { + return this.processingAction.isAdvanced(); + } + + /** + * Returns a Map of info for the advanced options this user has on this action + * @return a Map of info for the advanced options this user has on this action + */ + public List getAdvancedInfo() { + return this.processingAction.getAdvancedInfo(); + } + } 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 743d00b2b6..67b400c659 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 @@ -15,8 +15,6 @@ import javax.servlet.http.HttpServletRequest; import org.dspace.app.util.Util; import org.dspace.authorize.AuthorizeException; -import org.dspace.content.DCDate; -import org.dspace.content.MetadataSchemaEnum; import org.dspace.core.Context; import org.dspace.xmlworkflow.factory.XmlWorkflowServiceFactory; import org.dspace.xmlworkflow.state.Step; @@ -34,8 +32,6 @@ import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; */ public class AcceptEditRejectAction extends ProcessingAction { - private static final String SUBMIT_APPROVE = "submit_approve"; - private static final String SUBMIT_REJECT = "submit_reject"; private static final String SUBMITTER_IS_DELETED_PAGE = "submitter_deleted"; //TODO: rename to AcceptAndEditMetadataAction @@ -53,7 +49,7 @@ public class AcceptEditRejectAction extends ProcessingAction { case SUBMIT_APPROVE: return processAccept(c, wfi); case SUBMIT_REJECT: - return processRejectPage(c, wfi, request); + return super.processRejectPage(c, wfi, request); case SUBMITTER_IS_DELETED_PAGE: return processSubmitterIsDeletedPage(c, wfi, request); default: @@ -69,33 +65,18 @@ public class AcceptEditRejectAction extends ProcessingAction { options.add(SUBMIT_APPROVE); options.add(SUBMIT_REJECT); options.add(ProcessingAction.SUBMIT_EDIT_METADATA); + options.add(RETURN_TO_POOL); return options; } public ActionResult processAccept(Context c, XmlWorkflowItem wfi) throws SQLException, AuthorizeException { //Delete the tasks - addApprovedProvenance(c, wfi); + super.addApprovedProvenance(c, wfi); return new ActionResult(ActionResult.TYPE.TYPE_OUTCOME, ActionResult.OUTCOME_COMPLETE); } - public ActionResult processRejectPage(Context c, XmlWorkflowItem wfi, HttpServletRequest request) - throws SQLException, AuthorizeException, IOException { - String reason = request.getParameter("reason"); - if (reason == null || 0 == reason.trim().length()) { - addErrorField(request, "reason"); - return new ActionResult(ActionResult.TYPE.TYPE_ERROR); - } - - // We have pressed reject, so remove the task the user has & put it back - // to a workspace item - XmlWorkflowServiceFactory.getInstance().getXmlWorkflowService().sendWorkflowItemBackSubmission(c, wfi, - c.getCurrentUser(), this.getProvenanceStartId(), reason); - - return new ActionResult(ActionResult.TYPE.TYPE_SUBMISSION_PAGE); - } - public ActionResult processSubmitterIsDeletedPage(Context c, XmlWorkflowItem wfi, HttpServletRequest request) throws SQLException, AuthorizeException, IOException { if (request.getParameter("submit_delete") != null) { @@ -111,21 +92,4 @@ public class AcceptEditRejectAction extends ProcessingAction { return new ActionResult(ActionResult.TYPE.TYPE_PAGE); } } - - private void addApprovedProvenance(Context c, XmlWorkflowItem wfi) throws SQLException, AuthorizeException { - //Add the provenance for the accept - String now = DCDate.getCurrent().toString(); - - // Get user's name + email address - String usersName = XmlWorkflowServiceFactory.getInstance().getXmlWorkflowService() - .getEPersonName(c.getCurrentUser()); - - String provDescription = getProvenanceStartId() + " Approved for entry into archive by " - + usersName + " on " + now + " (GMT) "; - - // Add to item as a DC field - itemService.addMetadata(c, wfi.getItem(), MetadataSchemaEnum.DC.getName(), "description", "provenance", "en", - provDescription); - itemService.update(c, wfi.getItem()); - } } 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 3c4e0ffc1d..9b83be5d7b 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 @@ -14,10 +14,7 @@ import javax.servlet.http.HttpServletRequest; import org.dspace.app.util.Util; import org.dspace.authorize.AuthorizeException; -import org.dspace.content.DCDate; -import org.dspace.content.MetadataSchemaEnum; import org.dspace.core.Context; -import org.dspace.xmlworkflow.factory.XmlWorkflowServiceFactory; import org.dspace.xmlworkflow.state.Step; import org.dspace.xmlworkflow.state.actions.ActionResult; import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; @@ -52,7 +49,7 @@ public class FinalEditAction extends ProcessingAction { switch (Util.getSubmitButton(request, SUBMIT_CANCEL)) { case SUBMIT_APPROVE: //Delete the tasks - addApprovedProvenance(c, wfi); + super.addApprovedProvenance(c, wfi); return new ActionResult(ActionResult.TYPE.TYPE_OUTCOME, ActionResult.OUTCOME_COMPLETE); default: //We pressed the leave button so return to our submissions page @@ -67,25 +64,8 @@ public class FinalEditAction extends ProcessingAction { List options = new ArrayList<>(); options.add(SUBMIT_APPROVE); options.add(ProcessingAction.SUBMIT_EDIT_METADATA); + options.add(RETURN_TO_POOL); return options; } - private void addApprovedProvenance(Context c, XmlWorkflowItem wfi) throws SQLException, AuthorizeException { - //Add the provenance for the accept - String now = DCDate.getCurrent().toString(); - - // Get user's name + email address - String usersName = XmlWorkflowServiceFactory.getInstance().getXmlWorkflowService() - .getEPersonName(c.getCurrentUser()); - - String provDescription = getProvenanceStartId() + " Approved for entry into archive by " - + usersName + " on " + now + " (GMT) "; - - // Add to item as a DC field - itemService.addMetadata(c, wfi.getItem(), MetadataSchemaEnum.DC.getName(), "description", "provenance", "en", - provDescription); - itemService.update(c, wfi.getItem()); - } - - } 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 8b8358a8d6..7a1c62adbd 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 @@ -7,12 +7,16 @@ */ package org.dspace.xmlworkflow.state.actions.processingaction; +import java.io.IOException; import java.sql.SQLException; import javax.servlet.http.HttpServletRequest; +import org.dspace.authorize.AuthorizeException; import org.dspace.content.service.ItemService; import org.dspace.core.Context; +import org.dspace.xmlworkflow.service.XmlWorkflowService; import org.dspace.xmlworkflow.state.actions.Action; +import org.dspace.xmlworkflow.state.actions.ActionResult; import org.dspace.xmlworkflow.storedcomponents.ClaimedTask; import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; import org.dspace.xmlworkflow.storedcomponents.service.ClaimedTaskService; @@ -32,9 +36,15 @@ public abstract class ProcessingAction extends Action { protected ClaimedTaskService claimedTaskService; @Autowired(required = true) protected ItemService itemService; + @Autowired + protected XmlWorkflowService xmlWorkflowService; public static final String SUBMIT_EDIT_METADATA = "submit_edit_metadata"; public static final String SUBMIT_CANCEL = "submit_cancel"; + protected static final String SUBMIT_APPROVE = "submit_approve"; + protected static final String SUBMIT_REJECT = "submit_reject"; + protected static final String RETURN_TO_POOL = "return_to_pool"; + protected static final String REJECT_REASON = "reason"; @Override public boolean isAuthorized(Context context, HttpServletRequest request, XmlWorkflowItem wfi) throws SQLException { @@ -48,4 +58,31 @@ public abstract class ProcessingAction extends Action { task.getStepID().equals(getParent().getStep().getId()) && task.getActionID().equals(getParent().getId()); } + + /** + * Process result when option {@link this#SUBMIT_REJECT} is selected. + * - Sets the reason and workflow step responsible on item in dc.description.provenance + * - Send workflow back to the submission + * If reason is not given => error + */ + public ActionResult processRejectPage(Context c, XmlWorkflowItem wfi, HttpServletRequest request) + throws SQLException, AuthorizeException, IOException { + String reason = request.getParameter(REJECT_REASON); + if (reason == null || 0 == reason.trim().length()) { + addErrorField(request, REJECT_REASON); + return new ActionResult(ActionResult.TYPE.TYPE_ERROR); + } + + // We have pressed reject, so remove the task the user has & put it back + // to a workspace item + xmlWorkflowService.sendWorkflowItemBackSubmission(c, wfi, c.getCurrentUser(), this.getProvenanceStartId(), + reason); + + return new ActionResult(ActionResult.TYPE.TYPE_SUBMISSION_PAGE); + } + + @Override + protected boolean isAdvanced() { + return !getAdvancedOptions().isEmpty(); + } } 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 8474757be6..bd74ab3c71 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 @@ -15,8 +15,6 @@ import javax.servlet.http.HttpServletRequest; import org.dspace.app.util.Util; import org.dspace.authorize.AuthorizeException; -import org.dspace.content.DCDate; -import org.dspace.content.MetadataSchemaEnum; import org.dspace.core.Context; import org.dspace.xmlworkflow.factory.XmlWorkflowServiceFactory; import org.dspace.xmlworkflow.state.Step; @@ -36,11 +34,8 @@ public class ReviewAction extends ProcessingAction { public static final int MAIN_PAGE = 0; public static final int REJECT_PAGE = 1; - private static final String SUBMIT_APPROVE = "submit_approve"; - private static final String SUBMIT_REJECT = "submit_reject"; private static final String SUBMITTER_IS_DELETED_PAGE = "submitter_deleted"; - @Override public void activate(Context c, XmlWorkflowItem wfItem) { @@ -54,7 +49,7 @@ public class ReviewAction extends ProcessingAction { case SUBMIT_APPROVE: return processAccept(c, wfi); case SUBMIT_REJECT: - return processRejectPage(c, wfi, step, request); + return super.processRejectPage(c, wfi, request); case SUBMITTER_IS_DELETED_PAGE: return processSubmitterIsDeletedPage(c, wfi, request); default: @@ -69,50 +64,15 @@ public class ReviewAction extends ProcessingAction { List options = new ArrayList<>(); options.add(SUBMIT_APPROVE); options.add(SUBMIT_REJECT); + options.add(RETURN_TO_POOL); return options; } public ActionResult processAccept(Context c, XmlWorkflowItem wfi) throws SQLException, AuthorizeException { - //Delete the tasks - addApprovedProvenance(c, wfi); + super.addApprovedProvenance(c, wfi); return new ActionResult(ActionResult.TYPE.TYPE_OUTCOME, ActionResult.OUTCOME_COMPLETE); } - private void addApprovedProvenance(Context c, XmlWorkflowItem wfi) throws SQLException, AuthorizeException { - //Add the provenance for the accept - String now = DCDate.getCurrent().toString(); - - // Get user's name + email address - String usersName = XmlWorkflowServiceFactory.getInstance().getXmlWorkflowService() - .getEPersonName(c.getCurrentUser()); - - String provDescription = getProvenanceStartId() + " Approved for entry into archive by " - + usersName + " on " + now + " (GMT) "; - - // Add to item as a DC field - itemService.addMetadata(c, wfi.getItem(), MetadataSchemaEnum.DC.getName(), "description", "provenance", "en", - provDescription); - itemService.update(c, wfi.getItem()); - } - - public ActionResult processRejectPage(Context c, XmlWorkflowItem wfi, Step step, HttpServletRequest request) - throws SQLException, AuthorizeException, IOException { - String reason = request.getParameter("reason"); - if (reason == null || 0 == reason.trim().length()) { - request.setAttribute("page", REJECT_PAGE); - addErrorField(request, "reason"); - return new ActionResult(ActionResult.TYPE.TYPE_ERROR); - } - - //We have pressed reject, so remove the task the user has & put it back to a workspace item - XmlWorkflowServiceFactory.getInstance().getXmlWorkflowService() - .sendWorkflowItemBackSubmission(c, wfi, c.getCurrentUser(), - this.getProvenanceStartId(), reason); - - - return new ActionResult(ActionResult.TYPE.TYPE_SUBMISSION_PAGE); - } - public ActionResult processSubmitterIsDeletedPage(Context c, XmlWorkflowItem wfi, HttpServletRequest request) throws SQLException, AuthorizeException, IOException { if (request.getParameter("submit_delete") != null) { 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 a834641111..16d35b3668 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 @@ -7,6 +7,9 @@ */ package org.dspace.xmlworkflow.state.actions.processingaction; +import static org.dspace.xmlworkflow.state.actions.processingaction.ScoreReviewAction.REVIEW_FIELD; +import static org.dspace.xmlworkflow.state.actions.processingaction.ScoreReviewAction.SCORE_FIELD; + import java.io.IOException; import java.sql.SQLException; import java.util.ArrayList; @@ -19,7 +22,6 @@ import org.dspace.content.MetadataSchemaEnum; import org.dspace.content.MetadataValue; import org.dspace.core.Context; import org.dspace.xmlworkflow.factory.XmlWorkflowServiceFactory; -import org.dspace.xmlworkflow.service.WorkflowRequirementsService; import org.dspace.xmlworkflow.state.Step; import org.dspace.xmlworkflow.state.actions.ActionResult; import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; @@ -37,6 +39,7 @@ import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; */ public class ScoreEvaluationAction extends ProcessingAction { + // Minimum aggregate of scores private int minimumAcceptanceScore; @Override @@ -47,43 +50,64 @@ public class ScoreEvaluationAction extends ProcessingAction { @Override public ActionResult execute(Context c, XmlWorkflowItem wfi, Step step, HttpServletRequest request) throws SQLException, AuthorizeException, IOException { - boolean hasPassed = false; - //Retrieve all our scores from the metadata & add em up + // Retrieve all our scores from the metadata & add em up + int scoreMean = getMeanScore(wfi); + //We have passed if we have at least gained our minimum score + boolean hasPassed = getMinimumAcceptanceScore() <= scoreMean; + //Whether or not we have passed, clear our score information + itemService.clearMetadata(c, wfi.getItem(), SCORE_FIELD.schema, SCORE_FIELD.element, SCORE_FIELD.qualifier, + Item.ANY); + if (hasPassed) { + this.addRatingInfoToProv(c, wfi, scoreMean); + return new ActionResult(ActionResult.TYPE.TYPE_OUTCOME, ActionResult.OUTCOME_COMPLETE); + } else { + //We haven't passed, reject our item + XmlWorkflowServiceFactory.getInstance().getXmlWorkflowService() + .sendWorkflowItemBackSubmission(c, wfi, c.getCurrentUser(), this.getProvenanceStartId(), + "The item was reject due to a bad review score."); + return new ActionResult(ActionResult.TYPE.TYPE_SUBMISSION_PAGE); + } + } + + private int getMeanScore(XmlWorkflowItem wfi) { List scores = itemService - .getMetadata(wfi.getItem(), WorkflowRequirementsService.WORKFLOW_SCHEMA, "score", null, Item.ANY); + .getMetadata(wfi.getItem(), SCORE_FIELD.schema, SCORE_FIELD.element, SCORE_FIELD.qualifier, Item.ANY); + int scoreMean = 0; if (0 < scores.size()) { int totalScoreCount = 0; for (MetadataValue score : scores) { totalScoreCount += Integer.parseInt(score.getValue()); } - int scoreMean = totalScoreCount / scores.size(); - //We have passed if we have at least gained our minimum score - hasPassed = getMinimumAcceptanceScore() <= scoreMean; - //Wether or not we have passed, clear our score information - itemService - .clearMetadata(c, wfi.getItem(), WorkflowRequirementsService.WORKFLOW_SCHEMA, "score", null, Item.ANY); + scoreMean = totalScoreCount / scores.size(); + } + return scoreMean; + } - String provDescription = getProvenanceStartId() + " Approved for entry into archive with a score of: " + - scoreMean; - itemService.addMetadata(c, wfi.getItem(), MetadataSchemaEnum.DC.getName(), - "description", "provenance", "en", provDescription); - itemService.update(c, wfi.getItem()); + private void addRatingInfoToProv(Context c, XmlWorkflowItem wfi, int scoreMean) + throws SQLException, AuthorizeException { + StringBuilder provDescription = new StringBuilder(); + provDescription.append(String.format("%s Approved for entry into archive with a score of: %s", + getProvenanceStartId(), scoreMean)); + List reviews = itemService + .getMetadata(wfi.getItem(), REVIEW_FIELD.schema, REVIEW_FIELD.element, REVIEW_FIELD.qualifier, Item.ANY); + if (!reviews.isEmpty()) { + provDescription.append(" | Reviews: "); } - if (hasPassed) { - return new ActionResult(ActionResult.TYPE.TYPE_OUTCOME, ActionResult.OUTCOME_COMPLETE); - } else { - //We haven't passed, reject our item - XmlWorkflowServiceFactory.getInstance().getXmlWorkflowService() - .sendWorkflowItemBackSubmission(c, wfi, c.getCurrentUser(), - this.getProvenanceStartId(), - "The item was reject due to a bad review score."); - return new ActionResult(ActionResult.TYPE.TYPE_SUBMISSION_PAGE); + for (MetadataValue review : reviews) { + provDescription.append(String.format("; %s", review.getValue())); } + c.turnOffAuthorisationSystem(); + itemService.addMetadata(c, wfi.getItem(), MetadataSchemaEnum.DC.getName(), + "description", "provenance", "en", provDescription.toString()); + itemService.update(c, wfi.getItem()); + c.restoreAuthSystemState(); } @Override public List getOptions() { - return new ArrayList<>(); + List options = new ArrayList<>(); + options.add(RETURN_TO_POOL); + return options; } public int getMinimumAcceptanceScore() { 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 c28fe2d93e..43a3decacc 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 @@ -9,14 +9,20 @@ package org.dspace.xmlworkflow.state.actions.processingaction; import java.sql.SQLException; import java.util.Arrays; +import java.util.Collections; import java.util.List; import javax.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.util.Util; import org.dspace.authorize.AuthorizeException; +import org.dspace.content.MetadataFieldName; import org.dspace.core.Context; import org.dspace.xmlworkflow.service.WorkflowRequirementsService; import org.dspace.xmlworkflow.state.Step; +import org.dspace.xmlworkflow.state.actions.ActionAdvancedInfo; import org.dspace.xmlworkflow.state.actions.ActionResult; import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; @@ -24,40 +30,121 @@ import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; * This action will allow multiple users to rate a certain item * if the mean of this score is higher then the minimum score the * item will be sent to the next action/step else it will be rejected - * - * @author Bram De Schouwer (bram.deschouwer at dot com) - * @author Kevin Van de Velde (kevin at atmire dot com) - * @author Ben Bosman (ben at atmire dot com) - * @author Mark Diggory (markd at atmire dot com) */ public class ScoreReviewAction extends ProcessingAction { + private static final Logger log = LogManager.getLogger(ScoreReviewAction.class); - private static final String SUBMIT_SCORE = "submit_score"; + // Option(s) + public static final String SUBMIT_SCORE = "submit_score"; + + // Response param(s) + private static final String SCORE = "score"; + private static final String REVIEW = "review"; + + // Metadata fields to save params in + public static final MetadataFieldName SCORE_FIELD = + new MetadataFieldName(WorkflowRequirementsService.WORKFLOW_SCHEMA, SCORE, null); + public static final MetadataFieldName REVIEW_FIELD = + new MetadataFieldName(WorkflowRequirementsService.WORKFLOW_SCHEMA, REVIEW, null); + + // Whether or not it is required that a text review is added to the rating + private boolean descriptionRequired; + // Maximum value rating is allowed to be + private int maxValue; @Override public void activate(Context c, XmlWorkflowItem wf) { - + // empty } @Override public ActionResult execute(Context c, XmlWorkflowItem wfi, Step step, HttpServletRequest request) - throws SQLException, AuthorizeException { - if (request.getParameter(SUBMIT_SCORE) != null) { - int score = Util.getIntParameter(request, "score"); - //Add our score to the metadata - itemService.addMetadata(c, wfi.getItem(), WorkflowRequirementsService.WORKFLOW_SCHEMA, "score", null, null, - String.valueOf(score)); - itemService.update(c, wfi.getItem()); - - return new ActionResult(ActionResult.TYPE.TYPE_OUTCOME, ActionResult.OUTCOME_COMPLETE); - } else { - //We have pressed the leave button so return to our submission page - return new ActionResult(ActionResult.TYPE.TYPE_SUBMISSION_PAGE); + throws SQLException, AuthorizeException { + if (super.isOptionInParam(request) && + StringUtils.equalsIgnoreCase(Util.getSubmitButton(request, SUBMIT_CANCEL), SUBMIT_SCORE)) { + return processSetRating(c, wfi, request); } + return new ActionResult(ActionResult.TYPE.TYPE_CANCEL); + } + + private ActionResult processSetRating(Context c, XmlWorkflowItem wfi, HttpServletRequest request) + throws SQLException, AuthorizeException { + + int score = Util.getIntParameter(request, SCORE); + String review = request.getParameter(REVIEW); + if (!this.checkRequestValid(score, review)) { + return new ActionResult(ActionResult.TYPE.TYPE_ERROR); + } + //Add our rating and review to the metadata + itemService.addMetadata(c, wfi.getItem(), SCORE_FIELD.schema, SCORE_FIELD.element, SCORE_FIELD.qualifier, null, + String.valueOf(score)); + if (StringUtils.isNotBlank(review)) { + itemService.addMetadata(c, wfi.getItem(), REVIEW_FIELD.schema, REVIEW_FIELD.element, + REVIEW_FIELD.qualifier, null, String.format("%s - %s", score, review)); + } + itemService.update(c, wfi.getItem()); + + return new ActionResult(ActionResult.TYPE.TYPE_OUTCOME, ActionResult.OUTCOME_COMPLETE); + } + + /** + * Request is not valid if: + * - Given score is higher than configured maxValue + * - There is no review given and description is configured to be required + * Config in workflow-actions.xml + * + * @param score Given score rating from request + * @param review Given review/description from request + * @return True if valid request params with config, otherwise false + */ + private boolean checkRequestValid(int score, String review) { + if (score > this.maxValue) { + log.error("{} only allows max rating {} (config workflow-actions.xml), given rating of " + + "{} not allowed.", this.getClass().toString(), this.maxValue, score); + return false; + } + if (StringUtils.isBlank(review) && this.descriptionRequired) { + log.error("{} has config descriptionRequired=true (workflow-actions.xml), so rating " + + "requests without 'review' query param containing description are not allowed", + this.getClass().toString()); + return false; + } + return true; } @Override public List getOptions() { + return List.of(SUBMIT_SCORE, RETURN_TO_POOL); + } + + @Override + protected List getAdvancedOptions() { return Arrays.asList(SUBMIT_SCORE); } + + @Override + protected List getAdvancedInfo() { + ScoreReviewActionAdvancedInfo scoreReviewActionAdvancedInfo = new ScoreReviewActionAdvancedInfo(); + scoreReviewActionAdvancedInfo.setDescriptionRequired(descriptionRequired); + scoreReviewActionAdvancedInfo.setMaxValue(maxValue); + scoreReviewActionAdvancedInfo.setType(SUBMIT_SCORE); + scoreReviewActionAdvancedInfo.generateId(SUBMIT_SCORE); + return Collections.singletonList(scoreReviewActionAdvancedInfo); + } + + /** + * Setter that sets the descriptionRequired property from workflow-actions.xml + * @param descriptionRequired boolean whether a description is required + */ + public void setDescriptionRequired(boolean descriptionRequired) { + this.descriptionRequired = descriptionRequired; + } + + /** + * Setter that sets the maxValue property from workflow-actions.xml + * @param maxValue integer of the maximum allowed value + */ + public void setMaxValue(int maxValue) { + this.maxValue = maxValue; + } } diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ScoreReviewActionAdvancedInfo.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ScoreReviewActionAdvancedInfo.java new file mode 100644 index 0000000000..5b97fe3195 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ScoreReviewActionAdvancedInfo.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.xmlworkflow.state.actions.processingaction; + +import org.dspace.xmlworkflow.state.actions.ActionAdvancedInfo; +import org.springframework.util.DigestUtils; + +/** + * Class that holds the advanced information needed for the + * {@link org.dspace.xmlworkflow.state.actions.processingaction.ScoreReviewAction} + * See config {@code workflow-actions.cfg} + */ +public class ScoreReviewActionAdvancedInfo extends ActionAdvancedInfo { + private boolean descriptionRequired; + private int maxValue; + + public boolean isDescriptionRequired() { + return descriptionRequired; + } + + public void setDescriptionRequired(boolean descriptionRequired) { + this.descriptionRequired = descriptionRequired; + } + + public int getMaxValue() { + return maxValue; + } + + public void setMaxValue(int maxValue) { + this.maxValue = maxValue; + } + + @Override + public void generateId(String type) { + String idString = type + + ";descriptionRequired," + descriptionRequired + + ";maxValue," + maxValue; + super.id = DigestUtils.md5DigestAsHex(idString.getBytes()); + } +} 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 28f21cc418..0e8ab40a52 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 @@ -9,17 +9,27 @@ package org.dspace.xmlworkflow.state.actions.processingaction; import java.sql.SQLException; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.UUID; +import javax.annotation.Nullable; import javax.servlet.http.HttpServletRequest; +import org.apache.commons.lang.ArrayUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.app.util.Util; import org.dspace.authorize.AuthorizeException; import org.dspace.core.Context; import org.dspace.eperson.EPerson; +import org.dspace.eperson.Group; import org.dspace.eperson.service.EPersonService; +import org.dspace.eperson.service.GroupService; +import org.dspace.services.ConfigurationService; import org.dspace.xmlworkflow.Role; import org.dspace.xmlworkflow.state.Step; +import org.dspace.xmlworkflow.state.actions.ActionAdvancedInfo; import org.dspace.xmlworkflow.state.actions.ActionResult; import org.dspace.xmlworkflow.storedcomponents.WorkflowItemRole; import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; @@ -37,13 +47,13 @@ import org.springframework.beans.factory.annotation.Autowired; */ public class SelectReviewerAction extends ProcessingAction { - public static final int SEARCH_RESULTS_PAGE = 1; - - public static final int RESULTS_PER_PAGE = 5; + private static final Logger log = LogManager.getLogger(SelectReviewerAction.class); private static final String SUBMIT_CANCEL = "submit_cancel"; - private static final String SUBMIT_SEARCH = "submit_search"; - private static final String SUBMIT_SELECT_REVIEWER = "submit_select_reviewer_"; + private static final String SUBMIT_SELECT_REVIEWER = "submit_select_reviewer"; + private static final String PARAM_REVIEWER = "eperson"; + + private static final String CONFIG_REVIEWER_GROUP = "action.selectrevieweraction.group"; private Role role; @@ -53,6 +63,15 @@ public class SelectReviewerAction extends ProcessingAction { @Autowired(required = true) private WorkflowItemRoleService workflowItemRoleService; + @Autowired + private ConfigurationService configurationService; + + @Autowired + private GroupService groupService; + + private static Group selectFromReviewsGroup; + private static boolean selectFromReviewsGroupInitialised = false; + @Override public void activate(Context c, XmlWorkflowItem wf) { @@ -60,56 +79,128 @@ public class SelectReviewerAction extends ProcessingAction { @Override public ActionResult execute(Context c, XmlWorkflowItem wfi, Step step, HttpServletRequest request) - throws SQLException, AuthorizeException { + throws SQLException, AuthorizeException { String submitButton = Util.getSubmitButton(request, SUBMIT_CANCEL); //Check if our user has pressed cancel if (submitButton.equals(SUBMIT_CANCEL)) { //Send us back to the submissions page return new ActionResult(ActionResult.TYPE.TYPE_CANCEL); - - } else if (submitButton.equals(SUBMIT_SEARCH)) { - //Perform the search - String query = request.getParameter("query"); - int page = Util.getIntParameter(request, "result-page"); - if (page == -1) { - page = 0; - } - - int resultCount = ePersonService.searchResultCount(c, query); - List epeople = ePersonService.search(c, query, page * RESULTS_PER_PAGE, RESULTS_PER_PAGE); - - - request.setAttribute("eperson-result-count", resultCount); - request.setAttribute("eperson-results", epeople); - request.setAttribute("result-page", page); - request.setAttribute("page", SEARCH_RESULTS_PAGE); - return new ActionResult(ActionResult.TYPE.TYPE_PAGE, SEARCH_RESULTS_PAGE); } else if (submitButton.startsWith(SUBMIT_SELECT_REVIEWER)) { - //Retrieve the identifier of the eperson which will do the reviewing - UUID reviewerId = UUID.fromString(submitButton.substring(submitButton.lastIndexOf("_") + 1)); - EPerson reviewer = ePersonService.find(c, reviewerId); - //Assign the reviewer. The workflowitemrole will be translated into a task in the autoassign - WorkflowItemRole workflowItemRole = workflowItemRoleService.create(c); - workflowItemRole.setEPerson(reviewer); - workflowItemRole.setRoleId(getRole().getId()); - workflowItemRole.setWorkflowItem(wfi); - workflowItemRoleService.update(c, workflowItemRole); - return new ActionResult(ActionResult.TYPE.TYPE_OUTCOME, ActionResult.OUTCOME_COMPLETE); + return processSelectReviewers(c, wfi, request); } //There are only 2 active buttons on this page, so if anything else happens just return an error return new ActionResult(ActionResult.TYPE.TYPE_ERROR); } + /** + * Method to handle the {@link this#SUBMIT_SELECT_REVIEWER} action: + * - will retrieve the reviewer(s) uuid from request (param {@link this#PARAM_REVIEWER}) + * - assign them to a {@link WorkflowItemRole} + * - In {@link org.dspace.xmlworkflow.state.actions.userassignment.AutoAssignAction} these reviewer(s) will get + * claimed task for this {@link XmlWorkflowItem} + * Will result in error if: + * - No reviewer(s) uuid in request (param {@link this#PARAM_REVIEWER}) + * - If none of the reviewer(s) uuid passed along result in valid EPerson + * - If the reviewer(s) passed along are not in {@link this#selectFromReviewsGroup} when it is set + * + * @param c current DSpace session + * @param wfi the item on which the action is to be performed + * @param request the current client request + * @return the result of performing the action + */ + private ActionResult processSelectReviewers(Context c, XmlWorkflowItem wfi, HttpServletRequest request) + throws SQLException, AuthorizeException { + //Retrieve the identifier of the eperson which will do the reviewing + String[] reviewerIds = request.getParameterValues(PARAM_REVIEWER); + if (ArrayUtils.isEmpty(reviewerIds)) { + return new ActionResult(ActionResult.TYPE.TYPE_ERROR); + } + List reviewers = new ArrayList<>(); + for (String reviewerId : reviewerIds) { + EPerson reviewer = ePersonService.find(c, UUID.fromString(reviewerId)); + if (reviewer == null) { + log.warn("No EPerson found with uuid {}", reviewerId); + } else { + reviewers.add(reviewer); + } + } + + if (!this.checkReviewersValid(c, reviewers)) { + return new ActionResult(ActionResult.TYPE.TYPE_ERROR); + } + + createWorkflowItemRole(c, wfi, reviewers); + return new ActionResult(ActionResult.TYPE.TYPE_OUTCOME, ActionResult.OUTCOME_COMPLETE); + } + + private boolean checkReviewersValid(Context c, List reviewers) throws SQLException { + if (reviewers.size() == 0) { + return false; + } + Group group = this.getGroup(c); + if (group != null) { + for (EPerson reviewer: reviewers) { + if (!groupService.isMember(c, reviewer, group)) { + log.error("Reviewers selected must be member of group {}", group.getID()); + return false; + } + } + } + return true; + } + + private WorkflowItemRole createWorkflowItemRole(Context c, XmlWorkflowItem wfi, List reviewers) + throws SQLException, AuthorizeException { + WorkflowItemRole workflowItemRole = workflowItemRoleService.create(c); + workflowItemRole.setRoleId(getRole().getId()); + workflowItemRole.setWorkflowItem(wfi); + if (reviewers.size() == 1) { + // 1 reviewer in workflowitemrole => will be translated into a claimed task in the autoassign + workflowItemRole.setEPerson(reviewers.get(0)); + } else { + // multiple reviewers, create a temporary group and assign this group, the workflowitemrole will be + // translated into a claimed task for reviewers in the autoassign, where group will be deleted + c.turnOffAuthorisationSystem(); + Group selectedReviewsGroup = groupService.create(c); + groupService.setName(selectedReviewsGroup, "selectedReviewsGroup_" + wfi.getID()); + for (EPerson reviewer : reviewers) { + groupService.addMember(c, selectedReviewsGroup, reviewer); + } + workflowItemRole.setGroup(selectedReviewsGroup); + c.restoreAuthSystemState(); + } + workflowItemRoleService.update(c, workflowItemRole); + return workflowItemRole; + } + @Override public List getOptions() { List options = new ArrayList<>(); - options.add(SUBMIT_SEARCH); options.add(SUBMIT_SELECT_REVIEWER); + options.add(RETURN_TO_POOL); return options; } + @Override + protected List getAdvancedOptions() { + return Arrays.asList(SUBMIT_SELECT_REVIEWER); + } + + @Override + protected List getAdvancedInfo() { + List advancedInfo = new ArrayList<>(); + SelectReviewerActionAdvancedInfo selectReviewerActionAdvancedInfo = new SelectReviewerActionAdvancedInfo(); + if (getGroup(null) != null) { + selectReviewerActionAdvancedInfo.setGroup(getGroup(null).getID().toString()); + } + selectReviewerActionAdvancedInfo.setType(SUBMIT_SELECT_REVIEWER); + selectReviewerActionAdvancedInfo.generateId(SUBMIT_SELECT_REVIEWER); + advancedInfo.add(selectReviewerActionAdvancedInfo); + return advancedInfo; + } + public Role getRole() { return role; } @@ -118,4 +209,49 @@ public class SelectReviewerAction extends ProcessingAction { public void setRole(Role role) { this.role = role; } + + /** + * Get the Reviewer group from the "action.selectrevieweraction.group" property in actions.cfg by its UUID or name + * Returns null if no (valid) group configured + * + * @return configured reviewers Group from property or null if none + */ + private Group getGroup(@Nullable Context context) { + if (selectFromReviewsGroupInitialised) { + return this.selectFromReviewsGroup; + } + if (context == null) { + context = new Context(); + } + String groupIdOrName = configurationService.getProperty(CONFIG_REVIEWER_GROUP); + + if (StringUtils.isNotBlank(groupIdOrName)) { + Group group = null; + try { + // try to get group by name + group = groupService.findByName(context, groupIdOrName); + if (group == null) { + // try to get group by uuid if not a name + group = groupService.find(context, UUID.fromString(groupIdOrName)); + } + } catch (Exception e) { + // There is an issue with the reviewer group that is set; if it is not set then can be chosen + // from all epeople + log.error("Issue with determining matching group for config {}={} for reviewer group of " + + "select reviewers workflow", CONFIG_REVIEWER_GROUP, groupIdOrName); + } + + this.selectFromReviewsGroup = group; + } + selectFromReviewsGroupInitialised = true; + return this.selectFromReviewsGroup; + } + + /** + * To be used by IT, e.g. {@code XmlWorkflowServiceIT}, when defining new 'Reviewers' group + */ + static public void resetGroup() { + selectFromReviewsGroup = null; + selectFromReviewsGroupInitialised = false; + } } diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SelectReviewerActionAdvancedInfo.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SelectReviewerActionAdvancedInfo.java new file mode 100644 index 0000000000..7a86a0b03d --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SelectReviewerActionAdvancedInfo.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.xmlworkflow.state.actions.processingaction; + +import org.dspace.xmlworkflow.state.actions.ActionAdvancedInfo; +import org.springframework.util.DigestUtils; + +/** + * Class that holds the advanced information needed for the + * {@link org.dspace.xmlworkflow.state.actions.processingaction.SelectReviewerAction} + * See config {@code workflow-actions.cfg} + */ +public class SelectReviewerActionAdvancedInfo extends ActionAdvancedInfo { + private String group; + + public String getGroup() { + return group; + } + + public void setGroup(String group) { + this.group = group; + } + + @Override + public void generateId(String type) { + String idString = type + + ";group," + group; + super.id = DigestUtils.md5DigestAsHex(idString.getBytes()); + } +} + 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 27cf98f77f..b3fe896ace 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 @@ -13,11 +13,15 @@ import java.util.ArrayList; import java.util.List; import javax.servlet.http.HttpServletRequest; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.app.util.Util; import org.dspace.authorize.AuthorizeException; -import org.dspace.content.DCDate; -import org.dspace.content.MetadataSchemaEnum; +import org.dspace.content.WorkspaceItem; +import org.dspace.content.factory.ContentServiceFactory; import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.dspace.workflow.WorkflowException; import org.dspace.xmlworkflow.factory.XmlWorkflowServiceFactory; import org.dspace.xmlworkflow.state.Step; import org.dspace.xmlworkflow.state.actions.ActionResult; @@ -34,39 +38,59 @@ import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; * @author Mark Diggory (markd at atmire dot com) */ public class SingleUserReviewAction extends ProcessingAction { - - public static final int MAIN_PAGE = 0; - public static final int REJECT_PAGE = 1; - public static final int SUBMITTER_IS_DELETED_PAGE = 2; + private static final Logger log = LogManager.getLogger(SingleUserReviewAction.class); public static final int OUTCOME_REJECT = 1; - protected static final String SUBMIT_APPROVE = "submit_approve"; - protected static final String SUBMIT_REJECT = "submit_reject"; protected static final String SUBMIT_DECLINE_TASK = "submit_decline_task"; @Override public void activate(Context c, XmlWorkflowItem wfItem) { - + // empty } @Override public ActionResult execute(Context c, XmlWorkflowItem wfi, Step step, HttpServletRequest request) - throws SQLException, AuthorizeException, IOException { - int page = Util.getIntParameter(request, "page"); - - switch (page) { - case MAIN_PAGE: - return processMainPage(c, wfi, step, request); - case REJECT_PAGE: - return processRejectPage(c, wfi, step, request); - case SUBMITTER_IS_DELETED_PAGE: - return processSubmitterIsDeletedPage(c, wfi, request); + throws SQLException, AuthorizeException, IOException, WorkflowException { + if (!super.isOptionInParam(request)) { + return new ActionResult(ActionResult.TYPE.TYPE_CANCEL); + } + switch (Util.getSubmitButton(request, SUBMIT_CANCEL)) { + case SUBMIT_APPROVE: + return processAccept(c, wfi); + case SUBMIT_REJECT: + return processReject(c, wfi, request); + case SUBMIT_DECLINE_TASK: + return processDecline(c, wfi); default: return new ActionResult(ActionResult.TYPE.TYPE_CANCEL); } } + /** + * Process {@link super#SUBMIT_REJECT} on this action, will either: + * - If submitter of item no longer exists => Permanently delete corresponding item (no wfi/wsi remaining) + * - Otherwise: reject item back to submission => becomes wsi of submitter again + */ + private ActionResult processReject(Context c, XmlWorkflowItem wfi, HttpServletRequest request) + throws SQLException, IOException, AuthorizeException { + if (wfi.getSubmitter() == null) { + // If the original submitter is no longer there, delete the task + return processDelete(c, wfi); + } else { + return super.processRejectPage(c, wfi, request); + } + } + + /** + * Accept the workflow item => last step in workflow so will be archived + * Info on step & reviewer will be added on metadata dc.description.provenance of resulting item + */ + public ActionResult processAccept(Context c, XmlWorkflowItem wfi) throws SQLException, AuthorizeException { + super.addApprovedProvenance(c, wfi); + return new ActionResult(ActionResult.TYPE.TYPE_OUTCOME, ActionResult.OUTCOME_COMPLETE); + } + @Override public List getOptions() { List options = new ArrayList<>(); @@ -76,87 +100,29 @@ public class SingleUserReviewAction extends ProcessingAction { return options; } - public ActionResult processMainPage(Context c, XmlWorkflowItem wfi, Step step, HttpServletRequest request) - throws SQLException, AuthorizeException { - if (request.getParameter(SUBMIT_APPROVE) != null) { - //Delete the tasks - addApprovedProvenance(c, wfi); - - return new ActionResult(ActionResult.TYPE.TYPE_OUTCOME, ActionResult.OUTCOME_COMPLETE); - } else if (request.getParameter(SUBMIT_REJECT) != null) { - // Make sure we indicate which page we want to process - if (wfi.getSubmitter() == null) { - request.setAttribute("page", SUBMITTER_IS_DELETED_PAGE); - } else { - request.setAttribute("page", REJECT_PAGE); - } - // We have pressed reject item, so take the user to a page where they can reject - return new ActionResult(ActionResult.TYPE.TYPE_PAGE); - } else if (request.getParameter(SUBMIT_DECLINE_TASK) != null) { - return new ActionResult(ActionResult.TYPE.TYPE_OUTCOME, OUTCOME_REJECT); - - } else { - //We pressed the leave button so return to our submissions page - return new ActionResult(ActionResult.TYPE.TYPE_SUBMISSION_PAGE); - } - } - - private void addApprovedProvenance(Context c, XmlWorkflowItem wfi) throws SQLException, AuthorizeException { - //Add the provenance for the accept - String now = DCDate.getCurrent().toString(); - - // Get user's name + email address - String usersName = XmlWorkflowServiceFactory.getInstance().getXmlWorkflowService() - .getEPersonName(c.getCurrentUser()); - - String provDescription = getProvenanceStartId() + " Approved for entry into archive by " - + usersName + " on " + now + " (GMT) "; - - // Add to item as a DC field - itemService.addMetadata(c, wfi.getItem(), MetadataSchemaEnum.DC.getName(), "description", "provenance", "en", - provDescription); - itemService.update(c, wfi.getItem()); - } - - public ActionResult processRejectPage(Context c, XmlWorkflowItem wfi, Step step, HttpServletRequest request) + /** + * Since original submitter no longer exists, workflow item is permanently deleted + */ + private ActionResult processDelete(Context c, XmlWorkflowItem wfi) throws SQLException, AuthorizeException, IOException { - if (request.getParameter("submit_reject") != null) { - String reason = request.getParameter("reason"); - if (reason == null || 0 == reason.trim().length()) { - request.setAttribute("page", REJECT_PAGE); - addErrorField(request, "reason"); - return new ActionResult(ActionResult.TYPE.TYPE_ERROR); - } - - //We have pressed reject, so remove the task the user has & put it back to a workspace item - XmlWorkflowServiceFactory.getInstance().getXmlWorkflowService() - .sendWorkflowItemBackSubmission(c, wfi, c.getCurrentUser(), - this.getProvenanceStartId(), reason); - - - return new ActionResult(ActionResult.TYPE.TYPE_SUBMISSION_PAGE); - } else { - //Cancel, go back to the main task page - request.setAttribute("page", MAIN_PAGE); - - return new ActionResult(ActionResult.TYPE.TYPE_PAGE); - } + EPerson user = c.getCurrentUser(); + c.turnOffAuthorisationSystem(); + WorkspaceItem workspaceItem = XmlWorkflowServiceFactory.getInstance().getXmlWorkflowService() + .abort(c, wfi, user); + ContentServiceFactory.getInstance().getWorkspaceItemService().deleteAll(c, workspaceItem); + c.restoreAuthSystemState(); + return new ActionResult(ActionResult.TYPE.TYPE_SUBMISSION_PAGE); } - public ActionResult processSubmitterIsDeletedPage(Context c, XmlWorkflowItem wfi, HttpServletRequest request) - throws SQLException, AuthorizeException, IOException { - if (request.getParameter("submit_delete") != null) { - XmlWorkflowServiceFactory.getInstance().getXmlWorkflowService() - .deleteWorkflowByWorkflowItem(c, wfi, c.getCurrentUser()); - // Delete and send user back to myDspace page - return new ActionResult(ActionResult.TYPE.TYPE_SUBMISSION_PAGE); - } else if (request.getParameter("submit_keep_it") != null) { - // Do nothing, just send it back to myDspace page - return new ActionResult(ActionResult.TYPE.TYPE_SUBMISSION_PAGE); - } else { - //Cancel, go back to the main task page - request.setAttribute("page", MAIN_PAGE); - return new ActionResult(ActionResult.TYPE.TYPE_PAGE); - } + /** + * Selected reviewer declines to review task, then the workflow is aborted and restarted + */ + private ActionResult processDecline(Context c, XmlWorkflowItem wfi) + throws SQLException, IOException, AuthorizeException, WorkflowException { + c.turnOffAuthorisationSystem(); + xmlWorkflowService.restartWorkflow(c, wfi, c.getCurrentUser(), this.getProvenanceStartId()); + c.restoreAuthSystemState(); + return new ActionResult(ActionResult.TYPE.TYPE_SUBMISSION_PAGE); } + } 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 51f4bf0a93..401a7c506b 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 @@ -80,6 +80,10 @@ public class AutoAssignAction extends UserSelectionAction { } //Delete our workflow item role since the users have been assigned workflowItemRoleService.delete(c, workflowItemRole); + if (role.isDeleteTemporaryGroup() && workflowItemRole.getGroup() != null) { + // Delete temporary groups created after members have workflow task assigned + groupService.delete(c, workflowItemRole.getGroup()); + } } } else { log.warn(LogHelper.getHeader(c, "Error while executing auto assign action", @@ -127,7 +131,7 @@ public class AutoAssignAction extends UserSelectionAction { protected void createTaskForEPerson(Context c, XmlWorkflowItem wfi, Step step, WorkflowActionConfig actionConfig, EPerson user) throws SQLException, AuthorizeException, IOException { if (claimedTaskService.find(c, wfi, step.getId(), actionConfig.getId()) != null) { - workflowRequirementsService.addClaimedUser(c, wfi, step, c.getCurrentUser()); + workflowRequirementsService.addClaimedUser(c, wfi, step, user); XmlWorkflowServiceFactory.getInstance().getXmlWorkflowService() .createOwnedTask(c, wfi, step, actionConfig, user); } 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 c9c61908aa..21fcf6f309 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 @@ -138,6 +138,10 @@ public class ClaimAction extends UserSelectionAction { RoleMembers roleMembers = role.getMembers(context, wfi); ArrayList epersons = roleMembers.getAllUniqueMembers(context); + if (epersons.isEmpty() || step.getRequiredUsers() > epersons.size()) { + log.warn(String.format("There must be at least %s ePerson(s) in the group", + step.getRequiredUsers())); + } return !(epersons.isEmpty() || step.getRequiredUsers() > epersons.size()); } else { // We don't have a role and do have a UI so throw a workflow exception diff --git a/dspace-api/src/main/resources/Messages.properties b/dspace-api/src/main/resources/Messages.properties index c478e4e69b..9be443f5ea 100644 --- a/dspace-api/src/main/resources/Messages.properties +++ b/dspace-api/src/main/resources/Messages.properties @@ -51,6 +51,7 @@ 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 diff --git a/dspace-api/src/main/resources/org/dspace/license/CreativeCommons.xsl b/dspace-api/src/main/resources/org/dspace/license/CreativeCommons.xsl index f32942a302..d9f6cd3614 100644 --- a/dspace-api/src/main/resources/org/dspace/license/CreativeCommons.xsl +++ b/dspace-api/src/main/resources/org/dspace/license/CreativeCommons.xsl @@ -8,7 +8,7 @@ http://www.dspace.org/license/ --> - @@ -47,4 +47,4 @@ - \ No newline at end of file + diff --git a/dspace-api/src/main/resources/org/dspace/license/LicenseCleanup.xsl b/dspace-api/src/main/resources/org/dspace/license/LicenseCleanup.xsl index 84c62158fe..d9a9745a1b 100644 --- a/dspace-api/src/main/resources/org/dspace/license/LicenseCleanup.xsl +++ b/dspace-api/src/main/resources/org/dspace/license/LicenseCleanup.xsl @@ -8,7 +8,7 @@ http://www.dspace.org/license/ --> - - \ No newline at end of file + diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/flywayupgrade/oracle/upgradeToFlyway4x.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/flywayupgrade/oracle/upgradeToFlyway4x.sql deleted file mode 100644 index 7907fccc00..0000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/flywayupgrade/oracle/upgradeToFlyway4x.sql +++ /dev/null @@ -1,29 +0,0 @@ --- --- Copyright 2010-2017 Boxfuse GmbH --- --- Licensed under the Apache License, Version 2.0 (the "License"); --- you may not use this file except in compliance with the License. --- You may obtain a copy of the License at --- --- http://www.apache.org/licenses/LICENSE-2.0 --- --- Unless required by applicable law or agreed to in writing, software --- distributed under the License is distributed on an "AS IS" BASIS, --- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. --- See the License for the specific language governing permissions and --- limitations under the License. --- ------------------ --- This is the Oracle upgrade script from Flyway v4.2.0, copied/borrowed from: --- https://github.com/flyway/flyway/blob/flyway-4.2.0/flyway-core/src/main/resources/org/flywaydb/core/internal/dbsupport/oracle/upgradeMetaDataTable.sql --- --- The variables in this script are replaced in FlywayUpgradeUtils.upgradeFlywayTable() ------------------- - -DROP INDEX "${schema}"."${table}_vr_idx"; -DROP INDEX "${schema}"."${table}_ir_idx"; -ALTER TABLE "${schema}"."${table}" DROP COLUMN "version_rank"; -ALTER TABLE "${schema}"."${table}" DROP PRIMARY KEY DROP INDEX; -ALTER TABLE "${schema}"."${table}" MODIFY "version" NULL; -ALTER TABLE "${schema}"."${table}" ADD CONSTRAINT "${table}_pk" PRIMARY KEY ("installed_rank"); -UPDATE "${schema}"."${table}" SET "type"='BASELINE' WHERE "type"='INIT'; diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/flywayupgrade/postgres/upgradeToFlyway4x.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/flywayupgrade/postgres/upgradeToFlyway4x.sql index 7548fa4c6a..edebe6e087 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/flywayupgrade/postgres/upgradeToFlyway4x.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/flywayupgrade/postgres/upgradeToFlyway4x.sql @@ -15,7 +15,7 @@ -- ----------------- -- This is the PostgreSQL upgrade script from Flyway v4.2.0, copied/borrowed from: --- https://github.com/flyway/flyway/blob/flyway-4.2.0/flyway-core/src/main/resources/org/flywaydb/core/internal/dbsupport/oracle/upgradeMetaDataTable.sql +-- https://github.com/flyway/flyway/blob/flyway-4.2.0/flyway-core/src/main/resources/org/flywaydb/core/internal/dbsupport/postgresql/upgradeMetaDataTable.sql -- -- The variables in this script are replaced in FlywayUpgradeUtils.upgradeFlywayTable() ------------------ diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/README.md b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/README.md index 8088c6ccca..87e114ca53 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/README.md +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/README.md @@ -4,33 +4,25 @@ in Production. Instead, DSpace uses the H2 Database to perform Unit Testing during development. -By default, the DSpace Unit Testing environment configures H2 to run in -"Oracle Mode" and initializes the H2 database using the scripts in this directory. +By default, the DSpace Unit Testing environment configures H2 to run in memory +and initializes the H2 database using the scripts in this directory. See +`[src]/dspace-api/src/test/data/dspaceFolder/config/local.cfg`. + These database migrations are automatically called by [Flyway](http://flywaydb.org/) -when the `DatabaseManager` initializes itself (see `initializeDatabase()` method). +in `DatabaseUtils`. -The H2 migrations in this directory are *based on* the Oracle Migrations, but -with some modifications in order to be valid in H2. +The H2 migrations in this directory all use H2's grammar/syntax. +For additional info see the [H2 SQL Grammar](https://www.h2database.com/html/grammar.html). -## Oracle vs H2 script differences - -One of the primary differences between the Oracle scripts and these H2 ones -is in the syntax of the `ALTER TABLE` command. Unfortunately, H2's syntax for -that command differs greatly from Oracle (and PostgreSQL as well). - -Most of the remainder of the scripts contain the exact Oracle syntax (which is -usually valid in H2). But, to you can always `diff` scripts of the same name -for further syntax differences. - -For additional info see the [H2 SQL Grammar](http://www.h2database.com/html/grammar.html). ## More Information on Flyway The SQL scripts in this directory are H2-specific database migrations. They are used to automatically upgrade your DSpace database using [Flyway](http://flywaydb.org/). As such, these scripts are automatically called by Flyway when the DSpace -`DatabaseManager` initializes itself (see `initializeDatabase()` method). During -that process, Flyway determines which version of DSpace your database is using +`DatabaseUtils` initializes. + +During that process, Flyway determines which version of DSpace your database is using and then executes the appropriate upgrade script(s) to bring it up to the latest version. diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.5_2022.12.01__add_table_subscriptionparamter_change_columns_subscription_table.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.5_2022.12.01__add_table_subscriptionparamter_change_columns_subscription_table.sql new file mode 100644 index 0000000000..dc187d3c27 --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.5_2022.12.01__add_table_subscriptionparamter_change_columns_subscription_table.sql @@ -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/ +-- + +----------------------------------------------------------------------------------- +-- ADD table subscription_parameter +----------------------------------------------------------------------------------- + + +CREATE SEQUENCE if NOT EXISTS subscription_parameter_seq; +------------------------------------------------------- +-- Create the subscription_parameter table +------------------------------------------------------- + +CREATE TABLE if NOT EXISTS subscription_parameter +( + subscription_parameter_id INTEGER NOT NULL, + name CHARACTER VARYING(255), + value CHARACTER VARYING(255), + subscription_id INTEGER NOT NULL, + CONSTRAINT subscription_parameter_pkey PRIMARY KEY (subscription_parameter_id), + CONSTRAINT subscription_parameter_subscription_fkey FOREIGN KEY (subscription_id) REFERENCES subscription (subscription_id) ON DELETE CASCADE +); + +-- +ALTER TABLE subscription ADD COLUMN if NOT EXISTS dspace_object_id UUID; +-- +ALTER TABLE subscription ADD COLUMN if NOT EXISTS type CHARACTER VARYING(255); +-- +ALTER TABLE subscription DROP CONSTRAINT IF EXISTS subscription_dspaceobject_fkey; +ALTER TABLE subscription ADD CONSTRAINT subscription_dspaceobject_fkey FOREIGN KEY (dspace_object_id) REFERENCES dspaceobject (uuid); +-- -- +UPDATE subscription set dspace_object_id = collection_id , type = 'content'; +-- +ALTER TABLE subscription DROP CONSTRAINT IF EXISTS Subscription_collection_id_fk; +-- +ALTER TABLE subscription DROP COLUMN IF EXISTS collection_id; + + + diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.5_2022.12.06__index_action_resource_policy.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.5_2022.12.06__index_action_resource_policy.sql new file mode 100644 index 0000000000..696e84433d --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.5_2022.12.06__index_action_resource_policy.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/ +-- + +CREATE INDEX resourcepolicy_action_idx ON resourcepolicy(action_id); diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.3_2022.06.16__process_to_group.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.5_2022.12.09__Supervision_Orders_table.sql similarity index 57% rename from dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.3_2022.06.16__process_to_group.sql rename to dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.5_2022.12.09__Supervision_Orders_table.sql index 0e7d417ae5..33d3eb5c82 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.3_2022.06.16__process_to_group.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.5_2022.12.09__Supervision_Orders_table.sql @@ -7,12 +7,14 @@ -- ------------------------------------------------------------------------------- --- Table to store Groups related to a Process on its creation +-- Table to store supervision orders ------------------------------------------------------------------------------- -CREATE TABLE Process2Group +CREATE TABLE supervision_orders ( - process_id INTEGER REFERENCES Process(process_id), - group_id UUID REFERENCES epersongroup (uuid) ON DELETE CASCADE, - CONSTRAINT PK_Process2Group PRIMARY KEY (process_id, group_id) -); \ No newline at end of file + id INTEGER PRIMARY KEY, + item_id UUID REFERENCES Item(uuid) ON DELETE CASCADE, + eperson_group_id UUID REFERENCES epersongroup(uuid) ON DELETE CASCADE +); + +CREATE SEQUENCE supervision_orders_seq; diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.5_2022.12.15__system_wide_alerts.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.5_2022.12.15__system_wide_alerts.sql new file mode 100644 index 0000000000..9d13138fda --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.5_2022.12.15__system_wide_alerts.sql @@ -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/ +-- + +----------------------------------------------------------------------------------- +-- Create table for System wide alerts +----------------------------------------------------------------------------------- + +CREATE SEQUENCE alert_id_seq; + +CREATE TABLE systemwidealert +( + alert_id INTEGER NOT NULL PRIMARY KEY, + message VARCHAR(512), + allow_sessions VARCHAR(64), + countdown_to TIMESTAMP, + active BOOLEAN +); diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2021.02.08__tilted_rels.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2023.03.17__Remove_unused_sequence.sql similarity index 77% rename from dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2021.02.08__tilted_rels.sql rename to dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2023.03.17__Remove_unused_sequence.sql index 95d07be477..47cd157336 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2021.02.08__tilted_rels.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2023.03.17__Remove_unused_sequence.sql @@ -7,7 +7,7 @@ -- ----------------------------------------------------------------------------------- --- Create columns copy_left and copy_right for RelationshipType +-- Drop the 'history_seq' sequence (related table deleted at Dspace-1.5) ----------------------------------------------------------------------------------- -ALTER TABLE relationship_type ADD tilted INTEGER; +DROP SEQUENCE history_seq; \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2019.11.13__relationship_type_copy_left_right.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2023.03.24__Update_PNG_in_bitstream_format_registry.sql similarity index 60% rename from dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2019.11.13__relationship_type_copy_left_right.sql rename to dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2023.03.24__Update_PNG_in_bitstream_format_registry.sql index 0db294c1c1..8aec44a7f6 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2019.11.13__relationship_type_copy_left_right.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2023.03.24__Update_PNG_in_bitstream_format_registry.sql @@ -7,8 +7,11 @@ -- ----------------------------------------------------------------------------------- --- Create columns copy_left and copy_right for RelationshipType +-- Update short description for PNG mimetype in the bitstream format registry +-- See: https://github.com/DSpace/DSpace/pull/8722 ----------------------------------------------------------------------------------- -ALTER TABLE relationship_type ADD copy_to_left NUMBER(1) DEFAULT 0 NOT NULL; -ALTER TABLE relationship_type ADD copy_to_right NUMBER(1) DEFAULT 0 NOT NULL; +UPDATE bitstreamformatregistry +SET short_description='PNG' +WHERE short_description='image/png' + AND mimetype='image/png'; diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2023.03.29__orcid_queue_and_history_descriptions_to_text_type.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2023.03.29__orcid_queue_and_history_descriptions_to_text_type.sql new file mode 100644 index 0000000000..7641eb9fc2 --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2023.03.29__orcid_queue_and_history_descriptions_to_text_type.sql @@ -0,0 +1,10 @@ +-- +-- The contents of this file are subject to the license and 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 orcid_history ALTER COLUMN description SET DATA TYPE CLOB; +ALTER TABLE orcid_queue ALTER COLUMN description SET DATA TYPE CLOB; diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2023.04.19__process_parameters_to_text_type.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2023.04.19__process_parameters_to_text_type.sql new file mode 100644 index 0000000000..1028ba370c --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2023.04.19__process_parameters_to_text_type.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 parameters SET DATA TYPE CLOB; diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/metadata/oracle/V7.0_2020.10.31__CollectionCommunity_Metadata_Handle.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/metadata/oracle/V7.0_2020.10.31__CollectionCommunity_Metadata_Handle.sql deleted file mode 100644 index fff1fe154f..0000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/metadata/oracle/V7.0_2020.10.31__CollectionCommunity_Metadata_Handle.sql +++ /dev/null @@ -1,90 +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/ --- - --- =============================================================== --- WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING --- --- DO NOT MANUALLY RUN THIS DATABASE MIGRATION. IT WILL BE EXECUTED --- AUTOMATICALLY (IF NEEDED) BY "FLYWAY" WHEN YOU STARTUP DSPACE. --- http://flywaydb.org/ --- =============================================================== - -------------------------------------------------------------- --- This will create COMMUNITY handle metadata -------------------------------------------------------------- - -insert into metadatavalue (metadata_field_id, text_value, text_lang, place, authority, confidence, dspace_object_id) - select distinct - T1.metadata_field_id as metadata_field_id, - concat('${handle.canonical.prefix}', h.handle) as text_value, - null as text_lang, 0 as place, - null as authority, - -1 as confidence, - c.uuid as dspace_object_id - - from community c - left outer join handle h on h.resource_id = c.uuid - left outer join metadatavalue mv on mv.dspace_object_id = c.uuid - left outer join metadatafieldregistry mfr on mv.metadata_field_id = mfr.metadata_field_id - left outer join metadataschemaregistry msr on mfr.metadata_schema_id = msr.metadata_schema_id - - cross join (select mfr.metadata_field_id as metadata_field_id from metadatafieldregistry mfr - left outer join metadataschemaregistry msr on mfr.metadata_schema_id = msr.metadata_schema_id - where msr.short_id = 'dc' - and mfr.element = 'identifier' - and mfr.qualifier = 'uri') T1 - - where uuid not in ( - select c.uuid as uuid from community c - left outer join handle h on h.resource_id = c.uuid - left outer join metadatavalue mv on mv.dspace_object_id = c.uuid - left outer join metadatafieldregistry mfr on mv.metadata_field_id = mfr.metadata_field_id - left outer join metadataschemaregistry msr on mfr.metadata_schema_id = msr.metadata_schema_id - where msr.short_id = 'dc' - and mfr.element = 'identifier' - and mfr.qualifier = 'uri' - ) -; - -------------------------------------------------------------- --- This will create COLLECTION handle metadata -------------------------------------------------------------- - -insert into metadatavalue (metadata_field_id, text_value, text_lang, place, authority, confidence, dspace_object_id) - select distinct - T1.metadata_field_id as metadata_field_id, - concat('${handle.canonical.prefix}', h.handle) as text_value, - null as text_lang, 0 as place, - null as authority, - -1 as confidence, - c.uuid as dspace_object_id - - from collection c - left outer join handle h on h.resource_id = c.uuid - left outer join metadatavalue mv on mv.dspace_object_id = c.uuid - left outer join metadatafieldregistry mfr on mv.metadata_field_id = mfr.metadata_field_id - left outer join metadataschemaregistry msr on mfr.metadata_schema_id = msr.metadata_schema_id - - cross join (select mfr.metadata_field_id as metadata_field_id from metadatafieldregistry mfr - left outer join metadataschemaregistry msr on mfr.metadata_schema_id = msr.metadata_schema_id - where msr.short_id = 'dc' - and mfr.element = 'identifier' - and mfr.qualifier = 'uri') T1 - - where uuid not in ( - select c.uuid as uuid from collection c - left outer join handle h on h.resource_id = c.uuid - left outer join metadatavalue mv on mv.dspace_object_id = c.uuid - left outer join metadatafieldregistry mfr on mv.metadata_field_id = mfr.metadata_field_id - left outer join metadataschemaregistry msr on mfr.metadata_schema_id = msr.metadata_schema_id - where msr.short_id = 'dc' - and mfr.element = 'identifier' - and mfr.qualifier = 'uri' - ) -; - diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/README.md b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/README.md deleted file mode 100644 index 6cef123859..0000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/README.md +++ /dev/null @@ -1,84 +0,0 @@ -# Oracle Flyway Database Migrations (i.e. Upgrades) - ---- -WARNING: Oracle Support is deprecated. -See https://github.com/DSpace/DSpace/issues/8214 ---- - -The SQL scripts in this directory are Oracle-specific database migrations. They are -used to automatically upgrade your DSpace database using [Flyway](http://flywaydb.org/). -As such, these scripts are automatically called by Flyway when the DSpace -`DatabaseManager` initializes itself (see `initializeDatabase()` method). During -that process, Flyway determines which version of DSpace your database is using -and then executes the appropriate upgrade script(s) to bring it up to the latest -version. - -If any failures occur, Flyway will "rollback" the upgrade script which resulted -in an error and log the issue in the DSpace log file at `[dspace]/log/dspace.log.[date]` - -**WARNING:** IT IS NOT RECOMMENDED TO RUN THESE SCRIPTS MANUALLY. If you do so, -Flyway will may throw failures the next time you startup DSpace, as Flyway will -not realize you manually ran one or more scripts. - -Please see the Flyway Documentation for more information: http://flywaydb.org/ - -## Oracle Porting Notes for the Curious - -Oracle is missing quite a number of cool features found in Postgres, so -workarounds had to be found, most of which are hidden behind tests in -DatabaseManager. If Oracle is your DBMS, the workarounds are activated: - -Oracle doesn't like ';' characters in JDBC SQL - they have all been removed -from the DSpace source, including code in the .sql file reader to strip ;'s. - -browse code - LIMIT and OFFSET is used to limit browse results, and an -Oracle-hack is used to limit the result set to a given size - -Oracle has no boolean data type, so a new schema file was created that -uses NUMBER(1) (AKA 'integers') and code is inserted everywhere to use 0 for -false and 1 for true if DSpace is using Oracle. - -Oracle doesn't have a TEXT data type either, so TEXT columns are defined -as VARCHAR2 in the Oracle-specific schema. - -Oracle doesn't allow dynamic naming for objects, so our cute trick to -derive the name of the sequence by appending _seq to the table name -in a function doesn't work in Oracle - workaround is to insert Oracle -code to generate the name of the sequence and then place that into -our SQL calls to generate a new ID. - -Oracle doesn't let you directly set the value of sequences, so -update-sequences.sql is forced to use a special script sequpdate.sql -to update the sequences. - -Bitstream had a column 'size' which is a reserved word in Oracle, -so this had to be changed to 'size_bytes' with corresponding code changes. - -VARCHAR2 has a limit of 4000 characters, so DSpace text data is limited to 4k. -Going to the CLOB data type can get around that, but seemed like too much effort -for now. Note that with UTF-8 encoding that 4k could translate to 1300 -characters worst-case (every character taking up 3 bytes is the worst case -scenario.) - -### UPDATE 5 April 2007 - -CLOBs are now used as follows: -MetadataValue:text_value -Community:introductory_text -Community:copyright_text -Collection:introductory_text -Collection:license -Collection:copyright_text - -DatabaseManager had to have some of the type checking changed, because Oracle's -JDBC driver is reporting INTEGERS as type DECIMAL. - -Oracle doesn't like it when you reference table names in lower case when -getting JDBC metadata for the tables, so they are converted in TableRow -to upper case. - -### UPDATE 27 November 2012 - -Oracle complains with ORA-01408 if you attempt to create an index on a column which -has already had the UNIQUE contraint added (such an index is implicit in maintaining the uniqueness -of the column). See [DS-1370](https://jira.duraspace.org/browse/DS-1370) for details. diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V1.2__Initial_DSpace_1.2_Oracle_database_schema.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V1.2__Initial_DSpace_1.2_Oracle_database_schema.sql deleted file mode 100644 index 157274e05d..0000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V1.2__Initial_DSpace_1.2_Oracle_database_schema.sql +++ /dev/null @@ -1,550 +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/ --- - --- =============================================================== --- WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING --- --- DO NOT MANUALLY RUN THIS DATABASE MIGRATION. IT WILL BE EXECUTED --- AUTOMATICALLY (IF NEEDED) BY "FLYWAY" WHEN YOU STARTUP DSPACE. --- http://flywaydb.org/ --- =============================================================== - -CREATE SEQUENCE bitstreamformatregistry_seq; -CREATE SEQUENCE fileextension_seq; -CREATE SEQUENCE bitstream_seq; -CREATE SEQUENCE eperson_seq; --- start group sequence at 0, since Anonymous group = 0 -CREATE SEQUENCE epersongroup_seq MINVALUE 0 START WITH 0; -CREATE SEQUENCE item_seq; -CREATE SEQUENCE bundle_seq; -CREATE SEQUENCE item2bundle_seq; -CREATE SEQUENCE bundle2bitstream_seq; -CREATE SEQUENCE dctyperegistry_seq; -CREATE SEQUENCE dcvalue_seq; -CREATE SEQUENCE community_seq; -CREATE SEQUENCE collection_seq; -CREATE SEQUENCE community2community_seq; -CREATE SEQUENCE community2collection_seq; -CREATE SEQUENCE collection2item_seq; -CREATE SEQUENCE resourcepolicy_seq; -CREATE SEQUENCE epersongroup2eperson_seq; -CREATE SEQUENCE handle_seq; -CREATE SEQUENCE workspaceitem_seq; -CREATE SEQUENCE workflowitem_seq; -CREATE SEQUENCE tasklistitem_seq; -CREATE SEQUENCE registrationdata_seq; -CREATE SEQUENCE subscription_seq; -CREATE SEQUENCE history_seq; -CREATE SEQUENCE historystate_seq; -CREATE SEQUENCE communities2item_seq; -CREATE SEQUENCE itemsbyauthor_seq; -CREATE SEQUENCE itemsbytitle_seq; -CREATE SEQUENCE itemsbydate_seq; -CREATE SEQUENCE itemsbydateaccessioned_seq; - - -------------------------------------------------------- --- BitstreamFormatRegistry table -------------------------------------------------------- -CREATE TABLE BitstreamFormatRegistry -( - bitstream_format_id INTEGER PRIMARY KEY, - mimetype VARCHAR2(48), - short_description VARCHAR2(128) UNIQUE, - description VARCHAR2(2000), - support_level INTEGER, - -- Identifies internal types - internal NUMBER(1) -); - -------------------------------------------------------- --- FileExtension table -------------------------------------------------------- -CREATE TABLE FileExtension -( - file_extension_id INTEGER PRIMARY KEY, - bitstream_format_id INTEGER REFERENCES BitstreamFormatRegistry(bitstream_format_id), - extension VARCHAR2(16) -); - -------------------------------------------------------- --- Bitstream table -------------------------------------------------------- -CREATE TABLE Bitstream -( - bitstream_id INTEGER PRIMARY KEY, - bitstream_format_id INTEGER REFERENCES BitstreamFormatRegistry(bitstream_format_id), - name VARCHAR2(256), - size_bytes INTEGER, - checksum VARCHAR2(64), - checksum_algorithm VARCHAR2(32), - description VARCHAR2(2000), - user_format_description VARCHAR2(2000), - source VARCHAR2(256), - internal_id VARCHAR2(256), - deleted NUMBER(1), - store_number INTEGER, - sequence_id INTEGER -); - -------------------------------------------------------- --- EPerson table -------------------------------------------------------- -CREATE TABLE EPerson -( - eperson_id INTEGER PRIMARY KEY, - email VARCHAR2(64) UNIQUE, - password VARCHAR2(64), - firstname VARCHAR2(64), - lastname VARCHAR2(64), - can_log_in NUMBER(1), - require_certificate NUMBER(1), - self_registered NUMBER(1), - last_active TIMESTAMP, - sub_frequency INTEGER, - phone VARCHAR2(32) -); - -------------------------------------------------------- --- EPersonGroup table -------------------------------------------------------- -CREATE TABLE EPersonGroup -( - eperson_group_id INTEGER PRIMARY KEY, - name VARCHAR2(256) UNIQUE -); - -------------------------------------------------------- --- Item table -------------------------------------------------------- -CREATE TABLE Item -( - item_id INTEGER PRIMARY KEY, - submitter_id INTEGER REFERENCES EPerson(eperson_id), - in_archive NUMBER(1), - withdrawn NUMBER(1), - last_modified TIMESTAMP, - owning_collection INTEGER -); - -------------------------------------------------------- --- Bundle table -------------------------------------------------------- -CREATE TABLE Bundle -( - bundle_id INTEGER PRIMARY KEY, - mets_bitstream_id INTEGER REFERENCES Bitstream(bitstream_id), - name VARCHAR2(16), -- ORIGINAL | THUMBNAIL | TEXT - primary_bitstream_id INTEGER REFERENCES Bitstream(bitstream_id) -); - -------------------------------------------------------- --- Item2Bundle table -------------------------------------------------------- -CREATE TABLE Item2Bundle -( - id INTEGER PRIMARY KEY, - item_id INTEGER REFERENCES Item(item_id), - bundle_id INTEGER REFERENCES Bundle(bundle_id) -); - --- index by item_id -CREATE INDEX item2bundle_item_idx on Item2Bundle(item_id); - -------------------------------------------------------- --- Bundle2Bitstream table -------------------------------------------------------- -CREATE TABLE Bundle2Bitstream -( - id INTEGER PRIMARY KEY, - bundle_id INTEGER REFERENCES Bundle(bundle_id), - bitstream_id INTEGER REFERENCES Bitstream(bitstream_id) -); - --- index by bundle_id -CREATE INDEX bundle2bitstream_bundle_idx ON Bundle2Bitstream(bundle_id); - -------------------------------------------------------- --- DCTypeRegistry table -------------------------------------------------------- -CREATE TABLE DCTypeRegistry -( - dc_type_id INTEGER PRIMARY KEY, - element VARCHAR2(64), - qualifier VARCHAR2(64), - scope_note VARCHAR2(2000), - UNIQUE(element, qualifier) -); - -------------------------------------------------------- --- DCValue table -------------------------------------------------------- -CREATE TABLE DCValue -( - dc_value_id INTEGER PRIMARY KEY, - item_id INTEGER REFERENCES Item(item_id), - dc_type_id INTEGER REFERENCES DCTypeRegistry(dc_type_id), - text_value VARCHAR2(2000), - text_lang VARCHAR2(24), - place INTEGER, - source_id INTEGER -); - --- An index for item_id - almost all access is based on --- instantiating the item object, which grabs all dcvalues --- related to that item -CREATE INDEX dcvalue_item_idx on DCValue(item_id); - -------------------------------------------------------- --- Community table -------------------------------------------------------- -CREATE TABLE Community -( - community_id INTEGER PRIMARY KEY, - name VARCHAR2(128) UNIQUE, - short_description VARCHAR2(512), - introductory_text VARCHAR2(2000), - logo_bitstream_id INTEGER REFERENCES Bitstream(bitstream_id), - copyright_text VARCHAR2(2000), - side_bar_text VARCHAR2(2000) -); - -------------------------------------------------------- --- Collection table -------------------------------------------------------- -CREATE TABLE Collection -( - collection_id INTEGER PRIMARY KEY, - name VARCHAR2(128), - short_description VARCHAR2(512), - introductory_text VARCHAR2(2000), - logo_bitstream_id INTEGER REFERENCES Bitstream(bitstream_id), - template_item_id INTEGER REFERENCES Item(item_id), - provenance_description VARCHAR2(2000), - license VARCHAR2(2000), - copyright_text VARCHAR2(2000), - side_bar_text VARCHAR2(2000), - workflow_step_1 INTEGER REFERENCES EPersonGroup( eperson_group_id ), - workflow_step_2 INTEGER REFERENCES EPersonGroup( eperson_group_id ), - workflow_step_3 INTEGER REFERENCES EPersonGroup( eperson_group_id ) -); - -------------------------------------------------------- --- Community2Community table -------------------------------------------------------- -CREATE TABLE Community2Community -( - id INTEGER PRIMARY KEY, - parent_comm_id INTEGER REFERENCES Community(community_id), - child_comm_id INTEGER REFERENCES Community(community_id) -); - -------------------------------------------------------- --- Community2Collection table -------------------------------------------------------- -CREATE TABLE Community2Collection -( - id INTEGER PRIMARY KEY, - community_id INTEGER REFERENCES Community(community_id), - collection_id INTEGER REFERENCES Collection(collection_id) -); - -------------------------------------------------------- --- Collection2Item table -------------------------------------------------------- -CREATE TABLE Collection2Item -( - id INTEGER PRIMARY KEY, - collection_id INTEGER REFERENCES Collection(collection_id), - item_id INTEGER REFERENCES Item(item_id) -); - --- index by collection_id -CREATE INDEX collection2item_collection_idx ON Collection2Item(collection_id); - -------------------------------------------------------- --- ResourcePolicy table -------------------------------------------------------- -CREATE TABLE ResourcePolicy -( - policy_id INTEGER PRIMARY KEY, - resource_type_id INTEGER, - resource_id INTEGER, - action_id INTEGER, - eperson_id INTEGER REFERENCES EPerson(eperson_id), - epersongroup_id INTEGER REFERENCES EPersonGroup(eperson_group_id), - start_date DATE, - end_date DATE -); - --- index by resource_type,resource_id - all queries by --- authorization manager are select type=x, id=y, action=z -CREATE INDEX resourcepolicy_type_id_idx ON ResourcePolicy(resource_type_id,resource_id); - -------------------------------------------------------- --- EPersonGroup2EPerson table -------------------------------------------------------- -CREATE TABLE EPersonGroup2EPerson -( - id INTEGER PRIMARY KEY, - eperson_group_id INTEGER REFERENCES EPersonGroup(eperson_group_id), - eperson_id INTEGER REFERENCES EPerson(eperson_id) -); - --- Index by group ID (used heavily by AuthorizeManager) -CREATE INDEX epersongroup2eperson_group_idx on EPersonGroup2EPerson(eperson_group_id); - - -------------------------------------------------------- --- Handle table -------------------------------------------------------- -CREATE TABLE Handle -( - handle_id INTEGER PRIMARY KEY, - handle VARCHAR2(256) UNIQUE, - resource_type_id INTEGER, - resource_id INTEGER -); - -------------------------------------------------------- --- WorkspaceItem table -------------------------------------------------------- -CREATE TABLE WorkspaceItem -( - workspace_item_id INTEGER PRIMARY KEY, - item_id INTEGER REFERENCES Item(item_id), - collection_id INTEGER REFERENCES Collection(collection_id), - -- Answers to questions on first page of submit UI - multiple_titles NUMBER(1), -- boolean - published_before NUMBER(1), - multiple_files NUMBER(1), - -- How for the user has got in the submit process - stage_reached INTEGER -); - -------------------------------------------------------- --- WorkflowItem table -------------------------------------------------------- -CREATE TABLE WorkflowItem -( - workflow_id INTEGER PRIMARY KEY, - item_id INTEGER UNIQUE REFERENCES Item(item_id), - collection_id INTEGER REFERENCES Collection(collection_id), - state INTEGER, - owner INTEGER REFERENCES EPerson(eperson_id), - - -- Answers to questions on first page of submit UI - multiple_titles NUMBER(1), - published_before NUMBER(1), - multiple_files NUMBER(1) - -- Note: stage reached not applicable here - people involved in workflow - -- can always jump around submission UI - -); - -------------------------------------------------------- --- TasklistItem table -------------------------------------------------------- -CREATE TABLE TasklistItem -( - tasklist_id INTEGER PRIMARY KEY, - eperson_id INTEGER REFERENCES EPerson(eperson_id), - workflow_id INTEGER REFERENCES WorkflowItem(workflow_id) -); - - -------------------------------------------------------- --- RegistrationData table -------------------------------------------------------- -CREATE TABLE RegistrationData -( - registrationdata_id INTEGER PRIMARY KEY, - email VARCHAR2(64) UNIQUE, - token VARCHAR2(48), - expires TIMESTAMP -); - - -------------------------------------------------------- --- Subscription table -------------------------------------------------------- -CREATE TABLE Subscription -( - subscription_id INTEGER PRIMARY KEY, - eperson_id INTEGER REFERENCES EPerson(eperson_id), - collection_id INTEGER REFERENCES Collection(collection_id) -); - - -------------------------------------------------------- --- History table -------------------------------------------------------- -CREATE TABLE History -( - history_id INTEGER PRIMARY KEY, - -- When it was stored - creation_date TIMESTAMP, - -- A checksum to keep INTEGERizations from being stored more than once - checksum VARCHAR2(32) UNIQUE -); - -------------------------------------------------------- --- HistoryState table -------------------------------------------------------- -CREATE TABLE HistoryState -( - history_state_id INTEGER PRIMARY KEY, - object_id VARCHAR2(64) -); - ------------------------------------------------------------- --- Browse subsystem tables and views ------------------------------------------------------------- - -------------------------------------------------------- --- Communities2Item table -------------------------------------------------------- -CREATE TABLE Communities2Item -( - id INTEGER PRIMARY KEY, - community_id INTEGER REFERENCES Community(community_id), - item_id INTEGER REFERENCES Item(item_id) -); - -------------------------------------------------------- --- Community2Item view ------------------------------------------------------- -CREATE VIEW Community2Item as -SELECT Community2Collection.community_id, Collection2Item.item_id -FROM Community2Collection, Collection2Item -WHERE Collection2Item.collection_id = Community2Collection.collection_id -; - -------------------------------------------------------- --- ItemsByAuthor table -------------------------------------------------------- -CREATE TABLE ItemsByAuthor -( - items_by_author_id INTEGER PRIMARY KEY, - item_id INTEGER REFERENCES Item(item_id), - author VARCHAR2(2000), - sort_author VARCHAR2(2000) -); - --- index by sort_author, of course! -CREATE INDEX sort_author_idx on ItemsByAuthor(sort_author); - -------------------------------------------------------- --- CollectionItemsByAuthor view -------------------------------------------------------- -CREATE VIEW CollectionItemsByAuthor as -SELECT Collection2Item.collection_id, ItemsByAuthor.* -FROM ItemsByAuthor, Collection2Item -WHERE ItemsByAuthor.item_id = Collection2Item.item_id -; - -------------------------------------------------------- --- CommunityItemsByAuthor view -------------------------------------------------------- -CREATE VIEW CommunityItemsByAuthor as -SELECT Communities2Item.community_id, ItemsByAuthor.* -FROM ItemsByAuthor, Communities2Item -WHERE ItemsByAuthor.item_id = Communities2Item.item_id -; - ----------------------------------------- --- ItemsByTitle table ----------------------------------------- -CREATE TABLE ItemsByTitle -( - items_by_title_id INTEGER PRIMARY KEY, - item_id INTEGER REFERENCES Item(item_id), - title VARCHAR2(2000), - sort_title VARCHAR2(2000) -); - --- index by the sort_title -CREATE INDEX sort_title_idx on ItemsByTitle(sort_title); - - -------------------------------------------------------- --- CollectionItemsByTitle view -------------------------------------------------------- -CREATE VIEW CollectionItemsByTitle as -SELECT Collection2Item.collection_id, ItemsByTitle.* -FROM ItemsByTitle, Collection2Item -WHERE ItemsByTitle.item_id = Collection2Item.item_id -; - -------------------------------------------------------- --- CommunityItemsByTitle view -------------------------------------------------------- -CREATE VIEW CommunityItemsByTitle as -SELECT Communities2Item.community_id, ItemsByTitle.* -FROM ItemsByTitle, Communities2Item -WHERE ItemsByTitle.item_id = Communities2Item.item_id -; - -------------------------------------------------------- --- ItemsByDate table -------------------------------------------------------- -CREATE TABLE ItemsByDate -( - items_by_date_id INTEGER PRIMARY KEY, - item_id INTEGER REFERENCES Item(item_id), - date_issued VARCHAR2(2000) -); - --- sort by date -CREATE INDEX date_issued_idx on ItemsByDate(date_issued); - -------------------------------------------------------- --- CollectionItemsByDate view -------------------------------------------------------- -CREATE VIEW CollectionItemsByDate as -SELECT Collection2Item.collection_id, ItemsByDate.* -FROM ItemsByDate, Collection2Item -WHERE ItemsByDate.item_id = Collection2Item.item_id -; - -------------------------------------------------------- --- CommunityItemsByDate view -------------------------------------------------------- -CREATE VIEW CommunityItemsByDate as -SELECT Communities2Item.community_id, ItemsByDate.* -FROM ItemsByDate, Communities2Item -WHERE ItemsByDate.item_id = Communities2Item.item_id -; - -------------------------------------------------------- --- ItemsByDateAccessioned table -------------------------------------------------------- -CREATE TABLE ItemsByDateAccessioned -( - items_by_date_accessioned_id INTEGER PRIMARY KEY, - item_id INTEGER REFERENCES Item(item_id), - date_accessioned VARCHAR2(2000) -); - -------------------------------------------------------- --- CollectionItemsByDateAccession view -------------------------------------------------------- -CREATE VIEW CollectionItemsByDateAccession as -SELECT Collection2Item.collection_id, ItemsByDateAccessioned.* -FROM ItemsByDateAccessioned, Collection2Item -WHERE ItemsByDateAccessioned.item_id = Collection2Item.item_id -; - -------------------------------------------------------- --- CommunityItemsByDateAccession view -------------------------------------------------------- -CREATE VIEW CommunityItemsByDateAccession as -SELECT Communities2Item.community_id, ItemsByDateAccessioned.* -FROM ItemsByDateAccessioned, Communities2Item -WHERE ItemsByDateAccessioned.item_id = Communities2Item.item_id -; diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V1.3__Upgrade_to_DSpace_1.3_schema.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V1.3__Upgrade_to_DSpace_1.3_schema.sql deleted file mode 100644 index 37d7e115eb..0000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V1.3__Upgrade_to_DSpace_1.3_schema.sql +++ /dev/null @@ -1,57 +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/ --- - --- =============================================================== --- WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING --- --- DO NOT MANUALLY RUN THIS DATABASE MIGRATION. IT WILL BE EXECUTED --- AUTOMATICALLY (IF NEEDED) BY "FLYWAY" WHEN YOU STARTUP DSPACE. --- http://flywaydb.org/ --- =============================================================== - -CREATE SEQUENCE epersongroup2workspaceitem_seq; - -------------------------------------------------------------------------------- --- create the new EPersonGroup2WorkspaceItem table -------------------------------------------------------------------------------- - -CREATE TABLE EPersonGroup2WorkspaceItem -( - id INTEGER PRIMARY KEY, - eperson_group_id INTEGER REFERENCES EPersonGroup(eperson_group_id), - workspace_item_id INTEGER REFERENCES WorkspaceItem(workspace_item_id) -); - -------------------------------------------------------------------------------- --- modification to collection table to support being able to change the --- submitter and collection admin group names -------------------------------------------------------------------------------- -ALTER TABLE collection ADD submitter INTEGER REFERENCES EPersonGroup(eperson_group_id); - -ALTER TABLE collection ADD admin INTEGER REFERENCES EPersonGroup(eperson_group_id); - -ALTER TABLE eperson ADD netid VARCHAR2(64) UNIQUE; - -------------------------------------------------------------------------------- --- Additional indices for performance -------------------------------------------------------------------------------- - --- index by resource id and resource type id -CREATE INDEX handle_resource_id_type_idx ON handle(resource_id, resource_type_id); - --- Indexing browse tables update/re-index performance -CREATE INDEX Communities2Item_item_id_idx ON Communities2Item( item_id ); -CREATE INDEX ItemsByAuthor_item_id_idx ON ItemsByAuthor(item_id); -CREATE INDEX ItemsByTitle_item_id_idx ON ItemsByTitle(item_id); -CREATE INDEX ItemsByDate_item_id_idx ON ItemsByDate(item_id); -CREATE INDEX ItemsByDateAcc_item_id_idx ON ItemsByDateAccessioned(item_id); - --- Improve mapping tables -CREATE INDEX Com2Coll_community_id_idx ON Community2Collection(community_id); -CREATE INDEX Com2Coll_collection_id_idx ON Community2Collection(collection_id); -CREATE INDEX Coll2Item_item_id_idx ON Collection2Item( item_id ); diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V1.4.2__Upgrade_to_DSpace_1.4.2_schema.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V1.4.2__Upgrade_to_DSpace_1.4.2_schema.sql deleted file mode 100644 index a713ced8bb..0000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V1.4.2__Upgrade_to_DSpace_1.4.2_schema.sql +++ /dev/null @@ -1,133 +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/ --- - --- =============================================================== --- WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING --- --- DO NOT MANUALLY RUN THIS DATABASE MIGRATION. IT WILL BE EXECUTED --- AUTOMATICALLY (IF NEEDED) BY "FLYWAY" WHEN YOU STARTUP DSPACE. --- http://flywaydb.org/ --- =============================================================== - ---------------------------------------- --- Update MetadataValue to include CLOB ---------------------------------------- - -CREATE TABLE MetadataValueTemp -( - metadata_value_id INTEGER PRIMARY KEY, - item_id INTEGER REFERENCES Item(item_id), - metadata_field_id INTEGER REFERENCES MetadataFieldRegistry(metadata_field_id), - text_value CLOB, - text_lang VARCHAR(64), - place INTEGER -); - -INSERT INTO MetadataValueTemp -SELECT * FROM MetadataValue; - -DROP VIEW dcvalue; -DROP TABLE MetadataValue; -ALTER TABLE MetadataValueTemp RENAME TO MetadataValue; - -CREATE VIEW dcvalue AS - SELECT MetadataValue.metadata_value_id AS "dc_value_id", MetadataValue.item_id, - MetadataValue.metadata_field_id AS "dc_type_id", MetadataValue.text_value, - MetadataValue.text_lang, MetadataValue.place - FROM MetadataValue, MetadataFieldRegistry - WHERE MetadataValue.metadata_field_id = MetadataFieldRegistry.metadata_field_id - AND MetadataFieldRegistry.metadata_schema_id = 1; - -CREATE INDEX metadatavalue_item_idx ON MetadataValue(item_id); -CREATE INDEX metadatavalue_item_idx2 ON MetadataValue(item_id,metadata_field_id); - ------------------------------------- --- Update Community to include CLOBs ------------------------------------- - -CREATE TABLE CommunityTemp -( - community_id INTEGER PRIMARY KEY, - name VARCHAR2(128), - short_description VARCHAR2(512), - introductory_text CLOB, - logo_bitstream_id INTEGER REFERENCES Bitstream(bitstream_id), - copyright_text CLOB, - side_bar_text VARCHAR2(2000) -); - -INSERT INTO CommunityTemp -SELECT * FROM Community; - -DROP TABLE Community CASCADE CONSTRAINTS; -ALTER TABLE CommunityTemp RENAME TO Community; - -ALTER TABLE Community2Community ADD CONSTRAINT fk_c2c_parent -FOREIGN KEY (parent_comm_id) -REFERENCES Community (community_id); - -ALTER TABLE Community2Community ADD CONSTRAINT fk_c2c_child -FOREIGN KEY (child_comm_id) -REFERENCES Community (community_id); - -ALTER TABLE Community2Collection ADD CONSTRAINT fk_c2c_community -FOREIGN KEY (community_id) -REFERENCES Community (community_id); - -ALTER TABLE Communities2Item ADD CONSTRAINT fk_c2i_community -FOREIGN KEY (community_id) -REFERENCES Community (community_id); - -------------------------------------- --- Update Collection to include CLOBs -------------------------------------- - -CREATE TABLE CollectionTemp -( - collection_id INTEGER PRIMARY KEY, - name VARCHAR2(128), - short_description VARCHAR2(512), - introductory_text CLOB, - logo_bitstream_id INTEGER REFERENCES Bitstream(bitstream_id), - template_item_id INTEGER REFERENCES Item(item_id), - provenance_description VARCHAR2(2000), - license CLOB, - copyright_text CLOB, - side_bar_text VARCHAR2(2000), - workflow_step_1 INTEGER REFERENCES EPersonGroup( eperson_group_id ), - workflow_step_2 INTEGER REFERENCES EPersonGroup( eperson_group_id ), - workflow_step_3 INTEGER REFERENCES EPersonGroup( eperson_group_id ), - submitter INTEGER REFERENCES EPersonGroup( eperson_group_id ), - admin INTEGER REFERENCES EPersonGroup( eperson_group_id ) -); - -INSERT INTO CollectionTemp -SELECT * FROM Collection; - -DROP TABLE Collection CASCADE CONSTRAINTS; -ALTER TABLE CollectionTemp RENAME TO Collection; - -ALTER TABLE Community2Collection ADD CONSTRAINT fk_c2c_collection -FOREIGN KEY (collection_id) -REFERENCES Collection (collection_id); - -ALTER TABLE Collection2Item ADD CONSTRAINT fk_c2i_collection -FOREIGN KEY (collection_id) -REFERENCES Collection (collection_id); - -ALTER TABLE WorkspaceItem ADD CONSTRAINT fk_wsi_collection -FOREIGN KEY (collection_id) -REFERENCES Collection (collection_id); - -ALTER TABLE WorkflowItem ADD CONSTRAINT fk_wfi_collection -FOREIGN KEY (collection_id) -REFERENCES Collection (collection_id); - -ALTER TABLE Subscription ADD CONSTRAINT fk_subs_collection -FOREIGN KEY (collection_id) -REFERENCES Collection (collection_id); diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V1.4__Upgrade_to_DSpace_1.4_schema.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V1.4__Upgrade_to_DSpace_1.4_schema.sql deleted file mode 100644 index 54cf10067b..0000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V1.4__Upgrade_to_DSpace_1.4_schema.sql +++ /dev/null @@ -1,371 +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/ --- - --- =============================================================== --- WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING --- --- DO NOT MANUALLY RUN THIS DATABASE MIGRATION. IT WILL BE EXECUTED --- AUTOMATICALLY (IF NEEDED) BY "FLYWAY" WHEN YOU STARTUP DSPACE. --- http://flywaydb.org/ --- =============================================================== - -------------------------------------------------------------------------------- --- Sequences for Group within Group feature -------------------------------------------------------------------------------- -CREATE SEQUENCE group2group_seq; -CREATE SEQUENCE group2groupcache_seq; - ------------------------------------------------------- --- Group2Group table, records group membership in other groups ------------------------------------------------------- -CREATE TABLE Group2Group -( - id INTEGER PRIMARY KEY, - parent_id INTEGER REFERENCES EPersonGroup(eperson_group_id), - child_id INTEGER REFERENCES EPersonGroup(eperson_group_id) -); - ------------------------------------------------------- --- Group2GroupCache table, is the 'unwound' hierarchy in --- Group2Group. It explicitly names every parent child --- relationship, even with nested groups. For example, --- If Group2Group lists B is a child of A and C is a child of B, --- this table will have entries for parent(A,B), and parent(B,C) --- AND parent(A,C) so that all of the child groups of A can be --- looked up in a single simple query ------------------------------------------------------- -CREATE TABLE Group2GroupCache -( - id INTEGER PRIMARY KEY, - parent_id INTEGER REFERENCES EPersonGroup(eperson_group_id), - child_id INTEGER REFERENCES EPersonGroup(eperson_group_id) -); - - -------------------------------------------------------- --- New Metadata Tables and Sequences -------------------------------------------------------- -CREATE SEQUENCE metadataschemaregistry_seq; -CREATE SEQUENCE metadatafieldregistry_seq; -CREATE SEQUENCE metadatavalue_seq; - --- MetadataSchemaRegistry table -CREATE TABLE MetadataSchemaRegistry -( - metadata_schema_id INTEGER PRIMARY KEY, - namespace VARCHAR(256) UNIQUE, - short_id VARCHAR(32) -); - --- MetadataFieldRegistry table -CREATE TABLE MetadataFieldRegistry -( - metadata_field_id INTEGER PRIMARY KEY, - metadata_schema_id INTEGER NOT NULL REFERENCES MetadataSchemaRegistry(metadata_schema_id), - element VARCHAR(64), - qualifier VARCHAR(64), - scope_note VARCHAR2(2000) -); - --- MetadataValue table -CREATE TABLE MetadataValue -( - metadata_value_id INTEGER PRIMARY KEY, - item_id INTEGER REFERENCES Item(item_id), - metadata_field_id INTEGER REFERENCES MetadataFieldRegistry(metadata_field_id), - text_value VARCHAR2(2000), - text_lang VARCHAR(24), - place INTEGER -); - --- Create the DC schema -INSERT INTO MetadataSchemaRegistry VALUES (1,'http://dublincore.org/documents/dcmi-terms/','dc'); - --- Migrate the existing DCTypes into the new metadata field registry -INSERT INTO MetadataFieldRegistry - (metadata_schema_id, metadata_field_id, element, qualifier, scope_note) - SELECT '1' AS metadata_schema_id, dc_type_id, element, - qualifier, scope_note FROM dctyperegistry; - --- Copy the DCValues into the new MetadataValue table -INSERT INTO MetadataValue (item_id, metadata_field_id, text_value, text_lang, place) - SELECT item_id, dc_type_id, text_value, text_lang, place FROM dcvalue; - -DROP TABLE dcvalue; -CREATE VIEW dcvalue AS - SELECT MetadataValue.metadata_value_id AS "dc_value_id", MetadataValue.item_id, - MetadataValue.metadata_field_id AS "dc_type_id", MetadataValue.text_value, - MetadataValue.text_lang, MetadataValue.place - FROM MetadataValue, MetadataFieldRegistry - WHERE MetadataValue.metadata_field_id = MetadataFieldRegistry.metadata_field_id - AND MetadataFieldRegistry.metadata_schema_id = 1; - - --- After copying data from dctypregistry to metadataschemaregistry, we need to reset our sequences --- Update metadatafieldregistry_seq to new max value -DECLARE - curr NUMBER := 0; -BEGIN - SELECT max(metadata_field_id) INTO curr FROM metadatafieldregistry; - - curr := curr + 1; - - EXECUTE IMMEDIATE 'DROP SEQUENCE metadatafieldregistry_seq'; - - EXECUTE IMMEDIATE 'CREATE SEQUENCE metadatafieldregistry_seq START WITH ' || NVL(curr,1); -END; -/ --- Update metadatavalue_seq to new max value -DECLARE - curr NUMBER := 0; -BEGIN - SELECT max(metadata_value_id) INTO curr FROM metadatavalue; - - curr := curr + 1; - - EXECUTE IMMEDIATE 'DROP SEQUENCE metadatavalue_seq'; - - EXECUTE IMMEDIATE 'CREATE SEQUENCE metadatavalue_seq START WITH ' || NVL(curr,1); -END; -/ --- Update metadataschemaregistry_seq to new max value -DECLARE - curr NUMBER := 0; -BEGIN - SELECT max(metadata_schema_id) INTO curr FROM metadataschemaregistry; - - curr := curr + 1; - - EXECUTE IMMEDIATE 'DROP SEQUENCE metadataschemaregistry_seq'; - - EXECUTE IMMEDIATE 'CREATE SEQUENCE metadataschemaregistry_seq START WITH ' || NVL(curr,1); -END; -/ - --- Drop the old dctyperegistry -DROP TABLE dctyperegistry; - --- create indexes for the metadata tables -CREATE INDEX metadatavalue_item_idx ON MetadataValue(item_id); -CREATE INDEX metadatavalue_item_idx2 ON MetadataValue(item_id,metadata_field_id); -CREATE INDEX metadatafield_schema_idx ON MetadataFieldRegistry(metadata_schema_id); - - -------------------------------------------------------- --- Create the checksum checker tables -------------------------------------------------------- --- list of the possible results as determined --- by the system or an administrator - -CREATE TABLE checksum_results -( - result_code VARCHAR(64) PRIMARY KEY, - result_description VARCHAR2(2000) -); - - --- This table has a one-to-one relationship --- with the bitstream table. A row will be inserted --- every time a row is inserted into the bitstream table, and --- that row will be updated every time the checksum is --- re-calculated. - -CREATE TABLE most_recent_checksum -( - bitstream_id INTEGER PRIMARY KEY, - to_be_processed NUMBER(1) NOT NULL, - expected_checksum VARCHAR(64) NOT NULL, - current_checksum VARCHAR(64) NOT NULL, - last_process_start_date TIMESTAMP NOT NULL, - last_process_end_date TIMESTAMP NOT NULL, - checksum_algorithm VARCHAR(64) NOT NULL, - matched_prev_checksum NUMBER(1) NOT NULL, - result VARCHAR(64) REFERENCES checksum_results(result_code) -); - - --- A row will be inserted into this table every --- time a checksum is re-calculated. - -CREATE SEQUENCE checksum_history_seq; - -CREATE TABLE checksum_history -( - check_id INTEGER PRIMARY KEY, - bitstream_id INTEGER, - process_start_date TIMESTAMP, - process_end_date TIMESTAMP, - checksum_expected VARCHAR(64), - checksum_calculated VARCHAR(64), - result VARCHAR(64) REFERENCES checksum_results(result_code) -); - --- this will insert into the result code --- the initial results - -insert into checksum_results -values -( - 'INVALID_HISTORY', - 'Install of the cheksum checking code do not consider this history as valid' -); - -insert into checksum_results -values -( - 'BITSTREAM_NOT_FOUND', - 'The bitstream could not be found' -); - -insert into checksum_results -values -( - 'CHECKSUM_MATCH', - 'Current checksum matched previous checksum' -); - -insert into checksum_results -values -( - 'CHECKSUM_NO_MATCH', - 'Current checksum does not match previous checksum' -); - -insert into checksum_results -values -( - 'CHECKSUM_PREV_NOT_FOUND', - 'Previous checksum was not found: no comparison possible' -); - -insert into checksum_results -values -( - 'BITSTREAM_INFO_NOT_FOUND', - 'Bitstream info not found' -); - -insert into checksum_results -values -( - 'CHECKSUM_ALGORITHM_INVALID', - 'Invalid checksum algorithm' -); -insert into checksum_results -values -( - 'BITSTREAM_NOT_PROCESSED', - 'Bitstream marked to_be_processed=false' -); -insert into checksum_results -values -( - 'BITSTREAM_MARKED_DELETED', - 'Bitstream marked deleted in bitstream table' -); - --- this will insert into the most recent checksum --- on install all existing bitstreams --- setting all bitstreams already set as --- deleted to not be processed - -insert into most_recent_checksum -( - bitstream_id, - to_be_processed, - expected_checksum, - current_checksum, - last_process_start_date, - last_process_end_date, - checksum_algorithm, - matched_prev_checksum -) -select - bitstream.bitstream_id, - '1', - CASE WHEN bitstream.checksum IS NULL THEN '' ELSE bitstream.checksum END, - CASE WHEN bitstream.checksum IS NULL THEN '' ELSE bitstream.checksum END, - TO_TIMESTAMP(TO_CHAR(current_timestamp, 'DD-MM-RRRR HH24:MI:SS'), 'DD-MM-RRRR HH24:MI:SS'), - TO_TIMESTAMP(TO_CHAR(current_timestamp, 'DD-MM-RRRR HH24:MI:SS'), 'DD-MM-RRRR HH24:MI:SS'), - CASE WHEN bitstream.checksum_algorithm IS NULL THEN 'MD5' ELSE bitstream.checksum_algorithm END, - '1' -from bitstream; - --- Update all the deleted checksums --- to not be checked --- because they have since been --- deleted from the system - -update most_recent_checksum -set to_be_processed = 0 -where most_recent_checksum.bitstream_id in ( -select bitstream_id -from bitstream where deleted = '1' ); - --- this will insert into history table --- for the initial start --- we want to tell the users to disregard the initial --- inserts into the checksum history table - -insert into checksum_history -( - bitstream_id, - process_start_date, - process_end_date, - checksum_expected, - checksum_calculated -) -select most_recent_checksum.bitstream_id, - most_recent_checksum.last_process_end_date, - TO_TIMESTAMP(TO_CHAR(current_timestamp, 'DD-MM-RRRR HH24:MI:SS'), 'DD-MM-RRRR HH24:MI:SS'), - most_recent_checksum.expected_checksum, - most_recent_checksum.expected_checksum -FROM most_recent_checksum; - --- update the history to indicate that this was --- the first time the software was installed -update checksum_history -set result = 'INVALID_HISTORY'; - - -------------------------------------------------------- --- Table and views for 'browse by subject' functionality -------------------------------------------------------- -CREATE SEQUENCE itemsbysubject_seq; - -------------------------------------------------------- --- ItemsBySubject table -------------------------------------------------------- -CREATE TABLE ItemsBySubject -( - items_by_subject_id INTEGER PRIMARY KEY, - item_id INTEGER REFERENCES Item(item_id), - subject VARCHAR2(2000), - sort_subject VARCHAR2(2000) -); - --- index by sort_subject -CREATE INDEX sort_subject_idx on ItemsBySubject(sort_subject); - -------------------------------------------------------- --- CollectionItemsBySubject view -------------------------------------------------------- -CREATE VIEW CollectionItemsBySubject as -SELECT Collection2Item.collection_id, ItemsBySubject.* -FROM ItemsBySubject, Collection2Item -WHERE ItemsBySubject.item_id = Collection2Item.item_id -; - -------------------------------------------------------- --- CommunityItemsBySubject view -------------------------------------------------------- -CREATE VIEW CommunityItemsBySubject as -SELECT Communities2Item.community_id, ItemsBySubject.* -FROM ItemsBySubject, Communities2Item -WHERE ItemsBySubject.item_id = Communities2Item.item_id -; diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V1.5__Upgrade_to_DSpace_1.5_schema.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V1.5__Upgrade_to_DSpace_1.5_schema.sql deleted file mode 100644 index bb217bd0d1..0000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V1.5__Upgrade_to_DSpace_1.5_schema.sql +++ /dev/null @@ -1,142 +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/ --- - --- =============================================================== --- WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING --- --- DO NOT MANUALLY RUN THIS DATABASE MIGRATION. IT WILL BE EXECUTED --- AUTOMATICALLY (IF NEEDED) BY "FLYWAY" WHEN YOU STARTUP DSPACE. --- http://flywaydb.org/ --- =============================================================== - --- Remove NOT NULL restrictions from the checksum columns of most_recent_checksum -ALTER TABLE most_recent_checksum MODIFY expected_checksum null; -ALTER TABLE most_recent_checksum MODIFY current_checksum null; - ------------------------------------------------------- --- New Column language language in EPerson ------------------------------------------------------- - -alter table eperson ADD language VARCHAR2(64); -update eperson set language = 'en'; - --- totally unused column -alter table bundle drop column mets_bitstream_id; - -------------------------------------------------------------------------------- --- Necessary for Configurable Submission functionality: --- Modification to workspaceitem table to support keeping track --- of the last page reached within a step in the Configurable Submission Process -------------------------------------------------------------------------------- -ALTER TABLE workspaceitem ADD page_reached INTEGER; - - -------------------------------------------------------------------------- --- Increase the mimetype field size to support larger types, such as the --- new Word 2007 mimetypes. -------------------------------------------------------------------------- -ALTER TABLE BitstreamFormatRegistry MODIFY (mimetype VARCHAR(256)); - - -------------------------------------------------------------------------- --- Tables to manage cache of item counts for communities and collections -------------------------------------------------------------------------- - -CREATE TABLE collection_item_count ( - collection_id INTEGER PRIMARY KEY REFERENCES collection(collection_id), - count INTEGER -); - -CREATE TABLE community_item_count ( - community_id INTEGER PRIMARY KEY REFERENCES community(community_id), - count INTEGER -); - ------------------------------------------------------------------- --- Remove sequences and tables of the old browse system ------------------------------------------------------------------- - -DROP SEQUENCE itemsbyauthor_seq; -DROP SEQUENCE itemsbytitle_seq; -DROP SEQUENCE itemsbydate_seq; -DROP SEQUENCE itemsbydateaccessioned_seq; -DROP SEQUENCE itemsbysubject_seq; - -DROP TABLE ItemsByAuthor CASCADE CONSTRAINTS; -DROP TABLE ItemsByTitle CASCADE CONSTRAINTS; -DROP TABLE ItemsByDate CASCADE CONSTRAINTS; -DROP TABLE ItemsByDateAccessioned CASCADE CONSTRAINTS; -DROP TABLE ItemsBySubject CASCADE CONSTRAINTS; - -DROP TABLE History CASCADE CONSTRAINTS; -DROP TABLE HistoryState CASCADE CONSTRAINTS; - ----------------------------------------------------------------- --- Add indexes for foreign key columns ----------------------------------------------------------------- - -CREATE INDEX fe_bitstream_fk_idx ON FileExtension(bitstream_format_id); - -CREATE INDEX bit_bitstream_fk_idx ON Bitstream(bitstream_format_id); - -CREATE INDEX g2g_parent_fk_idx ON Group2Group(parent_id); -CREATE INDEX g2g_child_fk_idx ON Group2Group(child_id); - --- CREATE INDEX g2gc_parent_fk_idx ON Group2Group(parent_id); --- CREATE INDEX g2gc_child_fk_idx ON Group2Group(child_id); - -CREATE INDEX item_submitter_fk_idx ON Item(submitter_id); - -CREATE INDEX bundle_primary_fk_idx ON Bundle(primary_bitstream_id); - -CREATE INDEX item2bundle_bundle_fk_idx ON Item2Bundle(bundle_id); - -CREATE INDEX bundle2bits_bitstream_fk_idx ON Bundle2Bitstream(bitstream_id); - -CREATE INDEX metadatavalue_field_fk_idx ON MetadataValue(metadata_field_id); - -CREATE INDEX community_logo_fk_idx ON Community(logo_bitstream_id); - -CREATE INDEX collection_logo_fk_idx ON Collection(logo_bitstream_id); -CREATE INDEX collection_template_fk_idx ON Collection(template_item_id); -CREATE INDEX collection_workflow1_fk_idx ON Collection(workflow_step_1); -CREATE INDEX collection_workflow2_fk_idx ON Collection(workflow_step_2); -CREATE INDEX collection_workflow3_fk_idx ON Collection(workflow_step_3); -CREATE INDEX collection_submitter_fk_idx ON Collection(submitter); -CREATE INDEX collection_admin_fk_idx ON Collection(admin); - -CREATE INDEX com2com_parent_fk_idx ON Community2Community(parent_comm_id); -CREATE INDEX com2com_child_fk_idx ON Community2Community(child_comm_id); - -CREATE INDEX rp_eperson_fk_idx ON ResourcePolicy(eperson_id); -CREATE INDEX rp_epersongroup_fk_idx ON ResourcePolicy(epersongroup_id); - -CREATE INDEX epg2ep_eperson_fk_idx ON EPersonGroup2EPerson(eperson_id); - -CREATE INDEX workspace_item_fk_idx ON WorkspaceItem(item_id); -CREATE INDEX workspace_coll_fk_idx ON WorkspaceItem(collection_id); - --- CREATE INDEX workflow_item_fk_idx ON WorkflowItem(item_id); -CREATE INDEX workflow_coll_fk_idx ON WorkflowItem(collection_id); -CREATE INDEX workflow_owner_fk_idx ON WorkflowItem(owner); - -CREATE INDEX tasklist_eperson_fk_idx ON TasklistItem(eperson_id); -CREATE INDEX tasklist_workflow_fk_idx ON TasklistItem(workflow_id); - -CREATE INDEX subs_eperson_fk_idx ON Subscription(eperson_id); -CREATE INDEX subs_collection_fk_idx ON Subscription(collection_id); - -CREATE INDEX epg2wi_group_fk_idx ON epersongroup2workspaceitem(eperson_group_id); -CREATE INDEX epg2wi_workspace_fk_idx ON epersongroup2workspaceitem(workspace_item_id); - -CREATE INDEX Comm2Item_community_fk_idx ON Communities2Item( community_id ); - -CREATE INDEX mrc_result_fk_idx ON most_recent_checksum( result ); - -CREATE INDEX ch_result_fk_idx ON checksum_history( result ); - diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V1.6__Upgrade_to_DSpace_1.6_schema.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V1.6__Upgrade_to_DSpace_1.6_schema.sql deleted file mode 100644 index 659ca32983..0000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V1.6__Upgrade_to_DSpace_1.6_schema.sql +++ /dev/null @@ -1,93 +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/ --- - --- =============================================================== --- WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING --- --- DO NOT MANUALLY RUN THIS DATABASE MIGRATION. IT WILL BE EXECUTED --- AUTOMATICALLY (IF NEEDED) BY "FLYWAY" WHEN YOU STARTUP DSPACE. --- http://flywaydb.org/ --- =============================================================== - ------------------------------------------------------------------- --- New Column for Community Admin - Delegated Admin patch (DS-228) ------------------------------------------------------------------- -ALTER TABLE community ADD admin INTEGER REFERENCES epersongroup ( eperson_group_id ); -CREATE INDEX community_admin_fk_idx ON Community(admin); - -------------------------------------------------------------------------- --- DS-236 schema changes for Authority Control of Metadata Values -------------------------------------------------------------------------- -ALTER TABLE MetadataValue - ADD ( authority VARCHAR(100), - confidence INTEGER DEFAULT -1); - --------------------------------------------------------------------------- --- DS-295 CC License being assigned incorrect Mime Type during submission. --------------------------------------------------------------------------- -UPDATE bitstream SET bitstream_format_id = - (SELECT bitstream_format_id FROM bitstreamformatregistry WHERE short_description = 'CC License') - WHERE name = 'license_text' AND source = 'org.dspace.license.CreativeCommons'; - -UPDATE bitstream SET bitstream_format_id = - (SELECT bitstream_format_id FROM bitstreamformatregistry WHERE short_description = 'RDF XML') - WHERE name = 'license_rdf' AND source = 'org.dspace.license.CreativeCommons'; - -------------------------------------------------------------------------- --- DS-260 Cleanup of Owning collection column for template item created --- with the JSPUI after the collection creation -------------------------------------------------------------------------- -UPDATE item SET owning_collection = null WHERE item_id IN - (SELECT template_item_id FROM collection WHERE template_item_id IS NOT null); - --- Recreate restraints with a know name and deferrable option! --- (The previous version of these constraints is dropped by org.dspace.storage.rdbms.migration.V1_5_9__Drop_constraint_for_DSpace_1_6_schema) -ALTER TABLE community2collection ADD CONSTRAINT comm2coll_collection_fk FOREIGN KEY (collection_id) REFERENCES collection DEFERRABLE; -ALTER TABLE community2community ADD CONSTRAINT com2com_child_fk FOREIGN KEY (child_comm_id) REFERENCES community DEFERRABLE; -ALTER TABLE collection2item ADD CONSTRAINT coll2item_item_fk FOREIGN KEY (item_id) REFERENCES item DEFERRABLE; - - ------------------------------------------------------------------- --- New tables /sequences for the harvester functionality (DS-289) ------------------------------------------------------------------- -CREATE SEQUENCE harvested_collection_seq; -CREATE SEQUENCE harvested_item_seq; - -------------------------------------------------------- --- Create the harvest settings table -------------------------------------------------------- --- Values used by the OAIHarvester to harvest a collection --- HarvestInstance is the DAO class for this table - -CREATE TABLE harvested_collection -( - collection_id INTEGER REFERENCES collection(collection_id) ON DELETE CASCADE, - harvest_type INTEGER, - oai_source VARCHAR(256), - oai_set_id VARCHAR(256), - harvest_message VARCHAR2(512), - metadata_config_id VARCHAR(256), - harvest_status INTEGER, - harvest_start_time TIMESTAMP, - last_harvested TIMESTAMP, - id INTEGER PRIMARY KEY -); - -CREATE INDEX harvested_collection_fk_idx ON harvested_collection(collection_id); - - -CREATE TABLE harvested_item -( - item_id INTEGER REFERENCES item(item_id) ON DELETE CASCADE, - last_harvested TIMESTAMP, - oai_id VARCHAR(64), - id INTEGER PRIMARY KEY -); - -CREATE INDEX harvested_item_fk_idx ON harvested_item(item_id); - diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V1.7__Upgrade_to_DSpace_1.7_schema.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V1.7__Upgrade_to_DSpace_1.7_schema.sql deleted file mode 100644 index f4b2737fb3..0000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V1.7__Upgrade_to_DSpace_1.7_schema.sql +++ /dev/null @@ -1,20 +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/ --- - --- =============================================================== --- WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING --- --- DO NOT MANUALLY RUN THIS DATABASE MIGRATION. IT WILL BE EXECUTED --- AUTOMATICALLY (IF NEEDED) BY "FLYWAY" WHEN YOU STARTUP DSPACE. --- http://flywaydb.org/ --- =============================================================== - ------------------------------------------------------------------- --- Remove unused / obsolete sequence 'dctyperegistry_seq' (DS-729) ------------------------------------------------------------------- -DROP SEQUENCE dctyperegistry_seq; diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V1.8__Upgrade_to_DSpace_1.8_schema.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V1.8__Upgrade_to_DSpace_1.8_schema.sql deleted file mode 100644 index f96cddbe7f..0000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V1.8__Upgrade_to_DSpace_1.8_schema.sql +++ /dev/null @@ -1,23 +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/ --- - --- =============================================================== --- WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING --- --- DO NOT MANUALLY RUN THIS DATABASE MIGRATION. IT WILL BE EXECUTED --- AUTOMATICALLY (IF NEEDED) BY "FLYWAY" WHEN YOU STARTUP DSPACE. --- http://flywaydb.org/ --- =============================================================== - -------------------------------------------- --- New column for bitstream order DS-749 -- -------------------------------------------- -ALTER TABLE bundle2bitstream ADD bitstream_order INTEGER; - ---Place the sequence id's in the order -UPDATE bundle2bitstream SET bitstream_order=(SELECT sequence_id FROM bitstream WHERE bitstream.bitstream_id=bundle2bitstream.bitstream_id); diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V3.0__Upgrade_to_DSpace_3.x_schema.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V3.0__Upgrade_to_DSpace_3.x_schema.sql deleted file mode 100644 index 472dc7dc52..0000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V3.0__Upgrade_to_DSpace_3.x_schema.sql +++ /dev/null @@ -1,52 +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/ --- - --- =============================================================== --- WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING --- --- DO NOT MANUALLY RUN THIS DATABASE MIGRATION. IT WILL BE EXECUTED --- AUTOMATICALLY (IF NEEDED) BY "FLYWAY" WHEN YOU STARTUP DSPACE. --- http://flywaydb.org/ --- =============================================================== - -ALTER TABLE resourcepolicy - ADD ( - rpname VARCHAR2(30), - rptype VARCHAR2(30), - rpdescription VARCHAR2(100) - ); - - -ALTER TABLE item ADD discoverable NUMBER(1); - -CREATE TABLE versionhistory -( - versionhistory_id INTEGER NOT NULL PRIMARY KEY -); - -CREATE TABLE versionitem -( - versionitem_id INTEGER NOT NULL PRIMARY KEY, - item_id INTEGER REFERENCES Item(item_id), - version_number INTEGER, - eperson_id INTEGER REFERENCES EPerson(eperson_id), - version_date TIMESTAMP, - version_summary VARCHAR2(255), - versionhistory_id INTEGER REFERENCES VersionHistory(versionhistory_id) -); - -CREATE SEQUENCE versionitem_seq; -CREATE SEQUENCE versionhistory_seq; - - -------------------------------------------- --- New columns and longer hash for salted password hashing DS-861 -- -------------------------------------------- -ALTER TABLE EPerson modify( password VARCHAR(128)); -ALTER TABLE EPerson ADD salt VARCHAR(32); -ALTER TABLE EPerson ADD digest_algorithm VARCHAR(16); diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V4.0__Upgrade_to_DSpace_4.x_schema.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V4.0__Upgrade_to_DSpace_4.x_schema.sql deleted file mode 100644 index 8102376906..0000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V4.0__Upgrade_to_DSpace_4.x_schema.sql +++ /dev/null @@ -1,88 +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/ --- - --- =============================================================== --- WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING --- --- DO NOT MANUALLY RUN THIS DATABASE MIGRATION. IT WILL BE EXECUTED --- AUTOMATICALLY (IF NEEDED) BY "FLYWAY" WHEN YOU STARTUP DSPACE. --- http://flywaydb.org/ --- =============================================================== - -------------------------------------------- --- Ensure that discoverable has a sensible default -------------------------------------------- -update item set discoverable=1 WHERE discoverable IS NULL; - -------------------------------------------- --- Add support for DOIs (table and seq.) -- -------------------------------------------- - -CREATE TABLE Doi -( - doi_id INTEGER PRIMARY KEY, - doi VARCHAR2(256) UNIQUE, - resource_type_id INTEGER, - resource_id INTEGER, - status INTEGER -); - -CREATE SEQUENCE doi_seq; - --- index by resource id and resource type id -CREATE INDEX doi_resource_id_type_idx ON doi(resource_id, resource_type_id); - -------------------------------------------- --- Table of running web applications for 'dspace version' -- -------------------------------------------- - -CREATE TABLE Webapp -( - webapp_id INTEGER NOT NULL PRIMARY KEY, - AppName VARCHAR2(32), - URL VARCHAR2(1000), - Started TIMESTAMP, - isUI NUMBER(1) -); - -CREATE SEQUENCE webapp_seq; - -------------------------------------------------------- --- DS-824 RequestItem table -------------------------------------------------------- - -CREATE TABLE requestitem -( - requestitem_id INTEGER NOT NULL, - token varchar(48), - item_id INTEGER, - bitstream_id INTEGER, - allfiles NUMBER(1), - request_email VARCHAR2(64), - request_name VARCHAR2(64), - request_date TIMESTAMP, - accept_request NUMBER(1), - decision_date TIMESTAMP, - expires TIMESTAMP, - CONSTRAINT requestitem_pkey PRIMARY KEY (requestitem_id), - CONSTRAINT requestitem_token_key UNIQUE (token) -); - -CREATE SEQUENCE requestitem_seq; - -------------------------------------------------------- --- DS-1655 Disable "Initial Questions" page in Submission UI by default -------------------------------------------------------- -update workspaceitem set multiple_titles=1, published_before=1, multiple_files=1; -update workflowitem set multiple_titles=1, published_before=1, multiple_files=1; - -------------------------------------------------------- --- DS-1811 Removing a collection fails if non-Solr DAO has been used before for item count -------------------------------------------------------- -delete from collection_item_count; -delete from community_item_count; diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V4.9_2015.10.26__DS-2818_registry_update.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V4.9_2015.10.26__DS-2818_registry_update.sql deleted file mode 100644 index 6d75905ec9..0000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V4.9_2015.10.26__DS-2818_registry_update.sql +++ /dev/null @@ -1,64 +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/ --- - --- =============================================================== --- WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING --- --- DO NOT MANUALLY RUN THIS DATABASE MIGRATION. IT WILL BE EXECUTED --- AUTOMATICALLY (IF NEEDED) BY "FLYWAY" WHEN YOU STARTUP DSPACE. --- http://flywaydb.org/ --- =============================================================== - --- Special case of migration, we need to the EPerson schema in order to get our metadata for all queries to work --- but we cannot a DB connection until our database is up to date, so we need to create our registries manually in sql - -INSERT INTO metadataschemaregistry (metadata_schema_id, namespace, short_id) SELECT metadataschemaregistry_seq.nextval, 'http://dspace.org/eperson' as namespace, 'eperson' as short_id FROM dual - WHERE NOT EXISTS (SELECT metadata_schema_id,namespace,short_id FROM metadataschemaregistry WHERE namespace = 'http://dspace.org/eperson' AND short_id = 'eperson'); - - --- Insert eperson.firstname -INSERT INTO metadatafieldregistry (metadata_field_id, metadata_schema_id, element) - SELECT metadatafieldregistry_seq.nextval, - (SELECT metadata_schema_id FROM metadataschemaregistry WHERE short_id='eperson'), 'firstname' FROM dual - WHERE NOT EXISTS - (SELECT metadata_field_id,element FROM metadatafieldregistry WHERE element = 'firstname' AND qualifier IS NULL AND metadata_schema_id = (SELECT metadata_schema_id FROM metadataschemaregistry WHERE short_id='eperson')); - --- Insert eperson.lastname -INSERT INTO metadatafieldregistry (metadata_field_id, metadata_schema_id, element) - SELECT metadatafieldregistry_seq.nextval, - (SELECT metadata_schema_id FROM metadataschemaregistry WHERE short_id='eperson'), 'lastname' FROM dual - WHERE NOT EXISTS - (SELECT metadata_field_id,element FROM metadatafieldregistry WHERE element = 'lastname' AND qualifier IS NULL AND metadata_schema_id = (SELECT metadata_schema_id FROM metadataschemaregistry WHERE short_id='eperson')); - --- Insert eperson.phone -INSERT INTO metadatafieldregistry (metadata_field_id, metadata_schema_id, element) - SELECT metadatafieldregistry_seq.nextval, - (SELECT metadata_schema_id FROM metadataschemaregistry WHERE short_id='eperson'), 'phone' FROM dual - WHERE NOT EXISTS - (SELECT metadata_field_id,element FROM metadatafieldregistry WHERE element = 'phone' AND qualifier IS NULL AND metadata_schema_id = (SELECT metadata_schema_id FROM metadataschemaregistry WHERE short_id='eperson')); - --- Insert eperson.language -INSERT INTO metadatafieldregistry (metadata_field_id, metadata_schema_id, element) - SELECT metadatafieldregistry_seq.nextval, - (SELECT metadata_schema_id FROM metadataschemaregistry WHERE short_id='eperson'), 'language' FROM dual - WHERE NOT EXISTS - (SELECT metadata_field_id,element FROM metadatafieldregistry WHERE element = 'language' AND qualifier IS NULL AND metadata_schema_id = (SELECT metadata_schema_id FROM metadataschemaregistry WHERE short_id='eperson')); - --- Insert into dc.provenance -INSERT INTO metadatafieldregistry (metadata_field_id, metadata_schema_id, element) - SELECT metadatafieldregistry_seq.nextval, - (SELECT metadata_schema_id FROM metadataschemaregistry WHERE short_id='dc'), 'provenance' FROM dual - WHERE NOT EXISTS - (SELECT metadata_field_id,element FROM metadatafieldregistry WHERE element = 'provenance' AND qualifier IS NULL AND metadata_schema_id = (SELECT metadata_schema_id FROM metadataschemaregistry WHERE short_id='dc')); - --- Insert into dc.rights.license -INSERT INTO metadatafieldregistry (metadata_field_id, metadata_schema_id, element, qualifier) - SELECT metadatafieldregistry_seq.nextval, - (SELECT metadata_schema_id FROM metadataschemaregistry WHERE short_id='dc'), 'rights', 'license' FROM dual - WHERE NOT EXISTS - (SELECT metadata_field_id,element,qualifier FROM metadatafieldregistry WHERE element = 'rights' AND qualifier='license' AND metadata_schema_id = (SELECT metadata_schema_id FROM metadataschemaregistry WHERE short_id='dc')); \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V5.0_2014.08.08__DS-1945_Helpdesk_Request_a_Copy.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V5.0_2014.08.08__DS-1945_Helpdesk_Request_a_Copy.sql deleted file mode 100644 index c86cfe3122..0000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V5.0_2014.08.08__DS-1945_Helpdesk_Request_a_Copy.sql +++ /dev/null @@ -1,20 +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/ --- - --- =============================================================== --- WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING --- --- DO NOT MANUALLY RUN THIS DATABASE MIGRATION. IT WILL BE EXECUTED --- AUTOMATICALLY (IF NEEDED) BY "FLYWAY" WHEN YOU STARTUP DSPACE. --- http://flywaydb.org/ --- =============================================================== - ------------------------------------------------------- --- DS-1945 RequestItem Helpdesk, store request message ------------------------------------------------------- -ALTER TABLE requestitem ADD request_message VARCHAR2(2000); diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V5.0_2014.09.26__DS-1582_Metadata_For_All_Objects.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V5.0_2014.09.26__DS-1582_Metadata_For_All_Objects.sql deleted file mode 100644 index 8f0cd0d5e1..0000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V5.0_2014.09.26__DS-1582_Metadata_For_All_Objects.sql +++ /dev/null @@ -1,333 +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/ --- - --- =============================================================== --- WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING --- --- DO NOT MANUALLY RUN THIS DATABASE MIGRATION. IT WILL BE EXECUTED --- AUTOMATICALLY (IF NEEDED) BY "FLYWAY" WHEN YOU STARTUP DSPACE. --- http://flywaydb.org/ --- =============================================================== - ------------------------------------------------------- --- DS-1582 Metadata on all DSpace Objects --- NOTE: This script also has a complimentary Flyway Java Migration --- which drops the "item_id" constraint on metadatavalue --- org.dspace.storage.rdbms.migration.V5_0_2014_09_25__DS_1582_Metadata_For_All_Objects_drop_constraint ------------------------------------------------------- -alter table metadatavalue rename column item_id to resource_id; - -alter table metadatavalue MODIFY(resource_id not null); -alter table metadatavalue add resource_type_id integer; -UPDATE metadatavalue SET resource_type_id = 2; -alter table metadatavalue MODIFY(resource_type_id not null); - - - --- --------- --- community --- --------- - -INSERT INTO metadatavalue (metadata_value_id, resource_id, resource_type_id, metadata_field_id, text_value, text_lang, place) -SELECT -metadatavalue_seq.nextval as metadata_value_id, -community_id AS resource_id, -4 AS resource_type_id, -(select metadata_field_id from metadatafieldregistry where metadata_schema_id=(select metadata_schema_id from metadataschemaregistry where short_id='dc') and element = 'description' and qualifier is null) AS metadata_field_id, -introductory_text AS text_value, -null AS text_lang, -0 AS place -FROM community where not introductory_text is null; - -INSERT INTO metadatavalue (metadata_value_id, resource_id, resource_type_id, metadata_field_id, text_value, text_lang, place) -SELECT -metadatavalue_seq.nextval as metadata_value_id, -community_id AS resource_id, -4 AS resource_type_id, -(select metadata_field_id from metadatafieldregistry where metadata_schema_id=(select metadata_schema_id from metadataschemaregistry where short_id='dc') and element = 'description' and qualifier = 'abstract') AS metadata_field_id, -short_description AS text_value, -null AS text_lang, -0 AS place -FROM community where not short_description is null; - -INSERT INTO metadatavalue (metadata_value_id, resource_id, resource_type_id, metadata_field_id, text_value, text_lang, place) -SELECT -metadatavalue_seq.nextval as metadata_value_id, -community_id AS resource_id, -4 AS resource_type_id, -(select metadata_field_id from metadatafieldregistry where metadata_schema_id=(select metadata_schema_id from metadataschemaregistry where short_id='dc') and element = 'description' and qualifier = 'tableofcontents') AS metadata_field_id, -side_bar_text AS text_value, -null AS text_lang, -0 AS place -FROM community where not side_bar_text is null; - -INSERT INTO metadatavalue (metadata_value_id, resource_id, resource_type_id, metadata_field_id, text_value, text_lang, place) -SELECT -metadatavalue_seq.nextval as metadata_value_id, -community_id AS resource_id, -4 AS resource_type_id, -(select metadata_field_id from metadatafieldregistry where metadata_schema_id=(select metadata_schema_id from metadataschemaregistry where short_id='dc') and element = 'rights' and qualifier is null) AS metadata_field_id, -copyright_text AS text_value, -null AS text_lang, -0 AS place -FROM community where not copyright_text is null; - -INSERT INTO metadatavalue (metadata_value_id, resource_id, resource_type_id, metadata_field_id, text_value, text_lang, place) -SELECT -metadatavalue_seq.nextval as metadata_value_id, -community_id AS resource_id, -4 AS resource_type_id, -(select metadata_field_id from metadatafieldregistry where metadata_schema_id=(select metadata_schema_id from metadataschemaregistry where short_id='dc') and element = 'title' and qualifier is null) AS metadata_field_id, -name AS text_value, -null AS text_lang, -0 AS place -FROM community where not name is null; - -alter table community drop (introductory_text, short_description, side_bar_text, copyright_text, name); - - --- ---------- --- collection --- ---------- - - - -INSERT INTO metadatavalue (metadata_value_id, resource_id, resource_type_id, metadata_field_id, text_value, text_lang, place) -SELECT -metadatavalue_seq.nextval as metadata_value_id, -collection_id AS resource_id, -3 AS resource_type_id, -(select metadata_field_id from metadatafieldregistry where metadata_schema_id=(select metadata_schema_id from metadataschemaregistry where short_id='dc') and element = 'description' and qualifier is null) AS metadata_field_id, -introductory_text AS text_value, -null AS text_lang, -0 AS place -FROM collection where not introductory_text is null; - -INSERT INTO metadatavalue (metadata_value_id, resource_id, resource_type_id, metadata_field_id, text_value, text_lang, place) -SELECT -metadatavalue_seq.nextval as metadata_value_id, -collection_id AS resource_id, -3 AS resource_type_id, -(select metadata_field_id from metadatafieldregistry where metadata_schema_id=(select metadata_schema_id from metadataschemaregistry where short_id='dc') and element = 'description' and qualifier = 'abstract') AS metadata_field_id, -short_description AS text_value, -null AS text_lang, -0 AS place -FROM collection where not short_description is null; - -INSERT INTO metadatavalue (metadata_value_id, resource_id, resource_type_id, metadata_field_id, text_value, text_lang, place) -SELECT -metadatavalue_seq.nextval as metadata_value_id, -collection_id AS resource_id, -3 AS resource_type_id, -(select metadata_field_id from metadatafieldregistry where metadata_schema_id=(select metadata_schema_id from metadataschemaregistry where short_id='dc') and element = 'description' and qualifier = 'tableofcontents') AS metadata_field_id, -side_bar_text AS text_value, -null AS text_lang, -0 AS place -FROM collection where not side_bar_text is null; - -INSERT INTO metadatavalue (metadata_value_id, resource_id, resource_type_id, metadata_field_id, text_value, text_lang, place) -SELECT -metadatavalue_seq.nextval as metadata_value_id, -collection_id AS resource_id, -3 AS resource_type_id, -(select metadata_field_id from metadatafieldregistry where metadata_schema_id=(select metadata_schema_id from metadataschemaregistry where short_id='dc') and element = 'rights' and qualifier is null) AS metadata_field_id, -copyright_text AS text_value, -null AS text_lang, -0 AS place -FROM collection where not copyright_text is null; - -INSERT INTO metadatavalue (metadata_value_id, resource_id, resource_type_id, metadata_field_id, text_value, text_lang, place) -SELECT -metadatavalue_seq.nextval as metadata_value_id, -collection_id AS resource_id, -3 AS resource_type_id, -(select metadata_field_id from metadatafieldregistry where metadata_schema_id=(select metadata_schema_id from metadataschemaregistry where short_id='dc') and element = 'title' and qualifier is null) AS metadata_field_id, -name AS text_value, -null AS text_lang, -0 AS place -FROM collection where not name is null; - -INSERT INTO metadatavalue (metadata_value_id, resource_id, resource_type_id, metadata_field_id, text_value, text_lang, place) -SELECT -metadatavalue_seq.nextval as metadata_value_id, -collection_id AS resource_id, -3 AS resource_type_id, -(select metadata_field_id from metadatafieldregistry where metadata_schema_id=(select metadata_schema_id from metadataschemaregistry where short_id='dc') and element = 'provenance' and qualifier is null) AS metadata_field_id, -provenance_description AS text_value, -null AS text_lang, -0 AS place -FROM collection where not provenance_description is null; - -INSERT INTO metadatavalue (metadata_value_id, resource_id, resource_type_id, metadata_field_id, text_value, text_lang, place) -SELECT -metadatavalue_seq.nextval as metadata_value_id, -collection_id AS resource_id, -3 AS resource_type_id, -(select metadata_field_id from metadatafieldregistry where metadata_schema_id=(select metadata_schema_id from metadataschemaregistry where short_id='dc') and element = 'rights' and qualifier = 'license') AS metadata_field_id, -license AS text_value, -null AS text_lang, -0 AS place -FROM collection where not license is null; - -alter table collection drop (introductory_text, short_description, copyright_text, side_bar_text, name, license, provenance_description); - - --- --------- --- bundle --- --------- - -INSERT INTO metadatavalue (metadata_value_id, resource_id, resource_type_id, metadata_field_id, text_value, text_lang, place) -SELECT -metadatavalue_seq.nextval as metadata_value_id, -bundle_id AS resource_id, -1 AS resource_type_id, -(select metadata_field_id from metadatafieldregistry where metadata_schema_id=(select metadata_schema_id from metadataschemaregistry where short_id='dc') and element = 'title' and qualifier is null) AS metadata_field_id, -name AS text_value, -null AS text_lang, -0 AS place -FROM bundle where not name is null; - -alter table bundle drop column name; - - - --- --------- --- bitstream --- --------- - - -INSERT INTO metadatavalue (metadata_value_id, resource_id, resource_type_id, metadata_field_id, text_value, text_lang, place) -SELECT -metadatavalue_seq.nextval as metadata_value_id, -bitstream_id AS resource_id, -0 AS resource_type_id, -(select metadata_field_id from metadatafieldregistry where metadata_schema_id=(select metadata_schema_id from metadataschemaregistry where short_id='dc') and element = 'title' and qualifier is null) AS metadata_field_id, -name AS text_value, -null AS text_lang, -0 AS place -FROM bitstream where not name is null; - -INSERT INTO metadatavalue (metadata_value_id, resource_id, resource_type_id, metadata_field_id, text_value, text_lang, place) -SELECT -metadatavalue_seq.nextval as metadata_value_id, -bitstream_id AS resource_id, -0 AS resource_type_id, -(select metadata_field_id from metadatafieldregistry where metadata_schema_id=(select metadata_schema_id from metadataschemaregistry where short_id='dc') and element = 'description' and qualifier is null) AS metadata_field_id, -description AS text_value, -null AS text_lang, -0 AS place -FROM bitstream where not description is null; - -INSERT INTO metadatavalue (metadata_value_id, resource_id, resource_type_id, metadata_field_id, text_value, text_lang, place) -SELECT -metadatavalue_seq.nextval as metadata_value_id, -bitstream_id AS resource_id, -0 AS resource_type_id, -(select metadata_field_id from metadatafieldregistry where metadata_schema_id=(select metadata_schema_id from metadataschemaregistry where short_id='dc') and element = 'format' and qualifier is null) AS metadata_field_id, -user_format_description AS text_value, -null AS text_lang, -0 AS place -FROM bitstream where not user_format_description is null; - -INSERT INTO metadatavalue (metadata_value_id, resource_id, resource_type_id, metadata_field_id, text_value, text_lang, place) -SELECT -metadatavalue_seq.nextval as metadata_value_id, -bitstream_id AS resource_id, -0 AS resource_type_id, -(select metadata_field_id from metadatafieldregistry where metadata_schema_id=(select metadata_schema_id from metadataschemaregistry where short_id='dc') and element = 'source' and qualifier is null) AS metadata_field_id, -source AS text_value, -null AS text_lang, -0 AS place -FROM bitstream where not source is null; - -alter table bitstream drop (name, description, user_format_description, source); - - --- --------- --- epersongroup --- --------- - -INSERT INTO metadatavalue (metadata_value_id, resource_id, resource_type_id, metadata_field_id, text_value, text_lang, place) -SELECT -metadatavalue_seq.nextval as metadata_value_id, -eperson_group_id AS resource_id, -6 AS resource_type_id, -(select metadata_field_id from metadatafieldregistry where metadata_schema_id=(select metadata_schema_id from metadataschemaregistry where short_id='dc') and element = 'title' and qualifier is null) AS metadata_field_id, -name AS text_value, -null AS text_lang, -0 AS place -FROM epersongroup where not name is null; - -alter table epersongroup drop column name; - - - --- --------- --- eperson --- --------- - -INSERT INTO metadatavalue (metadata_value_id, resource_id, resource_type_id, metadata_field_id, text_value, text_lang, place) -SELECT -metadatavalue_seq.nextval as metadata_value_id, -eperson_id AS resource_id, -7 AS resource_type_id, -(select metadata_field_id from metadatafieldregistry where metadata_schema_id=(select metadata_schema_id from metadataschemaregistry where short_id='eperson') and element = 'firstname' and qualifier is null) AS metadata_field_id, -firstname AS text_value, -null AS text_lang, -0 AS place -FROM eperson where not firstname is null; - -INSERT INTO metadatavalue (metadata_value_id, resource_id, resource_type_id, metadata_field_id, text_value, text_lang, place) -SELECT -metadatavalue_seq.nextval as metadata_value_id, -eperson_id AS resource_id, -7 AS resource_type_id, -(select metadata_field_id from metadatafieldregistry where metadata_schema_id=(select metadata_schema_id from metadataschemaregistry where short_id='eperson') and element = 'lastname' and qualifier is null) AS metadata_field_id, -lastname AS text_value, -null AS text_lang, -0 AS place -FROM eperson where not lastname is null; - -INSERT INTO metadatavalue (metadata_value_id, resource_id, resource_type_id, metadata_field_id, text_value, text_lang, place) -SELECT -metadatavalue_seq.nextval as metadata_value_id, -eperson_id AS resource_id, -7 AS resource_type_id, -(select metadata_field_id from metadatafieldregistry where metadata_schema_id=(select metadata_schema_id from metadataschemaregistry where short_id='eperson') and element = 'phone' and qualifier is null) AS metadata_field_id, -phone AS text_value, -null AS text_lang, -0 AS place -FROM eperson where not phone is null; - - -INSERT INTO metadatavalue (metadata_value_id, resource_id, resource_type_id, metadata_field_id, text_value, text_lang, place) -SELECT -metadatavalue_seq.nextval as metadata_value_id, -eperson_id AS resource_id, -7 AS resource_type_id, -(select metadata_field_id from metadatafieldregistry where metadata_schema_id=(select metadata_schema_id from metadataschemaregistry where short_id='eperson') and element = 'language' and qualifier is null) AS metadata_field_id, -language AS text_value, -null AS text_lang, -0 AS place -FROM eperson where not language is null; - -alter table eperson drop (firstname, lastname, phone, language); - --- --------- --- dcvalue view --- --------- - -drop view dcvalue; - -CREATE VIEW dcvalue AS - SELECT MetadataValue.metadata_value_id AS "dc_value_id", MetadataValue.resource_id, - MetadataValue.metadata_field_id AS "dc_type_id", MetadataValue.text_value, - MetadataValue.text_lang, MetadataValue.place - FROM MetadataValue, MetadataFieldRegistry - WHERE MetadataValue.metadata_field_id = MetadataFieldRegistry.metadata_field_id - AND MetadataFieldRegistry.metadata_schema_id = 1 AND MetadataValue.resource_type_id = 2; diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V5.6_2016.08.23__DS-3097.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V5.6_2016.08.23__DS-3097.sql deleted file mode 100644 index 2e09b807de..0000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V5.6_2016.08.23__DS-3097.sql +++ /dev/null @@ -1,24 +0,0 @@ --- --- The contents of this file are subject to the license and copyright --- detailed in the LICENSE and NOTICE files at the root of the source --- tree and available online at --- --- http://www.dspace.org/license/ --- - ------------------------------------------------------- --- DS-3097 introduced new action id for WITHDRAWN_READ ------------------------------------------------------- - -UPDATE resourcepolicy SET action_id = 12 where action_id = 0 and resource_type_id = 0 and resource_id in ( - SELECT bundle2bitstream.bitstream_id FROM bundle2bitstream - LEFT JOIN item2bundle ON bundle2bitstream.bundle_id = item2bundle.bundle_id - LEFT JOIN item ON item2bundle.item_id = item.item_id - WHERE item.withdrawn = 1 -); - -UPDATE resourcepolicy SET action_id = 12 where action_id = 0 and resource_type_id = 1 and resource_id in ( - SELECT item2bundle.bundle_id FROM item2bundle - LEFT JOIN item ON item2bundle.item_id = item.item_id - WHERE item.withdrawn = 1 -); \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V5.7_2017.04.11__DS-3563_Index_metadatavalue_resource_type_id_column.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V5.7_2017.04.11__DS-3563_Index_metadatavalue_resource_type_id_column.sql deleted file mode 100644 index 9f9836faf4..0000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V5.7_2017.04.11__DS-3563_Index_metadatavalue_resource_type_id_column.sql +++ /dev/null @@ -1,23 +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/ --- - ------------------------------------------------------- --- DS-3563 Missing database index on metadatavalue.resource_type_id ------------------------------------------------------- --- Create an index on the metadata value resource_type_id column so that it can be searched efficiently. -declare - index_not_exists EXCEPTION; - PRAGMA EXCEPTION_INIT(index_not_exists, -1418); -begin - - execute immediate 'DROP INDEX metadatavalue_type_id_idx'; - exception - when index_not_exists then null; -end; -/ -CREATE INDEX metadatavalue_type_id_idx ON metadatavalue (resource_type_id); \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2015.03.07__DS-2701_Hibernate_migration.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2015.03.07__DS-2701_Hibernate_migration.sql deleted file mode 100644 index dd857e763d..0000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2015.03.07__DS-2701_Hibernate_migration.sql +++ /dev/null @@ -1,469 +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/ --- - ------------------------------------------------------- --- DS-2701 Service based API / Hibernate integration ------------------------------------------------------- -DROP VIEW community2item; - -CREATE TABLE dspaceobject -( - uuid RAW(16) NOT NULL PRIMARY KEY -); - -CREATE TABLE site -( - uuid RAW(16) NOT NULL PRIMARY KEY REFERENCES dspaceobject(uuid) -); - -ALTER TABLE eperson ADD uuid RAW(16) DEFAULT SYS_GUID(); -INSERT INTO dspaceobject (uuid) SELECT uuid FROM eperson; -ALTER TABLE eperson ADD FOREIGN KEY (uuid) REFERENCES dspaceobject; -ALTER TABLE eperson MODIFY uuid NOT NULL; -ALTER TABLE eperson ADD CONSTRAINT eperson_id_unique PRIMARY KEY (uuid); -UPDATE eperson SET require_certificate = '0' WHERE require_certificate IS NULL; -UPDATE eperson SET self_registered = '0' WHERE self_registered IS NULL; - - - -UPDATE metadatavalue SET text_value='Administrator' - WHERE resource_type_id=6 AND resource_id=1; -UPDATE metadatavalue SET text_value='Anonymous' - WHERE resource_type_id=6 AND resource_id=0; - -ALTER TABLE epersongroup ADD uuid RAW(16) DEFAULT SYS_GUID(); -INSERT INTO dspaceobject (uuid) SELECT uuid FROM epersongroup; -ALTER TABLE epersongroup ADD FOREIGN KEY (uuid) REFERENCES dspaceobject; -ALTER TABLE epersongroup MODIFY uuid NOT NULL; -ALTER TABLE epersongroup ADD CONSTRAINT epersongroup_id_unique PRIMARY KEY (uuid); - -ALTER TABLE item ADD uuid RAW(16) DEFAULT SYS_GUID(); -INSERT INTO dspaceobject (uuid) SELECT uuid FROM item; -ALTER TABLE item ADD FOREIGN KEY (uuid) REFERENCES dspaceobject; -ALTER TABLE item MODIFY uuid NOT NULL; -ALTER TABLE item ADD CONSTRAINT item_id_unique PRIMARY KEY (uuid); - -ALTER TABLE community ADD uuid RAW(16) DEFAULT SYS_GUID(); -INSERT INTO dspaceobject (uuid) SELECT uuid FROM community; -ALTER TABLE community ADD FOREIGN KEY (uuid) REFERENCES dspaceobject; -ALTER TABLE community MODIFY uuid NOT NULL; -ALTER TABLE community ADD CONSTRAINT community_id_unique PRIMARY KEY (uuid); - - -ALTER TABLE collection ADD uuid RAW(16) DEFAULT SYS_GUID(); -INSERT INTO dspaceobject (uuid) SELECT uuid FROM collection; -ALTER TABLE collection ADD FOREIGN KEY (uuid) REFERENCES dspaceobject; -ALTER TABLE collection MODIFY uuid NOT NULL; -ALTER TABLE collection ADD CONSTRAINT collection_id_unique PRIMARY KEY (uuid); - -ALTER TABLE bundle ADD uuid RAW(16) DEFAULT SYS_GUID(); -INSERT INTO dspaceobject (uuid) SELECT uuid FROM bundle; -ALTER TABLE bundle ADD FOREIGN KEY (uuid) REFERENCES dspaceobject; -ALTER TABLE bundle MODIFY uuid NOT NULL; -ALTER TABLE bundle ADD CONSTRAINT bundle_id_unique PRIMARY KEY (uuid); - -ALTER TABLE bitstream ADD uuid RAW(16) DEFAULT SYS_GUID(); -INSERT INTO dspaceobject (uuid) SELECT uuid FROM bitstream; -ALTER TABLE bitstream ADD FOREIGN KEY (uuid) REFERENCES dspaceobject; -ALTER TABLE bitstream MODIFY uuid NOT NULL; -ALTER TABLE bitstream ADD CONSTRAINT bitstream_id_unique PRIMARY KEY (uuid); -UPDATE bitstream SET sequence_id = -1 WHERE sequence_id IS NULL; -UPDATE bitstream SET size_bytes = -1 WHERE size_bytes IS NULL; -UPDATE bitstream SET deleted = '0' WHERE deleted IS NULL; -UPDATE bitstream SET store_number = -1 WHERE store_number IS NULL; - --- Migrate EPersonGroup2EPerson table -ALTER TABLE EPersonGroup2EPerson RENAME COLUMN eperson_group_id to eperson_group_legacy_id; -ALTER TABLE EPersonGroup2EPerson RENAME COLUMN eperson_id to eperson_legacy_id; -ALTER TABLE EPersonGroup2EPerson ADD eperson_group_id RAW(16) REFERENCES EpersonGroup(uuid); -ALTER TABLE EPersonGroup2EPerson ADD eperson_id RAW(16) REFERENCES Eperson(uuid); -CREATE INDEX EpersonGroup2Eperson_group on EpersonGroup2Eperson(eperson_group_id); -CREATE INDEX EpersonGroup2Eperson_person on EpersonGroup2Eperson(eperson_id); -UPDATE EPersonGroup2EPerson SET eperson_group_id = (SELECT EPersonGroup.uuid FROM EpersonGroup WHERE EPersonGroup2EPerson.eperson_group_legacy_id = EPersonGroup.eperson_group_id); -UPDATE EPersonGroup2EPerson SET eperson_id = (SELECT eperson.uuid FROM eperson WHERE EPersonGroup2EPerson.eperson_legacy_id = eperson.eperson_id); -ALTER TABLE EPersonGroup2EPerson MODIFY eperson_group_id NOT NULL; -ALTER TABLE EPersonGroup2EPerson MODIFY eperson_id NOT NULL; -ALTER TABLE EPersonGroup2EPerson DROP COLUMN eperson_group_legacy_id; -ALTER TABLE EPersonGroup2EPerson DROP COLUMN eperson_legacy_id; -ALTER TABLE epersongroup2eperson DROP COLUMN id; -ALTER TABLE EPersonGroup2EPerson add CONSTRAINT EPersonGroup2EPerson_unique primary key (eperson_group_id,eperson_id); - --- Migrate GROUP2GROUP table -ALTER TABLE Group2Group RENAME COLUMN parent_id to parent_legacy_id; -ALTER TABLE Group2Group RENAME COLUMN child_id to child_legacy_id; -ALTER TABLE Group2Group ADD parent_id RAW(16) REFERENCES EpersonGroup(uuid); -ALTER TABLE Group2Group ADD child_id RAW(16) REFERENCES EpersonGroup(uuid); -CREATE INDEX Group2Group_parent on Group2Group(parent_id); -CREATE INDEX Group2Group_child on Group2Group(child_id); -UPDATE Group2Group SET parent_id = (SELECT EPersonGroup.uuid FROM EpersonGroup WHERE Group2Group.parent_legacy_id = EPersonGroup.eperson_group_id); -UPDATE Group2Group SET child_id = (SELECT EpersonGroup.uuid FROM EpersonGroup WHERE Group2Group.child_legacy_id = EpersonGroup.eperson_group_id); -ALTER TABLE Group2Group MODIFY parent_id NOT NULL; -ALTER TABLE Group2Group MODIFY child_id NOT NULL; -ALTER TABLE Group2Group DROP COLUMN parent_legacy_id; -ALTER TABLE Group2Group DROP COLUMN child_legacy_id; -ALTER TABLE Group2Group DROP COLUMN id; -ALTER TABLE Group2Group add CONSTRAINT Group2Group_unique primary key (parent_id,child_id); - --- Migrate collection2item -ALTER TABLE Collection2Item RENAME COLUMN collection_id to collection_legacy_id; -ALTER TABLE Collection2Item RENAME COLUMN item_id to item_legacy_id; -ALTER TABLE Collection2Item ADD collection_id RAW(16) REFERENCES Collection(uuid); -ALTER TABLE Collection2Item ADD item_id RAW(16) REFERENCES Item(uuid); -CREATE INDEX Collecion2Item_collection on Collection2Item(collection_id); -CREATE INDEX Collecion2Item_item on Collection2Item(item_id); -UPDATE Collection2Item SET collection_id = (SELECT Collection.uuid FROM Collection WHERE Collection2Item.collection_legacy_id = Collection.collection_id); -UPDATE Collection2Item SET item_id = (SELECT Item.uuid FROM Item WHERE Collection2Item.item_legacy_id = Item.item_id); -ALTER TABLE Collection2Item MODIFY collection_id NOT NULL; -ALTER TABLE Collection2Item MODIFY item_id NOT NULL; -ALTER TABLE Collection2Item DROP COLUMN collection_legacy_id; -ALTER TABLE Collection2Item DROP COLUMN item_legacy_id; -ALTER TABLE Collection2Item DROP COLUMN id; --- Magic query that will delete all duplicate collection item_id references from the database (if we don't do this the primary key creation will fail) -DELETE FROM collection2item WHERE rowid NOT IN (SELECT MIN(rowid) FROM collection2item GROUP BY collection_id,item_id); -ALTER TABLE Collection2Item add CONSTRAINT collection2item_unique primary key (collection_id,item_id); - --- Migrate Community2Community -ALTER TABLE Community2Community RENAME COLUMN parent_comm_id to parent_legacy_id; -ALTER TABLE Community2Community RENAME COLUMN child_comm_id to child_legacy_id; -ALTER TABLE Community2Community ADD parent_comm_id RAW(16) REFERENCES Community(uuid); -ALTER TABLE Community2Community ADD child_comm_id RAW(16) REFERENCES Community(uuid); -CREATE INDEX Community2Community_parent on Community2Community(parent_comm_id); -CREATE INDEX Community2Community_child on Community2Community(child_comm_id); -UPDATE Community2Community SET parent_comm_id = (SELECT Community.uuid FROM Community WHERE Community2Community.parent_legacy_id = Community.community_id); -UPDATE Community2Community SET child_comm_id = (SELECT Community.uuid FROM Community WHERE Community2Community.child_legacy_id = Community.community_id); -ALTER TABLE Community2Community MODIFY parent_comm_id NOT NULL; -ALTER TABLE Community2Community MODIFY child_comm_id NOT NULL; -ALTER TABLE Community2Community DROP COLUMN parent_legacy_id; -ALTER TABLE Community2Community DROP COLUMN child_legacy_id; -ALTER TABLE Community2Community DROP COLUMN id; -ALTER TABLE Community2Community add CONSTRAINT Community2Community_unique primary key (parent_comm_id,child_comm_id); - --- Migrate community2collection -ALTER TABLE community2collection RENAME COLUMN collection_id to collection_legacy_id; -ALTER TABLE community2collection RENAME COLUMN community_id to community_legacy_id; -ALTER TABLE community2collection ADD collection_id RAW(16) REFERENCES Collection(uuid); -ALTER TABLE community2collection ADD community_id RAW(16) REFERENCES Community(uuid); -CREATE INDEX community2collection_collectio on community2collection(collection_id); -CREATE INDEX community2collection_community on community2collection(community_id); -UPDATE community2collection SET collection_id = (SELECT Collection.uuid FROM Collection WHERE community2collection.collection_legacy_id = Collection.collection_id); -UPDATE community2collection SET community_id = (SELECT Community.uuid FROM Community WHERE community2collection.community_legacy_id = Community.community_id); -ALTER TABLE community2collection MODIFY collection_id NOT NULL; -ALTER TABLE community2collection MODIFY community_id NOT NULL; -ALTER TABLE community2collection DROP COLUMN collection_legacy_id; -ALTER TABLE community2collection DROP COLUMN community_legacy_id; -ALTER TABLE community2collection DROP COLUMN id; -ALTER TABLE community2collection add CONSTRAINT community2collection_unique primary key (collection_id,community_id); - - --- Migrate Group2GroupCache table -ALTER TABLE Group2GroupCache RENAME COLUMN parent_id to parent_legacy_id; -ALTER TABLE Group2GroupCache RENAME COLUMN child_id to child_legacy_id; -ALTER TABLE Group2GroupCache ADD parent_id RAW(16) REFERENCES EpersonGroup(uuid); -ALTER TABLE Group2GroupCache ADD child_id RAW(16) REFERENCES EpersonGroup(uuid); -CREATE INDEX Group2GroupCache_parent on Group2GroupCache(parent_id); -CREATE INDEX Group2GroupCache_child on Group2GroupCache(child_id); -UPDATE Group2GroupCache SET parent_id = (SELECT EPersonGroup.uuid FROM EpersonGroup WHERE Group2GroupCache.parent_legacy_id = EPersonGroup.eperson_group_id); -UPDATE Group2GroupCache SET child_id = (SELECT EpersonGroup.uuid FROM EpersonGroup WHERE Group2GroupCache.child_legacy_id = EpersonGroup.eperson_group_id); -ALTER TABLE Group2GroupCache MODIFY parent_id NOT NULL; -ALTER TABLE Group2GroupCache MODIFY child_id NOT NULL; -ALTER TABLE Group2GroupCache DROP COLUMN parent_legacy_id; -ALTER TABLE Group2GroupCache DROP COLUMN child_legacy_id; -ALTER TABLE Group2GroupCache DROP COLUMN id; -ALTER TABLE Group2GroupCache add CONSTRAINT Group2GroupCache_unique primary key (parent_id,child_id); - --- Migrate Item2Bundle -ALTER TABLE item2bundle RENAME COLUMN bundle_id to bundle_legacy_id; -ALTER TABLE item2bundle RENAME COLUMN item_id to item_legacy_id; -ALTER TABLE item2bundle ADD bundle_id RAW(16) REFERENCES Bundle(uuid); -ALTER TABLE item2bundle ADD item_id RAW(16) REFERENCES Item(uuid); -CREATE INDEX item2bundle_bundle on item2bundle(bundle_id); -CREATE INDEX item2bundle_item on item2bundle(item_id); -UPDATE item2bundle SET bundle_id = (SELECT Bundle.uuid FROM Bundle WHERE item2bundle.bundle_legacy_id = Bundle.bundle_id); -UPDATE item2bundle SET item_id = (SELECT Item.uuid FROM Item WHERE item2bundle.item_legacy_id = Item.item_id); -ALTER TABLE item2bundle MODIFY bundle_id NOT NULL; -ALTER TABLE item2bundle MODIFY item_id NOT NULL; -ALTER TABLE item2bundle DROP COLUMN bundle_legacy_id; -ALTER TABLE item2bundle DROP COLUMN item_legacy_id; -ALTER TABLE item2bundle DROP COLUMN id; -ALTER TABLE item2bundle add CONSTRAINT item2bundle_unique primary key (bundle_id,item_id); - ---Migrate Bundle2Bitsteam -ALTER TABLE bundle2bitstream RENAME COLUMN bundle_id to bundle_legacy_id; -ALTER TABLE bundle2bitstream RENAME COLUMN bitstream_id to bitstream_legacy_id; -ALTER TABLE bundle2bitstream ADD bundle_id RAW(16) REFERENCES Bundle(uuid); -ALTER TABLE bundle2bitstream ADD bitstream_id RAW(16) REFERENCES Bitstream(uuid); -CREATE INDEX bundle2bitstream_bundle on bundle2bitstream(bundle_id); -CREATE INDEX bundle2bitstream_bitstream on bundle2bitstream(bitstream_id); -UPDATE bundle2bitstream SET bundle_id = (SELECT bundle.uuid FROM bundle WHERE bundle2bitstream.bundle_legacy_id = bundle.bundle_id); -UPDATE bundle2bitstream SET bitstream_id = (SELECT bitstream.uuid FROM bitstream WHERE bundle2bitstream.bitstream_legacy_id = bitstream.bitstream_id); -ALTER TABLE bundle2bitstream RENAME COLUMN bitstream_order to bitstream_order_legacy; -ALTER TABLE bundle2bitstream ADD bitstream_order INTEGER; -MERGE INTO bundle2bitstream dst -USING ( SELECT ROWID AS r_id - , ROW_NUMBER () OVER ( PARTITION BY bundle_id - ORDER BY bitstream_order_legacy, bitstream_id - ) AS new_order - FROM bundle2bitstream - ) src -ON (dst.ROWID = src.r_id) -WHEN MATCHED THEN UPDATE -SET dst.bitstream_order = (src.new_order-1) -; -ALTER TABLE bundle2bitstream MODIFY bundle_id NOT NULL; -ALTER TABLE bundle2bitstream MODIFY bitstream_id NOT NULL; -ALTER TABLE bundle2bitstream DROP COLUMN bundle_legacy_id; -ALTER TABLE bundle2bitstream DROP COLUMN bitstream_legacy_id; -ALTER TABLE bundle2bitstream DROP COLUMN id; -ALTER TABLE bundle2bitstream add CONSTRAINT bundle2bitstream_unique primary key (bitstream_id,bundle_id,bitstream_order); - - --- Migrate item -ALTER TABLE item RENAME COLUMN submitter_id to submitter_id_legacy_id; -ALTER TABLE item ADD submitter_id RAW(16) REFERENCES EPerson(uuid); -CREATE INDEX item_submitter on item(submitter_id); -UPDATE item SET submitter_id = (SELECT eperson.uuid FROM eperson WHERE item.submitter_id_legacy_id = eperson.eperson_id); -ALTER TABLE item DROP COLUMN submitter_id_legacy_id; - -ALTER TABLE item RENAME COLUMN owning_collection to owning_collection_legacy; -ALTER TABLE item ADD owning_collection RAW(16) REFERENCES Collection(uuid); -CREATE INDEX item_collection on item(owning_collection); -UPDATE item SET owning_collection = (SELECT Collection.uuid FROM Collection WHERE item.owning_collection_legacy = collection.collection_id); -ALTER TABLE item DROP COLUMN owning_collection_legacy; - -UPDATE item SET in_archive = '0' WHERE in_archive IS NULL; -UPDATE item SET discoverable = '0' WHERE discoverable IS NULL; -UPDATE item SET withdrawn = '0' WHERE withdrawn IS NULL; - --- Migrate bundle -ALTER TABLE bundle RENAME COLUMN primary_bitstream_id to primary_bitstream_legacy_id; -ALTER TABLE bundle ADD primary_bitstream_id RAW(16) REFERENCES Bitstream(uuid); -CREATE INDEX bundle_primary on bundle(primary_bitstream_id); -UPDATE bundle SET primary_bitstream_id = (SELECT Bitstream.uuid FROM Bitstream WHERE bundle.primary_bitstream_legacy_id = Bitstream.bitstream_id); -ALTER TABLE bundle DROP COLUMN primary_bitstream_legacy_id; - - --- Migrate community references -ALTER TABLE Community RENAME COLUMN admin to admin_legacy; -ALTER TABLE Community ADD admin RAW(16) REFERENCES EPersonGroup(uuid); -CREATE INDEX Community_admin on Community(admin); -UPDATE Community SET admin = (SELECT EPersonGroup.uuid FROM EPersonGroup WHERE Community.admin_legacy = EPersonGroup.eperson_group_id); -ALTER TABLE Community DROP COLUMN admin_legacy; - -ALTER TABLE Community RENAME COLUMN logo_bitstream_id to logo_bitstream_legacy_id; -ALTER TABLE Community ADD logo_bitstream_id RAW(16) REFERENCES Bitstream(uuid); -CREATE INDEX Community_bitstream on Community(logo_bitstream_id); -UPDATE Community SET logo_bitstream_id = (SELECT Bitstream.uuid FROM Bitstream WHERE Community.logo_bitstream_legacy_id = Bitstream.bitstream_id); -ALTER TABLE Community DROP COLUMN logo_bitstream_legacy_id; - - ---Migrate Collection references -ALTER TABLE Collection RENAME COLUMN workflow_step_1 to workflow_step_1_legacy; -ALTER TABLE Collection RENAME COLUMN workflow_step_2 to workflow_step_2_legacy; -ALTER TABLE Collection RENAME COLUMN workflow_step_3 to workflow_step_3_legacy; -ALTER TABLE Collection RENAME COLUMN submitter to submitter_legacy; -ALTER TABLE Collection RENAME COLUMN template_item_id to template_item_legacy_id; -ALTER TABLE Collection RENAME COLUMN logo_bitstream_id to logo_bitstream_legacy_id; -ALTER TABLE Collection RENAME COLUMN admin to admin_legacy; -ALTER TABLE Collection ADD workflow_step_1 RAW(16) REFERENCES EPersonGroup(uuid); -ALTER TABLE Collection ADD workflow_step_2 RAW(16) REFERENCES EPersonGroup(uuid); -ALTER TABLE Collection ADD workflow_step_3 RAW(16) REFERENCES EPersonGroup(uuid); -ALTER TABLE Collection ADD submitter RAW(16) REFERENCES EPersonGroup(uuid); -ALTER TABLE Collection ADD template_item_id RAW(16); -ALTER TABLE Collection ADD logo_bitstream_id RAW(16); -ALTER TABLE Collection ADD admin RAW(16) REFERENCES EPersonGroup(uuid); -CREATE INDEX Collection_workflow1 on Collection(workflow_step_1); -CREATE INDEX Collection_workflow2 on Collection(workflow_step_2); -CREATE INDEX Collection_workflow3 on Collection(workflow_step_3); -CREATE INDEX Collection_submitter on Collection(submitter); -CREATE INDEX Collection_template on Collection(template_item_id); -CREATE INDEX Collection_bitstream on Collection(logo_bitstream_id); -UPDATE Collection SET workflow_step_1 = (SELECT EPersonGroup.uuid FROM EPersonGroup WHERE Collection.workflow_step_1_legacy = EPersonGroup.eperson_group_id); -UPDATE Collection SET workflow_step_2 = (SELECT EPersonGroup.uuid FROM EPersonGroup WHERE Collection.workflow_step_2_legacy = EPersonGroup.eperson_group_id); -UPDATE Collection SET workflow_step_3 = (SELECT EPersonGroup.uuid FROM EPersonGroup WHERE Collection.workflow_step_3_legacy = EPersonGroup.eperson_group_id); -UPDATE Collection SET submitter = (SELECT EPersonGroup.uuid FROM EPersonGroup WHERE Collection.submitter_legacy = EPersonGroup.eperson_group_id); -UPDATE Collection SET template_item_id = (SELECT Item.uuid FROM Item WHERE Collection.template_item_legacy_id = Item.item_id); -UPDATE Collection SET logo_bitstream_id = (SELECT Bitstream.uuid FROM Bitstream WHERE Collection.logo_bitstream_legacy_id = Bitstream.bitstream_id); -UPDATE Collection SET admin = (SELECT EPersonGroup.uuid FROM EPersonGroup WHERE Collection.admin_legacy = EPersonGroup.eperson_group_id); -ALTER TABLE Collection DROP COLUMN workflow_step_1_legacy; -ALTER TABLE Collection DROP COLUMN workflow_step_2_legacy; -ALTER TABLE Collection DROP COLUMN workflow_step_3_legacy; -ALTER TABLE Collection DROP COLUMN submitter_legacy; -ALTER TABLE Collection DROP COLUMN template_item_legacy_id; -ALTER TABLE Collection DROP COLUMN logo_bitstream_legacy_id; -ALTER TABLE Collection DROP COLUMN admin_legacy; - - --- Migrate resource policy references -ALTER TABLE ResourcePolicy RENAME COLUMN eperson_id to eperson_id_legacy_id; -ALTER TABLE ResourcePolicy ADD eperson_id RAW(16) REFERENCES EPerson(uuid); -CREATE INDEX resourcepolicy_person on resourcepolicy(eperson_id); -UPDATE ResourcePolicy SET eperson_id = (SELECT eperson.uuid FROM eperson WHERE ResourcePolicy.eperson_id_legacy_id = eperson.eperson_id); -ALTER TABLE ResourcePolicy DROP COLUMN eperson_id_legacy_id; - -ALTER TABLE ResourcePolicy RENAME COLUMN epersongroup_id to epersongroup_id_legacy_id; -ALTER TABLE ResourcePolicy ADD epersongroup_id RAW(16) REFERENCES EPersonGroup(uuid); -CREATE INDEX resourcepolicy_group on resourcepolicy(epersongroup_id); -UPDATE ResourcePolicy SET epersongroup_id = (SELECT epersongroup.uuid FROM epersongroup WHERE ResourcePolicy.epersongroup_id_legacy_id = epersongroup.eperson_group_id); -ALTER TABLE ResourcePolicy DROP COLUMN epersongroup_id_legacy_id; - -ALTER TABLE ResourcePolicy ADD dspace_object RAW(16) REFERENCES dspaceobject(uuid); -CREATE INDEX resourcepolicy_object on resourcepolicy(dspace_object); -UPDATE ResourcePolicy SET dspace_object = (SELECT eperson.uuid FROM eperson WHERE ResourcePolicy.resource_id = eperson.eperson_id AND ResourcePolicy.resource_type_id = 7) WHERE ResourcePolicy.resource_type_id = 7; -UPDATE ResourcePolicy SET dspace_object = (SELECT epersongroup.uuid FROM epersongroup WHERE ResourcePolicy.resource_id = epersongroup.eperson_group_id AND ResourcePolicy.resource_type_id = 6) WHERE ResourcePolicy.resource_type_id = 6; -UPDATE ResourcePolicy SET dspace_object = (SELECT community.uuid FROM community WHERE ResourcePolicy.resource_id = community.community_id AND ResourcePolicy.resource_type_id = 4) WHERE ResourcePolicy.resource_type_id = 4; -UPDATE ResourcePolicy SET dspace_object = (SELECT collection.uuid FROM collection WHERE ResourcePolicy.resource_id = collection.collection_id AND ResourcePolicy.resource_type_id = 3) WHERE ResourcePolicy.resource_type_id = 3; -UPDATE ResourcePolicy SET dspace_object = (SELECT item.uuid FROM item WHERE ResourcePolicy.resource_id = item.item_id AND ResourcePolicy.resource_type_id = 2) WHERE ResourcePolicy.resource_type_id = 2; -UPDATE ResourcePolicy SET dspace_object = (SELECT bundle.uuid FROM bundle WHERE ResourcePolicy.resource_id = bundle.bundle_id AND ResourcePolicy.resource_type_id = 1) WHERE ResourcePolicy.resource_type_id = 1; -UPDATE ResourcePolicy SET dspace_object = (SELECT bitstream.uuid FROM bitstream WHERE ResourcePolicy.resource_id = bitstream.bitstream_id AND ResourcePolicy.resource_type_id = 0) WHERE ResourcePolicy.resource_type_id = 0; -UPDATE resourcepolicy SET resource_type_id = -1 WHERE resource_type_id IS NULL; -UPDATE resourcepolicy SET action_id = -1 WHERE action_id IS NULL; - - --- Migrate Subscription -ALTER TABLE Subscription RENAME COLUMN eperson_id to eperson_legacy_id; -ALTER TABLE Subscription ADD eperson_id RAW(16) REFERENCES EPerson(uuid); -CREATE INDEX Subscription_person on Subscription(eperson_id); -UPDATE Subscription SET eperson_id = (SELECT eperson.uuid FROM eperson WHERE Subscription.eperson_legacy_id = eperson.eperson_id); -ALTER TABLE Subscription DROP COLUMN eperson_legacy_id; - -ALTER TABLE Subscription RENAME COLUMN collection_id to collection_legacy_id; -ALTER TABLE Subscription ADD collection_id RAW(16) REFERENCES Collection(uuid); -CREATE INDEX Subscription_collection on Subscription(collection_id); -UPDATE Subscription SET collection_id = (SELECT collection.uuid FROM collection WHERE Subscription.collection_legacy_id = collection.collection_id); -ALTER TABLE Subscription DROP COLUMN collection_legacy_id; - - --- Migrate versionitem -ALTER TABLE versionitem RENAME COLUMN eperson_id to eperson_legacy_id; -ALTER TABLE versionitem ADD eperson_id RAW(16) REFERENCES EPerson(uuid); -CREATE INDEX versionitem_person on versionitem(eperson_id); -UPDATE versionitem SET eperson_id = (SELECT eperson.uuid FROM eperson WHERE versionitem.eperson_legacy_id = eperson.eperson_id); -ALTER TABLE versionitem DROP COLUMN eperson_legacy_id; - -ALTER TABLE versionitem RENAME COLUMN item_id to item_legacy_id; -ALTER TABLE versionitem ADD item_id RAW(16) REFERENCES Item(uuid); -CREATE INDEX versionitem_item on versionitem(item_id); -UPDATE versionitem SET item_id = (SELECT item.uuid FROM item WHERE versionitem.item_legacy_id = item.item_id); -ALTER TABLE versionitem DROP COLUMN item_legacy_id; -UPDATE versionitem SET version_number = -1 WHERE version_number IS NULL; - --- Migrate handle table -ALTER TABLE handle RENAME COLUMN resource_id to resource_legacy_id; -ALTER TABLE handle ADD resource_id RAW(16) REFERENCES dspaceobject(uuid); -CREATE INDEX handle_object on handle(resource_id); -UPDATE handle SET resource_id = (SELECT community.uuid FROM community WHERE handle.resource_legacy_id = community.community_id AND handle.resource_type_id = 4); -UPDATE handle SET resource_id = (SELECT collection.uuid FROM collection WHERE handle.resource_legacy_id = collection.collection_id AND handle.resource_type_id = 3); -UPDATE handle SET resource_id = (SELECT item.uuid FROM item WHERE handle.resource_legacy_id = item.item_id AND handle.resource_type_id = 2); - --- Migrate metadata value table -DROP VIEW dcvalue; - -ALTER TABLE metadatavalue ADD dspace_object_id RAW(16) REFERENCES dspaceobject(uuid); --- CREATE INDEX metadatavalue_field on metadatavalue(metadata_field_id); -CREATE INDEX metadatavalue_object on metadatavalue(dspace_object_id); -CREATE INDEX metadatavalue_field_object on metadatavalue(metadata_field_id, dspace_object_id); -UPDATE metadatavalue SET dspace_object_id = (SELECT eperson.uuid FROM eperson WHERE metadatavalue.resource_id = eperson.eperson_id AND metadatavalue.resource_type_id = 7) WHERE metadatavalue.resource_type_id= 7; -UPDATE metadatavalue SET dspace_object_id = (SELECT epersongroup.uuid FROM epersongroup WHERE metadatavalue.resource_id = epersongroup.eperson_group_id AND metadatavalue.resource_type_id = 6) WHERE metadatavalue.resource_type_id= 6; -UPDATE metadatavalue SET dspace_object_id = (SELECT community.uuid FROM community WHERE metadatavalue.resource_id = community.community_id AND metadatavalue.resource_type_id = 4) WHERE metadatavalue.resource_type_id= 4; -UPDATE metadatavalue SET dspace_object_id = (SELECT collection.uuid FROM collection WHERE metadatavalue.resource_id = collection.collection_id AND metadatavalue.resource_type_id = 3) WHERE metadatavalue.resource_type_id= 3; -UPDATE metadatavalue SET dspace_object_id = (SELECT item.uuid FROM item WHERE metadatavalue.resource_id = item.item_id AND metadatavalue.resource_type_id = 2) WHERE metadatavalue.resource_type_id= 2; -UPDATE metadatavalue SET dspace_object_id = (SELECT bundle.uuid FROM bundle WHERE metadatavalue.resource_id = bundle.bundle_id AND metadatavalue.resource_type_id = 1) WHERE metadatavalue.resource_type_id= 1; -UPDATE metadatavalue SET dspace_object_id = (SELECT bitstream.uuid FROM bitstream WHERE metadatavalue.resource_id = bitstream.bitstream_id AND metadatavalue.resource_type_id = 0) WHERE metadatavalue.resource_type_id= 0; -DROP INDEX metadatavalue_item_idx; -DROP INDEX metadatavalue_item_idx2; -ALTER TABLE metadatavalue DROP COLUMN resource_id; -ALTER TABLE metadatavalue DROP COLUMN resource_type_id; -UPDATE MetadataValue SET confidence = -1 WHERE confidence IS NULL; -UPDATE metadatavalue SET place = -1 WHERE place IS NULL; - --- Alter harvested item -ALTER TABLE harvested_item RENAME COLUMN item_id to item_legacy_id; -ALTER TABLE harvested_item ADD item_id RAW(16) REFERENCES item(uuid); -CREATE INDEX harvested_item_item on harvested_item(item_id); -UPDATE harvested_item SET item_id = (SELECT item.uuid FROM item WHERE harvested_item.item_legacy_id = item.item_id); -ALTER TABLE harvested_item DROP COLUMN item_legacy_id; - --- Alter harvested collection -ALTER TABLE harvested_collection RENAME COLUMN collection_id to collection_legacy_id; -ALTER TABLE harvested_collection ADD collection_id RAW(16) REFERENCES Collection(uuid); -CREATE INDEX harvested_collection_collectio on harvested_collection(collection_id); -UPDATE harvested_collection SET collection_id = (SELECT collection.uuid FROM collection WHERE harvested_collection.collection_legacy_id = collection.collection_id); -ALTER TABLE harvested_collection DROP COLUMN collection_legacy_id; - -UPDATE harvested_collection SET harvest_type = -1 WHERE harvest_type IS NULL; -UPDATE harvested_collection SET harvest_status = -1 WHERE harvest_status IS NULL; - - ---Alter workspaceitem -ALTER TABLE workspaceitem RENAME COLUMN item_id to item_legacy_id; -ALTER TABLE workspaceitem ADD item_id RAW(16) REFERENCES Item(uuid); -CREATE INDEX workspaceitem_item on workspaceitem(item_id); -UPDATE workspaceitem SET item_id = (SELECT item.uuid FROM item WHERE workspaceitem.item_legacy_id = item.item_id); -ALTER TABLE workspaceitem DROP COLUMN item_legacy_id; - -ALTER TABLE workspaceitem RENAME COLUMN collection_id to collection_legacy_id; -ALTER TABLE workspaceitem ADD collection_id RAW(16) REFERENCES Collection(uuid); -CREATE INDEX workspaceitem_coll on workspaceitem(collection_id); -UPDATE workspaceitem SET collection_id = (SELECT collection.uuid FROM collection WHERE workspaceitem.collection_legacy_id = collection.collection_id); -ALTER TABLE workspaceitem DROP COLUMN collection_legacy_id; - -UPDATE workspaceitem SET multiple_titles = '0' WHERE multiple_titles IS NULL; -UPDATE workspaceitem SET published_before = '0' WHERE published_before IS NULL; -UPDATE workspaceitem SET multiple_files = '0' WHERE multiple_files IS NULL; -UPDATE workspaceitem SET stage_reached = -1 WHERE stage_reached IS NULL; -UPDATE workspaceitem SET page_reached = -1 WHERE page_reached IS NULL; - ---Alter epersongroup2workspaceitem -ALTER TABLE epersongroup2workspaceitem RENAME COLUMN eperson_group_id to eperson_group_legacy_id; -ALTER TABLE epersongroup2workspaceitem ADD eperson_group_id RAW(16) REFERENCES epersongroup(uuid); -CREATE INDEX epersongroup2workspaceitem_gro on epersongroup2workspaceitem(eperson_group_id); -UPDATE epersongroup2workspaceitem SET eperson_group_id = (SELECT epersongroup.uuid FROM epersongroup WHERE epersongroup2workspaceitem.eperson_group_legacy_id = epersongroup.eperson_group_id); -ALTER TABLE epersongroup2workspaceitem DROP COLUMN eperson_group_legacy_id; - -ALTER TABLE epersongroup2workspaceitem DROP COLUMN id; -ALTER TABLE epersongroup2workspaceitem MODIFY workspace_item_id NOT NULL; -ALTER TABLE epersongroup2workspaceitem MODIFY eperson_group_id NOT NULL; -ALTER TABLE epersongroup2workspaceitem add CONSTRAINT epersongroup2wsitem_unqiue primary key (workspace_item_id,eperson_group_id); - ---Alter most_recent_checksum -ALTER TABLE most_recent_checksum RENAME COLUMN bitstream_id to bitstream_legacy_id; -ALTER TABLE most_recent_checksum ADD bitstream_id RAW(16) REFERENCES Bitstream(uuid); -CREATE INDEX most_recent_checksum_bitstream on most_recent_checksum(bitstream_id); -UPDATE most_recent_checksum SET bitstream_id = (SELECT Bitstream.uuid FROM Bitstream WHERE most_recent_checksum.bitstream_legacy_id = Bitstream.bitstream_id); -ALTER TABLE most_recent_checksum DROP COLUMN bitstream_legacy_id; - -UPDATE most_recent_checksum SET to_be_processed = '0' WHERE to_be_processed IS NULL; -UPDATE most_recent_checksum SET matched_prev_checksum = '0' WHERE matched_prev_checksum IS NULL; - ---Alter checksum_history -ALTER TABLE checksum_history RENAME COLUMN bitstream_id to bitstream_legacy_id; -ALTER TABLE checksum_history ADD bitstream_id RAW(16) REFERENCES Bitstream(uuid); -CREATE INDEX checksum_history_bitstream on checksum_history(bitstream_id); -UPDATE checksum_history SET bitstream_id = (SELECT Bitstream.uuid FROM Bitstream WHERE checksum_history.bitstream_legacy_id = Bitstream.bitstream_id); -ALTER TABLE checksum_history DROP COLUMN bitstream_legacy_id; - -RENAME checksum_history_seq TO checksum_history_check_id_seq; - ---Alter table doi -ALTER TABLE doi ADD dspace_object RAW(16) REFERENCES dspaceobject(uuid); -CREATE INDEX doi_object on doi(dspace_object); -UPDATE doi SET dspace_object = (SELECT community.uuid FROM community WHERE doi.resource_id = community.community_id AND doi.resource_type_id = 4) WHERE doi.resource_type_id = 4; -UPDATE doi SET dspace_object = (SELECT collection.uuid FROM collection WHERE doi.resource_id = collection.collection_id AND doi.resource_type_id = 3) WHERE doi.resource_type_id = 3; -UPDATE doi SET dspace_object = (SELECT item.uuid FROM item WHERE doi.resource_id = item.item_id AND doi.resource_type_id = 2) WHERE doi.resource_type_id = 2; -UPDATE doi SET dspace_object = (SELECT bundle.uuid FROM bundle WHERE doi.resource_id = bundle.bundle_id AND doi.resource_type_id = 1) WHERE doi.resource_type_id = 1; -UPDATE doi SET dspace_object = (SELECT bitstream.uuid FROM bitstream WHERE doi.resource_id = bitstream.bitstream_id AND doi.resource_type_id = 0) WHERE doi.resource_type_id = 0; - ---Update table bitstreamformatregistry -UPDATE bitstreamformatregistry SET support_level = -1 WHERE support_level IS NULL; - ---Update table requestitem -UPDATE requestitem SET allfiles = '0' WHERE allfiles IS NULL; -UPDATE requestitem SET accept_request = '0' WHERE accept_request IS NULL; - ---Update table webapp -UPDATE webapp SET isui = -1 WHERE isui IS NULL; diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2015_03_06_01__DS_3378_lost_oracle_indexes.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2015_03_06_01__DS_3378_lost_oracle_indexes.sql deleted file mode 100644 index 8f1a7ad157..0000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2015_03_06_01__DS_3378_lost_oracle_indexes.sql +++ /dev/null @@ -1,18 +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/ --- - ------------------------------------------------------- --- DS_3378 Lost oracle indexes ------------------------------------------------------- -CREATE UNIQUE INDEX eperson_eperson on eperson(eperson_id); -CREATE UNIQUE INDEX epersongroup_eperson_group on epersongroup(eperson_group_id); -CREATE UNIQUE INDEX community_community on community(community_id); -CREATE UNIQUE INDEX collection_collection on collection(collection_id); -CREATE UNIQUE INDEX item_item on item(item_id); -CREATE UNIQUE INDEX bundle_bundle on bundle(bundle_id); -CREATE UNIQUE INDEX bitstream_bitstream on bitstream(bitstream_id); diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.01.03__DS-3024.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.01.03__DS-3024.sql deleted file mode 100644 index 8ad6f7fcd2..0000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.01.03__DS-3024.sql +++ /dev/null @@ -1,25 +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/ --- - ------------------------------------------------------- --- DS-3024 Invent "permanent" groups ------------------------------------------------------- - -ALTER TABLE epersongroup - ADD (permanent NUMBER(1) DEFAULT 0); -UPDATE epersongroup SET permanent = 1 - WHERE uuid IN ( - SELECT dspace_object_id - FROM metadataschemaregistry s - JOIN metadatafieldregistry f USING (metadata_schema_id) - JOIN metadatavalue v USING (metadata_field_id) - WHERE s.short_id = 'dc' - AND f.element = 'title' - AND f.qualifier IS NULL - AND dbms_lob.compare(v.text_value, 'Administrator') = 0 OR dbms_lob.compare(v.text_value,'Anonymous') = 0 - ); diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.02.25__DS-3004-slow-searching-as-admin.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.02.25__DS-3004-slow-searching-as-admin.sql deleted file mode 100644 index 18cb4a5084..0000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.02.25__DS-3004-slow-searching-as-admin.sql +++ /dev/null @@ -1,30 +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/ --- - ---------------------------------------------------------------- --- DS-3024 extremely slow searching when logged in as admin ---------------------------------------------------------------- --- This script will put the group name on the epersongroup --- record itself for performance reasons. It will also make --- sure that a group name is unique (so that for example no two --- Administrator groups can be created). ---------------------------------------------------------------- - -ALTER TABLE epersongroup -ADD name VARCHAR2(250); - -CREATE UNIQUE INDEX epersongroup_unique_idx_name on epersongroup(name); - -UPDATE epersongroup -SET name = -(SELECT text_value - FROM metadatavalue v - JOIN metadatafieldregistry field on v.metadata_field_id = field.metadata_field_id - JOIN metadataschemaregistry s ON field.metadata_schema_id = s.metadata_schema_id - WHERE s.short_id = 'dc' AND element = 'title' AND qualifier IS NULL - AND v.dspace_object_id = epersongroup.uuid); \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.04.01__DS-1955_Increase_embargo_reason.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.04.01__DS-1955_Increase_embargo_reason.sql deleted file mode 100644 index e0a103749c..0000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.04.01__DS-1955_Increase_embargo_reason.sql +++ /dev/null @@ -1,25 +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/ --- - --- =============================================================== --- WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING --- --- DO NOT MANUALLY RUN THIS DATABASE MIGRATION. IT WILL BE EXECUTED --- AUTOMATICALLY (IF NEEDED) BY "FLYWAY" WHEN YOU STARTUP DSPACE. --- http://flywaydb.org/ --- =============================================================== - ------------------------------------------------------- --- DS-1955 resize rpdescription for embargo reason ------------------------------------------------------- - --- We cannot alter type between varchar2 & clob directly so an in between column is required -ALTER TABLE resourcepolicy ADD rpdescription_clob CLOB; -UPDATE resourcepolicy SET rpdescription_clob=rpdescription, rpdescription=null; -ALTER TABLE resourcepolicy DROP COLUMN rpdescription; -ALTER TABLE resourcepolicy RENAME COLUMN rpdescription_clob TO rpdescription; \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.04.04__DS-3086-OAI-Performance-fix.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.04.04__DS-3086-OAI-Performance-fix.sql deleted file mode 100644 index 7b13d10b6d..0000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.04.04__DS-3086-OAI-Performance-fix.sql +++ /dev/null @@ -1,46 +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/ --- - ---------------------------------------------------------------- --- DS-3086 OAI Harvesting performance ---------------------------------------------------------------- --- This script will create indexes on the key fields of the --- metadataschemaregistry and metadatafieldregistry tables to --- increase the performance of the queries. It will also add --- "ON DELETE CASCADE" to improve the performance of Item deletion. ---------------------------------------------------------------- - -CREATE UNIQUE INDEX metadataschema_idx_short_id on metadataschemaregistry(short_id); - -CREATE INDEX metadatafield_idx_elem_qual on metadatafieldregistry(element, qualifier); - -CREATE INDEX resourcepolicy_idx_rptype on resourcepolicy(rptype); - --- Add "ON DELETE CASCADE" to foreign key constraint to Item -ALTER TABLE RESOURCEPOLICY ADD DSPACE_OBJECT_NEW RAW(16); -UPDATE RESOURCEPOLICY SET DSPACE_OBJECT_NEW = DSPACE_OBJECT; -ALTER TABLE RESOURCEPOLICY DROP COLUMN DSPACE_OBJECT; -ALTER TABLE RESOURCEPOLICY RENAME COLUMN DSPACE_OBJECT_NEW to DSPACE_OBJECT; - -ALTER TABLE RESOURCEPOLICY -ADD CONSTRAINT RESOURCEPOLICY_DSPACE_OBJ_FK -FOREIGN KEY (DSPACE_OBJECT) -REFERENCES dspaceobject(uuid) -ON DELETE CASCADE; - --- Add "ON DELETE CASCADE" to foreign key constraint to Item -ALTER TABLE METADATAVALUE ADD DSPACE_OBJECT_NEW RAW(16); -UPDATE METADATAVALUE SET DSPACE_OBJECT_NEW = DSPACE_OBJECT_ID; -ALTER TABLE METADATAVALUE DROP COLUMN DSPACE_OBJECT_ID; -ALTER TABLE METADATAVALUE RENAME COLUMN DSPACE_OBJECT_NEW to DSPACE_OBJECT_ID; - -ALTER TABLE METADATAVALUE -ADD CONSTRAINT METADATAVALUE_DSPACE_OBJECT_FK -FOREIGN KEY (DSPACE_OBJECT_ID) -REFERENCES DSPACEOBJECT(UUID) -ON DELETE CASCADE; \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.04.14__DS-3125-fix-bundle-bitstream-delete-rights.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.04.14__DS-3125-fix-bundle-bitstream-delete-rights.sql deleted file mode 100644 index a1b303f036..0000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.04.14__DS-3125-fix-bundle-bitstream-delete-rights.sql +++ /dev/null @@ -1,33 +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/ --- - ---------------------------------------------------------------- --- DS-3125 Submitters cannot delete bistreams of workspaceitems ---------------------------------------------------------------- --- This script will add delete rights on all bundles/bitstreams --- for people who already have REMOVE rights. --- In previous versions REMOVE rights was enough to ensure that --- you could delete an object. ---------------------------------------------------------------- -INSERT INTO resourcepolicy (policy_id, resource_type_id, resource_id, action_id, start_date, end_date, rpname, -rptype, rpdescription, eperson_id, epersongroup_id, dspace_object) -SELECT -resourcepolicy_seq.nextval AS policy_id, -resource_type_id, -resource_id, --- Insert the Constants.DELETE action -2 AS action_id, -start_date, -end_date, -rpname, -rptype, -rpdescription, -eperson_id, -epersongroup_id, -dspace_object -FROM resourcepolicy WHERE action_id=4 AND (resource_type_id=0 OR resource_type_id=1 OR resource_type_id=2); \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.05.10__DS-3168-fix-requestitem_item_id_column.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.05.10__DS-3168-fix-requestitem_item_id_column.sql deleted file mode 100644 index 2ba3517e19..0000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.05.10__DS-3168-fix-requestitem_item_id_column.sql +++ /dev/null @@ -1,24 +0,0 @@ --- --- The contents of this file are subject to the license and copyright --- detailed in the LICENSE and NOTICE files at the root of the source --- tree and available online at --- --- http://www.dspace.org/license/ --- - ---------------------------------------------------------------- --- DS-3168 Embargo request Unknown Entity RequestItem ---------------------------------------------------------------- --- convert the item_id and bitstream_id columns from integer to UUID ---------------------------------------------------------------- -ALTER TABLE requestitem RENAME COLUMN item_id to item_legacy_id; -ALTER TABLE requestitem ADD item_id RAW(16) REFERENCES Item(uuid); -CREATE INDEX requestitem_item on requestitem(item_id); -UPDATE requestitem SET item_id = (SELECT item.uuid FROM item WHERE requestitem.item_legacy_id = item.item_id); -ALTER TABLE requestitem DROP COLUMN item_legacy_id; - -ALTER TABLE requestitem RENAME COLUMN bitstream_id to bitstream_legacy_id; -ALTER TABLE requestitem ADD bitstream_id RAW(16) REFERENCES Bitstream(uuid); -CREATE INDEX requestitem_bitstream on requestitem(bitstream_id); -UPDATE requestitem SET bitstream_id = (SELECT Bitstream.uuid FROM Bitstream WHERE requestitem.bitstream_legacy_id = Bitstream.bitstream_id); -ALTER TABLE requestitem DROP COLUMN bitstream_legacy_id; \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.07.21__DS-2775.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.07.21__DS-2775.sql deleted file mode 100644 index 7478397446..0000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.07.21__DS-2775.sql +++ /dev/null @@ -1,30 +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/ --- - ------------------------------------------------------- --- DS-2775 Drop unused sequences ------------------------------------------------------- - -DROP SEQUENCE bitstream_seq; -DROP SEQUENCE bundle2bitstream_seq; -DROP SEQUENCE bundle_seq; -DROP SEQUENCE collection2item_seq; -DROP SEQUENCE collection_seq; -DROP SEQUENCE community2collection_seq; -DROP SEQUENCE community2community_seq; -DROP SEQUENCE community_seq; -DROP SEQUENCE dcvalue_seq; -DROP SEQUENCE eperson_seq; -DROP SEQUENCE epersongroup2eperson_seq; -DROP SEQUENCE epersongroup2workspaceitem_seq; -DROP SEQUENCE epersongroup_seq; -DROP SEQUENCE group2group_seq; -DROP SEQUENCE group2groupcache_seq; -DROP SEQUENCE historystate_seq; -DROP SEQUENCE item2bundle_seq; -DROP SEQUENCE item_seq; diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.07.26__DS-3277_fix_handle_assignment.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.07.26__DS-3277_fix_handle_assignment.sql deleted file mode 100644 index 96f125f78b..0000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.07.26__DS-3277_fix_handle_assignment.sql +++ /dev/null @@ -1,44 +0,0 @@ --- --- The contents of this file are subject to the license and copyright --- detailed in the LICENSE and NOTICE files at the root of the source --- tree and available online at --- --- http://www.dspace.org/license/ --- - ----------------------------------------------------------------------------------- --- DS-3277 : 'handle_id' column needs its own separate sequence, so that Handles --- can be minted from 'handle_seq' ----------------------------------------------------------------------------------- --- Create a new sequence for 'handle_id' column. --- The role of this sequence is to simply provide a unique internal ID to the database. -CREATE SEQUENCE handle_id_seq; --- Initialize new 'handle_id_seq' to the maximum value of 'handle_id' -DECLARE - curr NUMBER := 0; -BEGIN - SELECT max(handle_id) INTO curr FROM handle; - - curr := curr + 1; - - EXECUTE IMMEDIATE 'DROP SEQUENCE handle_id_seq'; - - EXECUTE IMMEDIATE 'CREATE SEQUENCE handle_id_seq START WITH ' || NVL(curr,1); -END; -/ - --- Ensure the 'handle_seq' is updated to the maximum *suffix* in 'handle' column, --- as this sequence is used to mint new Handles. --- Code borrowed from update-sequences.sql and updateseq.sql -DECLARE - curr NUMBER := 0; -BEGIN - SELECT max(to_number(regexp_replace(handle, '.*/', ''), '999999999999')) INTO curr FROM handle WHERE REGEXP_LIKE(handle, '^.*/[0123456789]*$'); - - curr := curr + 1; - - EXECUTE IMMEDIATE 'DROP SEQUENCE handle_seq'; - - EXECUTE IMMEDIATE 'CREATE SEQUENCE handle_seq START WITH ' || NVL(curr,1); -END; -/ \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.08.23__DS-3097.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.08.23__DS-3097.sql deleted file mode 100644 index e1220c8c7c..0000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.08.23__DS-3097.sql +++ /dev/null @@ -1,24 +0,0 @@ --- --- The contents of this file are subject to the license and copyright --- detailed in the LICENSE and NOTICE files at the root of the source --- tree and available online at --- --- http://www.dspace.org/license/ --- - ------------------------------------------------------- --- DS-3097 introduced new action id for WITHDRAWN_READ ------------------------------------------------------- - -UPDATE resourcepolicy SET action_id = 12 where action_id = 0 and dspace_object in ( - SELECT bundle2bitstream.bitstream_id FROM bundle2bitstream - LEFT JOIN item2bundle ON bundle2bitstream.bundle_id = item2bundle.bundle_id - LEFT JOIN item ON item2bundle.item_id = item.uuid - WHERE item.withdrawn = 1 -); - -UPDATE resourcepolicy SET action_id = 12 where action_id = 0 and dspace_object in ( - SELECT item2bundle.bundle_id FROM item2bundle - LEFT JOIN item ON item2bundle.item_id = item.uuid - WHERE item.withdrawn = 1 -); \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.11.29__DS-3410-lost-indexes.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.11.29__DS-3410-lost-indexes.sql deleted file mode 100644 index 5c3c3842aa..0000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.11.29__DS-3410-lost-indexes.sql +++ /dev/null @@ -1,17 +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/ --- - ---------------------------------------------------------------- --- DS-3410 ---------------------------------------------------------------- --- This script will create lost indexes ---------------------------------------------------------------- - -CREATE INDEX resourcepolicy_object on resourcepolicy(dspace_object); -CREATE INDEX metadatavalue_object on metadatavalue(dspace_object_id); -CREATE INDEX metadatavalue_field_object on metadatavalue(metadata_field_id, dspace_object_id); \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.11.30__DS-3409.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.11.30__DS-3409.sql deleted file mode 100644 index 47b2d18be8..0000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.11.30__DS-3409.sql +++ /dev/null @@ -1,16 +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/ --- - ------------------------------------------------------- --- DS-3097 Handle of collections and communities are lost due to bug at V6.0_2015.03.07__DS-2701_Hibernate_migration.sql ------------------------------------------------------- - -UPDATE handle SET resource_id = (SELECT community.uuid FROM community WHERE handle.resource_legacy_id = community.community_id AND handle.resource_type_id = 4) where handle.resource_type_id = 4; -UPDATE handle SET resource_id = (SELECT collection.uuid FROM collection WHERE handle.resource_legacy_id = collection.collection_id AND handle.resource_type_id = 3) where handle.resource_type_id = 3; -UPDATE handle SET resource_id = (SELECT item.uuid FROM item WHERE handle.resource_legacy_id = item.item_id AND handle.resource_type_id = 2) where handle.resource_type_id = 2; - \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2017.10.12__DS-3542-stateless-sessions.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2017.10.12__DS-3542-stateless-sessions.sql deleted file mode 100644 index 30cfae91c8..0000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2017.10.12__DS-3542-stateless-sessions.sql +++ /dev/null @@ -1,20 +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/ --- - --- =============================================================== --- WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING --- --- DO NOT MANUALLY RUN THIS DATABASE MIGRATION. IT WILL BE EXECUTED --- AUTOMATICALLY (IF NEEDED) BY "FLYWAY" WHEN YOU STARTUP DSPACE. --- http://flywaydb.org/ --- =============================================================== - ------------------------------------------------------------------------------------------------------------- --- This adds an extra column to the eperson table where we save a salt for stateless authentication ------------------------------------------------------------------------------------------------------------- -ALTER TABLE eperson ADD session_salt varchar(32); \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2018.04.16__dspace-entities.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2018.04.16__dspace-entities.sql deleted file mode 100644 index fc1c0b2e23..0000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2018.04.16__dspace-entities.sql +++ /dev/null @@ -1,65 +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/ --- - --- =============================================================== --- WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING --- --- DO NOT MANUALLY RUN THIS DATABASE MIGRATION. IT WILL BE EXECUTED --- AUTOMATICALLY (IF NEEDED) BY "FLYWAY" WHEN YOU STARTUP DSPACE. --- http://flywaydb.org/ --- =============================================================== - -------------------------------------------------------------- --- This will create the setup for the dspace 7 entities usage -------------------------------------------------------------- -CREATE SEQUENCE entity_type_id_seq; -CREATE SEQUENCE relationship_type_id_seq; -CREATE SEQUENCE relationship_id_seq; - -CREATE TABLE entity_type -( - id INTEGER NOT NULL PRIMARY KEY, - label varchar(32) UNIQUE NOT NULL -); - -CREATE TABLE relationship_type -( - id INTEGER NOT NULL PRIMARY KEY, - left_type INTEGER NOT NULL, - right_type INTEGER NOT NULL, - left_label varchar(32) NOT NULL, - right_label varchar(32) NOT NULL, - left_min_cardinality INTEGER, - left_max_cardinality INTEGER, - right_min_cardinality INTEGER, - right_max_cardinality INTEGER, - FOREIGN KEY (left_type) REFERENCES entity_type(id), - FOREIGN KEY (right_type) REFERENCES entity_type(id), - CONSTRAINT u_relationship_type_constraint UNIQUE (left_type, right_type, left_label, right_label) - -); - -CREATE TABLE relationship -( - id INTEGER NOT NULL PRIMARY KEY, - left_id raw(16) NOT NULL REFERENCES item(uuid), - type_id INTEGER NOT NULL REFERENCES relationship_type(id), - right_id raw(16) NOT NULL REFERENCES item(uuid), - left_place INTEGER, - right_place INTEGER, - CONSTRAINT u_constraint UNIQUE (left_id, type_id, right_id) - -); - -CREATE INDEX entity_type_label_idx ON entity_type(label); -CREATE INDEX rl_ty_by_left_type_idx ON relationship_type(left_type); -CREATE INDEX rl_ty_by_right_type_idx ON relationship_type(right_type); -CREATE INDEX rl_ty_by_left_label_idx ON relationship_type(left_label); -CREATE INDEX rl_ty_by_right_label_idx ON relationship_type(right_label); -CREATE INDEX relationship_by_left_id_idx ON relationship(left_id); -CREATE INDEX relationship_by_right_id_idx ON relationship(right_id); diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2018.06.07__DS-3851-permission.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2018.06.07__DS-3851-permission.sql deleted file mode 100644 index 68ed690f89..0000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2018.06.07__DS-3851-permission.sql +++ /dev/null @@ -1,24 +0,0 @@ --- --- The contents of this file are subject to the license and copyright --- detailed in the LICENSE and NOTICE files at the root of the source --- tree and available online at --- --- http://www.dspace.org/license/ --- - --- =============================================================== --- WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING --- --- DO NOT MANUALLY RUN THIS DATABASE MIGRATION. IT WILL BE EXECUTED --- AUTOMATICALLY (IF NEEDED) BY "FLYWAY" WHEN YOU STARTUP DSPACE. --- http://flywaydb.org/ --- =============================================================== - ----------------------------------------------------------------------------------------------------------------- --- This adds TYPE_INHERITED to all old archived items permission due to the change on resource policy management ----------------------------------------------------------------------------------------------------------------- -UPDATE resourcepolicy set rptype = 'TYPE_INHERITED' - where resource_type_id = 2 and rptype is null - and dspace_object in ( - select uuid from item where in_archive = 1 - ); \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2019.05.02__DS-4239-workflow-xml-migration.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2019.05.02__DS-4239-workflow-xml-migration.sql deleted file mode 100644 index b23170f437..0000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2019.05.02__DS-4239-workflow-xml-migration.sql +++ /dev/null @@ -1,17 +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/ --- - ---------------------------------------------------------------- --- DS-4239 Migrate the workflow.xml to spring ---------------------------------------------------------------- --- This script will rename the default workflow "default" name --- to the new "defaultWorkflow" identifier ---------------------------------------------------------------- - -UPDATE cwf_pooltask SET workflow_id='defaultWorkflow' WHERE workflow_id='default'; -UPDATE cwf_claimtask SET workflow_id='defaultWorkflow' WHERE workflow_id='default'; \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2019.07.31__Retrieval_of_name_variant.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2019.07.31__Retrieval_of_name_variant.sql deleted file mode 100644 index cebae09f65..0000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2019.07.31__Retrieval_of_name_variant.sql +++ /dev/null @@ -1,18 +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/ --- - ------------------------------------------------------------------------------------ --- Create columns leftwardValue and rightwardValue in table relationship --- Rename columns left_label and right_label to leftward_type and rightward_type ------------------------------------------------------------------------------------ - -ALTER TABLE relationship ADD leftward_value VARCHAR2(50); -ALTER TABLE relationship ADD rightward_value VARCHAR2(50); - -ALTER TABLE relationship_type RENAME COLUMN left_label TO leftward_type; -ALTER TABLE relationship_type RENAME COLUMN right_label TO rightward_type; diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2019_06_14__scripts-and-process.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2019_06_14__scripts-and-process.sql deleted file mode 100644 index a7015e3033..0000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2019_06_14__scripts-and-process.sql +++ /dev/null @@ -1,40 +0,0 @@ --- --- The contents of this file are subject to the license and copyright --- detailed in the LICENSE and NOTICE files at the root of the source --- tree and available online at --- --- http://www.dspace.org/license/ --- - --- =============================================================== --- WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING --- --- DO NOT MANUALLY RUN THIS DATABASE MIGRATION. IT WILL BE EXECUTED --- AUTOMATICALLY (IF NEEDED) BY "FLYWAY" WHEN YOU STARTUP DSPACE. --- http://flywaydb.org/ --- =============================================================== -CREATE SEQUENCE process_id_seq; - -CREATE TABLE process -( - process_id INTEGER NOT NULL PRIMARY KEY, - user_id RAW(16) NOT NULL, - start_time TIMESTAMP, - finished_time TIMESTAMP, - creation_time TIMESTAMP NOT NULL, - script VARCHAR(256) NOT NULL, - status VARCHAR(32), - parameters VARCHAR(512) -); - -CREATE TABLE process2bitstream -( - process_id INTEGER REFERENCES process(process_id), - bitstream_id RAW(16) REFERENCES bitstream(uuid), - CONSTRAINT PK_process2bitstream PRIMARY KEY (process_id, bitstream_id) -); - -CREATE INDEX process_user_id_idx ON process(user_id); -CREATE INDEX process_status_idx ON process(status); -CREATE INDEX process_name_idx on process(script); -CREATE INDEX process_start_time_idx on process(start_time); \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2020.01.08__DS-626-statistics-tracker.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2020.01.08__DS-626-statistics-tracker.sql deleted file mode 100644 index a108fd74b4..0000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2020.01.08__DS-626-statistics-tracker.sql +++ /dev/null @@ -1,29 +0,0 @@ --- --- The contents of this file are subject to the license and copyright --- detailed in the LICENSE and NOTICE files at the root of the source --- tree and available online at --- --- http://www.dspace.org/license/ --- - --- =============================================================== --- WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING --- --- DO NOT MANUALLY RUN THIS DATABASE MIGRATION. IT WILL BE EXECUTED --- AUTOMATICALLY (IF NEEDED) BY "FLYWAY" WHEN YOU STARTUP DSPACE. --- http://flywaydb.org/ --- =============================================================== - -------------------------------------------------------------- --- This will create the setup for the IRUS statistics harvester -------------------------------------------------------------- - -CREATE SEQUENCE openurltracker_seq; - -CREATE TABLE openurltracker -( - tracker_id NUMBER, - tracker_url VARCHAR2(1000), - uploaddate DATE, - CONSTRAINT openurltracker_PK PRIMARY KEY (tracker_id) -); \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2021.03.18__Move_entity_type_to_dspace_schema.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2021.03.18__Move_entity_type_to_dspace_schema.sql deleted file mode 100644 index 9c39091f89..0000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2021.03.18__Move_entity_type_to_dspace_schema.sql +++ /dev/null @@ -1,56 +0,0 @@ --- --- The contents of this file are subject to the license and copyright --- detailed in the LICENSE and NOTICE files at the root of the source --- tree and available online at --- --- http://www.dspace.org/license/ --- - --- =============================================================== --- WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING --- --- DO NOT MANUALLY RUN THIS DATABASE MIGRATION. IT WILL BE EXECUTED --- AUTOMATICALLY (IF NEEDED) BY "FLYWAY" WHEN YOU STARTUP DSPACE. --- http://flywaydb.org/ --- =============================================================== - -------------------------------------------------------------------------------------------------------- --- Move all 'relationship.type' metadata fields to 'dspace.entity.type'. Remove 'relationship' schema. -------------------------------------------------------------------------------------------------------- --- Special case: we need to the 'dspace' schema to already exist. If users don't already have it we must create it --- manually via SQL, as by default it won't be created until database updates are finished. -INSERT INTO metadataschemaregistry (metadata_schema_id, namespace, short_id) - SELECT metadataschemaregistry_seq.nextval, 'http://dspace.org/dspace' as namespace, 'dspace' as short_id FROM dual - WHERE NOT EXISTS - (SELECT metadata_schema_id,namespace,short_id FROM metadataschemaregistry - WHERE namespace = 'http://dspace.org/dspace' AND short_id = 'dspace'); - - --- Add 'dspace.entity.type' field to registry (if missing) -INSERT INTO metadatafieldregistry (metadata_field_id, metadata_schema_id, element, qualifier) - SELECT metadatafieldregistry_seq.nextval, - (SELECT metadata_schema_id FROM metadataschemaregistry WHERE short_id='dspace'), 'entity', 'type' FROM dual - WHERE NOT EXISTS - (SELECT metadata_field_id,element,qualifier FROM metadatafieldregistry - WHERE metadata_schema_id = (SELECT metadata_schema_id FROM metadataschemaregistry WHERE short_id='dspace') - AND element = 'entitye' AND qualifier='type'); - --- Moves all 'relationship.type' field values to a new 'dspace.entity.type' field -UPDATE metadatavalue - SET metadata_field_id = - (SELECT metadata_field_id FROM metadatafieldregistry - WHERE metadata_schema_id = (SELECT metadata_schema_id FROM metadataschemaregistry WHERE short_id='dspace') - AND element = 'entity' AND qualifier='type') - WHERE metadata_field_id = - (SELECT metadata_field_id FROM metadatafieldregistry - WHERE metadata_schema_id = (SELECT metadata_schema_id FROM metadataschemaregistry WHERE short_id='relationship') - AND element = 'type' AND qualifier is NULL); - - --- Delete 'relationship.type' field from registry -DELETE FROM metadatafieldregistry - WHERE metadata_schema_id = (SELECT metadata_schema_id FROM metadataschemaregistry WHERE short_id = 'relationship') - AND element = 'type' AND qualifier is NULL; - --- Delete 'relationship' schema (which is now empty) -DELETE FROM metadataschemaregistry WHERE short_id = 'relationship' AND namespace = 'http://dspace.org/relationship'; diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2021.09.24__Move_entity_type_from_item_template_to_collection.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2021.09.24__Move_entity_type_from_item_template_to_collection.sql deleted file mode 100644 index 5a6abda041..0000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2021.09.24__Move_entity_type_from_item_template_to_collection.sql +++ /dev/null @@ -1,28 +0,0 @@ --- --- The contents of this file are subject to the license and copyright --- detailed in the LICENSE and NOTICE files at the root of the source --- tree and available online at --- --- http://www.dspace.org/license/ --- - --- =============================================================== --- WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING --- --- DO NOT MANUALLY RUN THIS DATABASE MIGRATION. IT WILL BE EXECUTED --- AUTOMATICALLY (IF NEEDED) BY "FLYWAY" WHEN YOU STARTUP DSPACE. --- http://flywaydb.org/ --- =============================================================== - -------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------- -UPDATE metadatavalue SET dspace_object_id = (SELECT uuid - FROM collection - WHERE template_item_id = dspace_object_id) -WHERE dspace_object_id IN (SELECT template_item_id - FROM Collection) - AND metadata_field_id - IN (SELECT metadata_field_id - FROM metadatafieldregistry mfr LEFT JOIN metadataschemaregistry msr - ON mfr.metadata_schema_id = msr.metadata_schema_id - WHERE msr.short_id = 'dspace' AND mfr.element = 'entity' AND mfr.qualifier = 'type'); \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.1_2021.10.18__Fix_MDV_place_after_migrating_from_DSpace_5.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.1_2021.10.18__Fix_MDV_place_after_migrating_from_DSpace_5.sql deleted file mode 100644 index 9c39c15e66..0000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.1_2021.10.18__Fix_MDV_place_after_migrating_from_DSpace_5.sql +++ /dev/null @@ -1,24 +0,0 @@ --- --- The contents of this file are subject to the license and copyright --- detailed in the LICENSE and NOTICE files at the root of the source --- tree and available online at --- --- http://www.dspace.org/license/ --- - ----------------------------------------------------- --- Make sure the metadatavalue.place column starts at 0 instead of 1 ----------------------------------------------------- -MERGE INTO metadatavalue mdv -USING ( - SELECT dspace_object_id, metadata_field_id, MIN(place) AS minplace - FROM metadatavalue - GROUP BY dspace_object_id, metadata_field_id -) mp -ON ( - mdv.dspace_object_id = mp.dspace_object_id - AND mdv.metadata_field_id = mp.metadata_field_id - AND mp.minplace > 0 -) -WHEN MATCHED THEN UPDATE -SET mdv.place = mdv.place - mp.minplace; \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.3_2022.04.29__orcid_queue_and_history.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.3_2022.04.29__orcid_queue_and_history.sql deleted file mode 100644 index 3fe424cf6c..0000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.3_2022.04.29__orcid_queue_and_history.sql +++ /dev/null @@ -1,54 +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/ --- - ------------------------------------------------------------------------------------ --- Create tables for ORCID Queue and History ------------------------------------------------------------------------------------ - -CREATE SEQUENCE orcid_queue_id_seq; - -CREATE TABLE orcid_queue -( - id INTEGER NOT NULL, - owner_id RAW(16) NOT NULL, - entity_id RAW(16), - put_code VARCHAR(255), - record_type VARCHAR(255), - description VARCHAR(255), - operation VARCHAR(255), - metadata CLOB, - attempts INTEGER, - CONSTRAINT orcid_queue_pkey PRIMARY KEY (id), - CONSTRAINT orcid_queue_owner_id_fkey FOREIGN KEY (owner_id) REFERENCES item (uuid), - CONSTRAINT orcid_queue_entity_id_fkey FOREIGN KEY (entity_id) REFERENCES item (uuid) -); - -CREATE INDEX orcid_queue_owner_id_index on orcid_queue(owner_id); - - -CREATE SEQUENCE orcid_history_id_seq; - -CREATE TABLE orcid_history -( - id INTEGER NOT NULL, - owner_id RAW(16) NOT NULL, - entity_id RAW(16), - put_code VARCHAR(255), - timestamp_last_attempt TIMESTAMP, - response_message CLOB, - status INTEGER, - metadata CLOB, - operation VARCHAR(255), - record_type VARCHAR(255), - description VARCHAR(255), - CONSTRAINT orcid_history_pkey PRIMARY KEY (id), - CONSTRAINT orcid_history_owner_id_fkey FOREIGN KEY (owner_id) REFERENCES item (uuid), - CONSTRAINT orcid_history_entity_id_fkey FOREIGN KEY (entity_id) REFERENCES item (uuid) -); - -CREATE INDEX orcid_history_owner_id_index on orcid_history(owner_id); \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.3_2022.05.16__Orcid_token_table.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.3_2022.05.16__Orcid_token_table.sql deleted file mode 100644 index 14bf853143..0000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.3_2022.05.16__Orcid_token_table.sql +++ /dev/null @@ -1,24 +0,0 @@ --- --- The contents of this file are subject to the license and copyright --- detailed in the LICENSE and NOTICE files at the root of the source --- tree and available online at --- --- http://www.dspace.org/license/ --- - ------------------------------------------------------------------------------------ --- Create table for ORCID access tokens ------------------------------------------------------------------------------------ - -CREATE SEQUENCE orcid_token_id_seq; - -CREATE TABLE orcid_token -( - id INTEGER NOT NULL, - eperson_id RAW(16) NOT NULL UNIQUE, - profile_item_id RAW(16), - access_token VARCHAR2(100) NOT NULL, - CONSTRAINT orcid_token_pkey PRIMARY KEY (id), - CONSTRAINT orcid_token_eperson_id_fkey FOREIGN KEY (eperson_id) REFERENCES eperson (uuid), - CONSTRAINT orcid_token_profile_item_id_fkey FOREIGN KEY (profile_item_id) REFERENCES item (uuid) -); diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.3_2022.06.20__add_last_version_status_column_to_relationship_table.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.3_2022.06.20__add_last_version_status_column_to_relationship_table.sql deleted file mode 100644 index 3eb9ae6dd4..0000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.3_2022.06.20__add_last_version_status_column_to_relationship_table.sql +++ /dev/null @@ -1,10 +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/ --- - --- NOTE: default 0 ensures that existing relations have "latest_version_status" set to "both" (first constant in enum, see Relationship class) -ALTER TABLE relationship ADD latest_version_status INTEGER DEFAULT 0 NOT NULL; diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.6_2023.04.19__process_parameters_to_text_type.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.6_2023.04.19__process_parameters_to_text_type.sql new file mode 100644 index 0000000000..6b2dd705ea --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.6_2023.04.19__process_parameters_to_text_type.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 MODIFY (parameters CLOB); diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/update-sequences.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/update-sequences.sql deleted file mode 100644 index b4d4d755cb..0000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/update-sequences.sql +++ /dev/null @@ -1,77 +0,0 @@ --- --- The contents of this file are subject to the license and copyright --- detailed in the LICENSE and NOTICE files at the root of the source --- tree and available online at --- --- http://www.dspace.org/license/ --- - --- SQL code to update the ID (primary key) generating sequences, if some --- import operation has set explicit IDs. --- --- Sequences are used to generate IDs for new rows in the database. If a --- bulk import operation, such as an SQL dump, specifies primary keys for --- imported data explicitly, the sequences are out of sync and need updating. --- This SQL code does just that. --- --- This should rarely be needed; any bulk import should be performed using the --- org.dspace.content API which is safe to use concurrently and in multiple --- JVMs. The SQL code below will typically only be required after a direct --- SQL data dump from a backup or somesuch. - --- The 'updateseq' procedure was derived from incseq.sql found at: --- http://www.akadia.com/services/scripts/incseq.sql - -DECLARE - PROCEDURE updateseq ( seq IN VARCHAR, - tbl IN VARCHAR, - attr IN VARCHAR, - cond IN VARCHAR DEFAULT '' ) IS - curr NUMBER := 0; - BEGIN - EXECUTE IMMEDIATE 'SELECT max(' || attr - || ') FROM ' || tbl - || ' ' || cond - INTO curr; - curr := curr + 1; - EXECUTE IMMEDIATE 'DROP SEQUENCE ' || seq; - EXECUTE IMMEDIATE 'CREATE SEQUENCE ' - || seq - || ' START WITH ' - || NVL(curr, 1); - END updateseq; - -BEGIN - updateseq('bitstreamformatregistry_seq', 'bitstreamformatregistry', - 'bitstream_format_id'); - updateseq('fileextension_seq', 'fileextension', 'file_extension_id'); - updateseq('resourcepolicy_seq', 'resourcepolicy', 'policy_id'); - updateseq('workspaceitem_seq', 'workspaceitem', 'workspace_item_id'); - updateseq('registrationdata_seq', 'registrationdata', - 'registrationdata_id'); - updateseq('subscription_seq', 'subscription', 'subscription_id'); - updateseq('metadatafieldregistry_seq', 'metadatafieldregistry', - 'metadata_field_id'); - updateseq('metadatavalue_seq', 'metadatavalue', 'metadata_value_id'); - updateseq('metadataschemaregistry_seq', 'metadataschemaregistry', - 'metadata_schema_id'); - updateseq('harvested_collection_seq', 'harvested_collection', 'id'); - updateseq('harvested_item_seq', 'harvested_item', 'id'); - updateseq('webapp_seq', 'webapp', 'webapp_id'); - updateseq('requestitem_seq', 'requestitem', 'requestitem_id'); - updateseq('handle_id_seq', 'handle', 'handle_id'); - - -- Handle Sequence is a special case. Since Handles minted by DSpace - -- use the 'handle_seq', we need to ensure the next assigned handle - -- will *always* be unique. So, 'handle_seq' always needs to be set - -- to the value of the *largest* handle suffix. That way when the - -- next handle is assigned, it will use the next largest number. This - -- query does the following: - -- For all 'handle' values which have a number in their suffix - -- (after '/'), find the maximum suffix value, convert it to a - -- number, and set the 'handle_seq' to start at the next value (see - -- updateseq above for more). - updateseq('handle_seq', 'handle', - q'{to_number(regexp_replace(handle, '.*/', ''), '999999999999')}', - q'{WHERE REGEXP_LIKE(handle, '^.*/[0123456789]*$')}'); -END; \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/README.md b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/README.md index 72eb279912..e16e4c6d4c 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/README.md +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/README.md @@ -3,8 +3,9 @@ The SQL scripts in this directory are PostgreSQL-specific database migrations. They are used to automatically upgrade your DSpace database using [Flyway](http://flywaydb.org/). As such, these scripts are automatically called by Flyway when the DSpace -`DatabaseManager` initializes itself (see `initializeDatabase()` method). During -that process, Flyway determines which version of DSpace your database is using +`DatabaseUtils` initializes. + +During that process, Flyway determines which version of DSpace your database is using and then executes the appropriate upgrade script(s) to bring it up to the latest version. @@ -22,7 +23,7 @@ Please see the Flyway Documentation for more information: http://flywaydb.org/ The `update-sequences.sql` script in this directory may still be used to update your internal database counts if you feel they have gotten out of "sync". This may sometimes occur after large restores of content (e.g. when using the DSpace -[AIP Backup and Restore](https://wiki.duraspace.org/display/DSDOC5x/AIP+Backup+and+Restore) +[AIP Backup and Restore](https://wiki.lyrasis.org/display/DSDOC7x/AIP+Backup+and+Restore) feature). This `update-sequences.sql` script can be executed by running diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.5_2022.12.01__add_table_subscriptionparamter_change_columns_subscription_table.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.5_2022.12.01__add_table_subscriptionparamter_change_columns_subscription_table.sql new file mode 100644 index 0000000000..61e01494fc --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.5_2022.12.01__add_table_subscriptionparamter_change_columns_subscription_table.sql @@ -0,0 +1,43 @@ +-- +-- The contents of this file are subject to the license and copyright +-- detailed in the LICENSE and NOTICE files at the root of the source +-- tree and available online at +-- +-- http://www.dspace.org/license/ +-- + +----------------------------------------------------------------------------------- +-- ADD table subscription_parameter +----------------------------------------------------------------------------------- + + +CREATE SEQUENCE if NOT EXISTS subscription_parameter_seq; +----------------------------------------------------------------------------------- +-- ADD table subscription_parameter +----------------------------------------------------------------------------------- +CREATE TABLE if NOT EXISTS subscription_parameter +( + subscription_parameter_id INTEGER NOT NULL, + name CHARACTER VARYING(255), + value CHARACTER VARYING(255), + subscription_id INTEGER NOT NULL, + CONSTRAINT subscription_parameter_pkey PRIMARY KEY (subscription_parameter_id), + CONSTRAINT subscription_parameter_subscription_fkey FOREIGN KEY (subscription_id) REFERENCES subscription (subscription_id) ON DELETE CASCADE +); +-- +ALTER TABLE subscription ADD COLUMN if NOT EXISTS dspace_object_id UUID; +-- -- +ALTER TABLE subscription ADD COLUMN if NOT EXISTS type CHARACTER VARYING(255); +---- -- +ALTER TABLE subscription DROP CONSTRAINT IF EXISTS subscription_dspaceobject_fkey; +ALTER TABLE subscription ADD CONSTRAINT subscription_dspaceobject_fkey FOREIGN KEY (dspace_object_id) REFERENCES dspaceobject (uuid); +-- +UPDATE subscription SET dspace_object_id = collection_id , type = 'content'; +-- +ALTER TABLE subscription DROP CONSTRAINT IF EXISTS subscription_collection_id_fkey; +-- -- +ALTER TABLE subscription DROP COLUMN IF EXISTS collection_id; +-- -- +INSERT INTO subscription_parameter (subscription_parameter_id, name, value, subscription_id) +SELECT getnextid('subscription_parameter'), 'frequency', 'D', subscription_id from "subscription" ; + diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.5_2022.12.06__index_action_resource_policy.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.5_2022.12.06__index_action_resource_policy.sql new file mode 100644 index 0000000000..696e84433d --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.5_2022.12.06__index_action_resource_policy.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/ +-- + +CREATE INDEX resourcepolicy_action_idx ON resourcepolicy(action_id); diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.5_2022.12.09__Supervision_Orders_table.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.5_2022.12.09__Supervision_Orders_table.sql new file mode 100644 index 0000000000..f27a4f2a1b --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.5_2022.12.09__Supervision_Orders_table.sql @@ -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/ +-- + +------------------------------------------------------------------------------- +-- Table to store supervision orders +------------------------------------------------------------------------------- + +CREATE TABLE supervision_orders +( + id INTEGER PRIMARY KEY, + item_id UUID REFERENCES Item(uuid) ON DELETE CASCADE, + eperson_group_id UUID REFERENCES epersongroup(uuid) ON DELETE CASCADE +); + +CREATE SEQUENCE supervision_orders_seq; + +------------------------------------------------------------------------------- +-- migrate data from epersongroup2workspaceitem table +------------------------------------------------------------------------------- + +INSERT INTO supervision_orders (id, item_id, eperson_group_id) +SELECT getnextid('supervision_orders') AS id, w.item_id, e.uuid +FROM epersongroup2workspaceitem ew INNER JOIN workspaceitem w +ON ew.workspace_item_id = w.workspace_item_id +INNER JOIN epersongroup e +ON ew.eperson_group_id = e.uuid; + + +-- UPDATE policies for supervision orders +-- items, bundles and bitstreams + +do +$$ +DECLARE +rec record; +BEGIN + +FOR rec IN + +SELECT so.item_id as dspace_object, so.eperson_group_id, rp.resource_type_id +FROM supervision_orders so +INNER JOIN RESOURCEPOLICY rp on so.item_id = rp.dspace_object +AND so.eperson_group_id = rp.epersongroup_id +WHERE rp.rptype IS NULL + +UNION + +SELECT ib.bundle_id as dspace_object, so.eperson_group_id, rp.resource_type_id +FROM supervision_orders so +INNER JOIN item2bundle ib ON so.item_id = ib.item_id +INNER JOIN RESOURCEPOLICY rp on ib.bundle_id = rp.dspace_object +AND so.eperson_group_id = rp.epersongroup_id +WHERE rp.rptype IS NULL + +UNION + +SELECT bs.bitstream_id as dspace_object, so.eperson_group_id, rp.resource_type_id +FROM supervision_orders so +INNER JOIN item2bundle ib ON so.item_id = ib.item_id +INNER JOIN bundle2bitstream bs ON ib.bundle_id = bs.bundle_id +INNER JOIN RESOURCEPOLICY rp on bs.bitstream_id = rp.dspace_object +AND so.eperson_group_id = rp.epersongroup_id +WHERE rp.rptype IS NULL + +LOOP + +UPDATE RESOURCEPOLICY SET rptype = 'TYPE_SUBMISSION' +where dspace_object = rec.dspace_object +AND epersongroup_id = rec.eperson_group_id +AND rptype IS NULL; + +END LOOP; +END; +$$; + +------------------------------------------------------------------------------- +-- drop epersongroup2workspaceitem table +------------------------------------------------------------------------------- + +DROP TABLE epersongroup2workspaceitem; \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.5_2022.12.15__system_wide_alerts.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.5_2022.12.15__system_wide_alerts.sql new file mode 100644 index 0000000000..9d13138fda --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.5_2022.12.15__system_wide_alerts.sql @@ -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/ +-- + +----------------------------------------------------------------------------------- +-- Create table for System wide alerts +----------------------------------------------------------------------------------- + +CREATE SEQUENCE alert_id_seq; + +CREATE TABLE systemwidealert +( + alert_id INTEGER NOT NULL PRIMARY KEY, + message VARCHAR(512), + allow_sessions VARCHAR(64), + countdown_to TIMESTAMP, + active BOOLEAN +); diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2021.01.22__Remove_basic_workflow.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2023.03.17__Remove_unused_sequence.sql similarity index 65% rename from dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2021.01.22__Remove_basic_workflow.sql rename to dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2023.03.17__Remove_unused_sequence.sql index f71173abe6..e4544e1de7 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2021.01.22__Remove_basic_workflow.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2023.03.17__Remove_unused_sequence.sql @@ -7,11 +7,7 @@ -- ----------------------------------------------------------------------------------- --- Drop the 'workflowitem' and 'tasklistitem' tables +-- Drop the 'history_seq' sequence (related table deleted at Dspace-1.5) ----------------------------------------------------------------------------------- -DROP TABLE workflowitem CASCADE CONSTRAINTS; -DROP TABLE tasklistitem CASCADE CONSTRAINTS; - -DROP SEQUENCE workflowitem_seq; -DROP SEQUENCE tasklistitem_seq; \ No newline at end of file +DROP SEQUENCE IF EXISTS history_seq; \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2021.10.04__alter_collection_table_drop_workflow_stem_columns.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2023.03.24__Update_PNG_in_bitstream_format_registry.sql similarity index 54% rename from dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2021.10.04__alter_collection_table_drop_workflow_stem_columns.sql rename to dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2023.03.24__Update_PNG_in_bitstream_format_registry.sql index ae8f1e7ef5..8aec44a7f6 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2021.10.04__alter_collection_table_drop_workflow_stem_columns.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2023.03.24__Update_PNG_in_bitstream_format_registry.sql @@ -6,10 +6,12 @@ -- http://www.dspace.org/license/ -- -------------------------------------------------------------------------------------- ----- ALTER table collection -------------------------------------------------------------------------------------- +----------------------------------------------------------------------------------- +-- Update short description for PNG mimetype in the bitstream format registry +-- See: https://github.com/DSpace/DSpace/pull/8722 +----------------------------------------------------------------------------------- -ALTER TABLE collection DROP COLUMN workflow_step_1; -ALTER TABLE collection DROP COLUMN workflow_step_2; -ALTER TABLE collection DROP COLUMN workflow_step_3; \ No newline at end of file +UPDATE bitstreamformatregistry +SET short_description='PNG' +WHERE short_description='image/png' + AND mimetype='image/png'; diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2023.03.29__orcid_queue_and_history_descriptions_to_text_type.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2023.03.29__orcid_queue_and_history_descriptions_to_text_type.sql new file mode 100644 index 0000000000..ae0e414e44 --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2023.03.29__orcid_queue_and_history_descriptions_to_text_type.sql @@ -0,0 +1,10 @@ +-- +-- The contents of this file are subject to the license and 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 orcid_history ALTER COLUMN description TYPE TEXT; +ALTER TABLE orcid_queue ALTER COLUMN description TYPE TEXT; diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2023.04.19__process_parameters_to_text_type.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2023.04.19__process_parameters_to_text_type.sql new file mode 100644 index 0000000000..f7e0e51d0b --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2023.04.19__process_parameters_to_text_type.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 parameters TYPE TEXT; diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/update-sequences.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/update-sequences.sql index 749f82382c..f96434f1ba 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/update-sequences.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/update-sequences.sql @@ -19,21 +19,41 @@ -- JVMs. The SQL code below will typically only be required after a direct -- SQL data dump from a backup or somesuch. - +SELECT setval('alert_id_seq', max(alert_id)) FROM systemwidealert; SELECT setval('bitstreamformatregistry_seq', max(bitstream_format_id)) FROM bitstreamformatregistry; +SELECT setval('checksum_history_check_id_seq', max(check_id)) FROM checksum_history; +SELECT setval('cwf_claimtask_seq', max(claimtask_id)) FROM cwf_claimtask; +SELECT setval('cwf_collectionrole_seq', max(collectionrole_id)) FROM cwf_collectionrole; +SELECT setval('cwf_in_progress_user_seq', max(in_progress_user_id)) FROM cwf_in_progress_user; +SELECT setval('cwf_pooltask_seq', max(pooltask_id)) FROM cwf_pooltask; +SELECT setval('cwf_workflowitem_seq', max(workflowitem_id)) FROM cwf_workflowitem; +SELECT setval('cwf_workflowitemrole_seq', max(workflowitemrole_id)) FROM cwf_workflowitemrole; +SELECT setval('doi_seq', max(doi_id)) FROM doi; +SELECT setval('entity_type_id_seq', max(id)) FROM entity_type; SELECT setval('fileextension_seq', max(file_extension_id)) FROM fileextension; -SELECT setval('resourcepolicy_seq', max(policy_id)) FROM resourcepolicy; -SELECT setval('workspaceitem_seq', max(workspace_item_id)) FROM workspaceitem; -SELECT setval('registrationdata_seq', max(registrationdata_id)) FROM registrationdata; -SELECT setval('subscription_seq', max(subscription_id)) FROM subscription; -SELECT setval('metadatafieldregistry_seq', max(metadata_field_id)) FROM metadatafieldregistry; -SELECT setval('metadatavalue_seq', max(metadata_value_id)) FROM metadatavalue; -SELECT setval('metadataschemaregistry_seq', max(metadata_schema_id)) FROM metadataschemaregistry; +SELECT setval('handle_id_seq', max(handle_id)) FROM handle; SELECT setval('harvested_collection_seq', max(id)) FROM harvested_collection; SELECT setval('harvested_item_seq', max(id)) FROM harvested_item; -SELECT setval('webapp_seq', max(webapp_id)) FROM webapp; +SELECT setval('metadatafieldregistry_seq', max(metadata_field_id)) FROM metadatafieldregistry; +SELECT setval('metadataschemaregistry_seq', max(metadata_schema_id)) FROM metadataschemaregistry; +SELECT setval('metadatavalue_seq', max(metadata_value_id)) FROM metadatavalue; +SELECT setval('openurltracker_seq', max(tracker_id)) FROM openurltracker; +SELECT setval('orcid_history_id_seq', max(id)) FROM orcid_history; +SELECT setval('orcid_queue_id_seq', max(id)) FROM orcid_queue; +SELECT setval('orcid_token_id_seq', max(id)) FROM orcid_token; +SELECT setval('process_id_seq', max(process_id)) FROM process; +SELECT setval('registrationdata_seq', max(registrationdata_id)) FROM registrationdata; +SELECT setval('relationship_id_seq', max(id)) FROM relationship; +SELECT setval('relationship_type_id_seq', max(id)) FROM relationship_type; SELECT setval('requestitem_seq', max(requestitem_id)) FROM requestitem; -SELECT setval('handle_id_seq', max(handle_id)) FROM handle; +SELECT setval('resourcepolicy_seq', max(policy_id)) FROM resourcepolicy; +SELECT setval('subscription_parameter_seq', max(subscription_id)) FROM subscription_parameter; +SELECT setval('subscription_seq', max(subscription_id)) FROM subscription; +SELECT setval('supervision_orders_seq', max(id)) FROM supervision_orders; +SELECT setval('versionhistory_seq', max(versionhistory_id)) FROM versionhistory; +SELECT setval('versionitem_seq', max(versionitem_id)) FROM versionitem; +SELECT setval('webapp_seq', max(webapp_id)) FROM webapp; +SELECT setval('workspaceitem_seq', max(workspace_item_id)) FROM workspaceitem; -- Handle Sequence is a special case. Since Handles minted by DSpace use the 'handle_seq', -- we need to ensure the next assigned handle will *always* be unique. So, 'handle_seq' diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/workflow/oracle/basicWorkflow/V5.7_2017.05.05__DS-3431.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/workflow/oracle/basicWorkflow/V5.7_2017.05.05__DS-3431.sql deleted file mode 100644 index 9bca3a17c9..0000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/workflow/oracle/basicWorkflow/V5.7_2017.05.05__DS-3431.sql +++ /dev/null @@ -1,503 +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/ --- - -------------------------------------------------------------------------- --- DS-3431 Workflow system is vulnerable to unauthorized manipulations -- -------------------------------------------------------------------------- - ------------------------------------------------------------------------ --- grant claiming permissions to all workflow step groups (step 1-3) -- ------------------------------------------------------------------------ -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, epersongroup_id, resource_id) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '3' AS resource_type_id, - '5' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - workflow_step_1 AS epersongroup_id, - collection_id AS dspace_object - FROM collection - WHERE workflow_step_1 IS NOT NULL - AND NOT EXISTS ( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 3 AND action_id = 5 AND epersongroup_id = workflow_step_1 and resource_id = collection_id - ); - -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, epersongroup_id, resource_id) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '3' AS resource_type_id, - '6' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - workflow_step_2 AS epersongroup_id, - collection_id AS dspace_object - FROM collection - WHERE workflow_step_2 IS NOT NULL - AND NOT EXISTS ( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 3 AND action_id = 6 AND epersongroup_id = workflow_step_2 and resource_id = collection_id - ); - -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, epersongroup_id, resource_id) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '3' AS resource_type_id, - '7' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - workflow_step_3 AS epersongroup_id, - collection_id AS dspace_object - FROM collection - WHERE workflow_step_3 IS NOT NULL - AND NOT EXISTS ( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 3 AND action_id = 7 AND epersongroup_id = workflow_step_3 and resource_id = collection_id - ); - ------------------------------------------------------------------------ --- grant add permissions to all workflow step groups (step 1-3) -- ------------------------------------------------------------------------ -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, epersongroup_id, resource_id) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '3' AS resource_type_id, - '3' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - workflow_step_1 AS epersongroup_id, - collection_id AS dspace_object - FROM collection - WHERE workflow_step_1 IS NOT NULL - AND NOT EXISTS ( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 3 AND action_id = 3 AND epersongroup_id = workflow_step_1 and resource_id = collection_id - ); - -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, epersongroup_id, resource_id) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '3' AS resource_type_id, - '3' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - workflow_step_2 AS epersongroup_id, - collection_id AS dspace_object - FROM collection - WHERE workflow_step_2 IS NOT NULL - AND NOT EXISTS ( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 3 AND action_id = 3 AND epersongroup_id = workflow_step_2 and resource_id = collection_id - ); - -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, epersongroup_id, resource_id) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '3' AS resource_type_id, - '3' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - workflow_step_3 AS epersongroup_id, - collection_id AS dspace_object - FROM collection - WHERE workflow_step_3 IS NOT NULL - AND NOT EXISTS ( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 3 AND action_id = 3 AND epersongroup_id = workflow_step_3 and resource_id = collection_id - ); - ----------------------------------------------------------------------------------- --- grant read/write/delete/add/remove permission on workflow items to reviewers -- ----------------------------------------------------------------------------------- -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, eperson_id, resource_id) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '2' AS resource_type_id, - '0' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - owner AS eperson_id, - item_id AS dspace_object - FROM workflowitem - WHERE - owner IS NOT NULL - AND (state = 2 OR state = 4 OR state = 6) - AND NOT EXISTS ( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 2 AND action_id = 0 AND eperson_id = owner AND resource_id = item_id - ); - -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, eperson_id, resource_id) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '2' AS resource_type_id, - '1' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - owner AS eperson_id, - item_id AS dspace_object - FROM workflowitem - WHERE - owner IS NOT NULL - AND (state = 2 OR state = 4 OR state = 6) - AND NOT EXISTS ( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 2 AND action_id = 1 AND eperson_id = owner AND resource_id = item_id - ); - -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, eperson_id, resource_id) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '2' AS resource_type_id, - '2' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - owner AS eperson_id, - item_id AS dspace_object - FROM workflowitem - WHERE - owner IS NOT NULL - AND (state = 2 OR state = 4 OR state = 6) - AND NOT EXISTS ( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 2 AND action_id = 2 AND eperson_id = owner AND resource_id = item_id - ); - -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, eperson_id, resource_id) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '2' AS resource_type_id, - '3' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - owner AS eperson_id, - item_id AS dspace_object - FROM workflowitem - WHERE - owner IS NOT NULL - AND (state = 2 OR state = 4 OR state = 6) - AND NOT EXISTS ( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 2 AND action_id = 3 AND eperson_id = owner AND resource_id = item_id - ); - -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, eperson_id, resource_id) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '2' AS resource_type_id, - '4' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - owner AS eperson_id, - item_id AS dspace_object - FROM workflowitem - WHERE - owner IS NOT NULL - AND (state = 2 OR state = 4 OR state = 6) - AND NOT EXISTS ( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 2 AND action_id = 4 AND eperson_id = owner AND resource_id = item_id - ); - ------------------------------------------------------------------------------------ --- grant read/write/delete/add/remove permission on Bundle ORIGINAL to reviewers -- ------------------------------------------------------------------------------------ -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, eperson_id, resource_id) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '1' AS resource_type_id, - '0' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - wfi.owner AS eperson_id, - i2b.bundle_id AS dspace_object - FROM workflowitem wfi - JOIN item2bundle i2b - ON i2b.item_id = wfi.item_id - JOIN metadatavalue mv - ON mv.resource_id = i2b.bundle_id - JOIN metadatafieldregistry mfr - ON mv.metadata_field_id = mfr.metadata_field_id - JOIN metadataschemaregistry msr - ON mfr.metadata_schema_id = msr.metadata_schema_id - WHERE - msr.namespace = 'http://dublincore.org/documents/dcmi-terms/' - AND mfr.element = 'title' - AND mfr.qualifier IS NULL - AND mv.text_value LIKE 'ORIGINAL' - AND wfi.owner IS NOT NULL - AND (wfi.state = 2 OR wfi.state = 4 OR wfi.state = 6) - AND NOT EXISTS( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 1 AND action_id = 0 AND resourcepolicy.eperson_id = owner AND resourcepolicy.resource_id = i2b.bundle_id - ); - -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, eperson_id, resource_id) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '1' AS resource_type_id, - '1' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - wfi.owner AS eperson_id, - i2b.bundle_id AS dspace_object - FROM workflowitem wfi - JOIN item2bundle i2b - ON i2b.item_id = wfi.item_id - JOIN metadatavalue mv - ON mv.resource_id = i2b.bundle_id - JOIN metadatafieldregistry mfr - ON mv.metadata_field_id = mfr.metadata_field_id - JOIN metadataschemaregistry msr - ON mfr.metadata_schema_id = msr.metadata_schema_id - WHERE - msr.namespace = 'http://dublincore.org/documents/dcmi-terms/' - AND mfr.element = 'title' - AND mfr.qualifier IS NULL - AND mv.text_value LIKE 'ORIGINAL' - AND wfi.owner IS NOT NULL - AND (wfi.state = 2 OR wfi.state = 4 OR wfi.state = 6) - AND NOT EXISTS( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 1 AND action_id = 1 AND resourcepolicy.eperson_id = owner AND resourcepolicy.resource_id = i2b.bundle_id - ); - -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, eperson_id, resource_id) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '1' AS resource_type_id, - '2' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - wfi.owner AS eperson_id, - i2b.bundle_id AS dspace_object - FROM workflowitem wfi - JOIN item2bundle i2b - ON i2b.item_id = wfi.item_id - JOIN metadatavalue mv - ON mv.resource_id = i2b.bundle_id - JOIN metadatafieldregistry mfr - ON mv.metadata_field_id = mfr.metadata_field_id - JOIN metadataschemaregistry msr - ON mfr.metadata_schema_id = msr.metadata_schema_id - WHERE - msr.namespace = 'http://dublincore.org/documents/dcmi-terms/' - AND mfr.element = 'title' - AND mfr.qualifier IS NULL - AND mv.text_value LIKE 'ORIGINAL' - AND wfi.owner IS NOT NULL - AND (wfi.state = 2 OR wfi.state = 4 OR wfi.state = 6) - AND NOT EXISTS( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 1 AND action_id = 2 AND resourcepolicy.eperson_id = owner AND resourcepolicy.resource_id = i2b.bundle_id - ); - -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, eperson_id, resource_id) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '1' AS resource_type_id, - '3' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - wfi.owner AS eperson_id, - i2b.bundle_id AS dspace_object - FROM workflowitem wfi - JOIN item2bundle i2b - ON i2b.item_id = wfi.item_id - JOIN metadatavalue mv - ON mv.resource_id = i2b.bundle_id - JOIN metadatafieldregistry mfr - ON mv.metadata_field_id = mfr.metadata_field_id - JOIN metadataschemaregistry msr - ON mfr.metadata_schema_id = msr.metadata_schema_id - WHERE - msr.namespace = 'http://dublincore.org/documents/dcmi-terms/' - AND mfr.element = 'title' - AND mfr.qualifier IS NULL - AND mv.text_value LIKE 'ORIGINAL' - AND wfi.owner IS NOT NULL - AND (wfi.state = 2 OR wfi.state = 4 OR wfi.state = 6) - AND NOT EXISTS( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 1 AND action_id = 3 AND resourcepolicy.eperson_id = owner AND resourcepolicy.resource_id = i2b.bundle_id - ); - -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, eperson_id, resource_id) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '1' AS resource_type_id, - '4' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - wfi.owner AS eperson_id, - i2b.bundle_id AS dspace_object - FROM workflowitem wfi - JOIN item2bundle i2b - ON i2b.item_id = wfi.item_id - JOIN metadatavalue mv - ON mv.resource_id = i2b.bundle_id - JOIN metadatafieldregistry mfr - ON mv.metadata_field_id = mfr.metadata_field_id - JOIN metadataschemaregistry msr - ON mfr.metadata_schema_id = msr.metadata_schema_id - WHERE - msr.namespace = 'http://dublincore.org/documents/dcmi-terms/' - AND mfr.element = 'title' - AND mfr.qualifier IS NULL - AND mv.text_value LIKE 'ORIGINAL' - AND wfi.owner IS NOT NULL - AND (wfi.state = 2 OR wfi.state = 4 OR wfi.state = 6) - AND NOT EXISTS( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 1 AND action_id = 4 AND resourcepolicy.eperson_id = owner AND resourcepolicy.resource_id = i2b.bundle_id - ); - - -------------------------------------------------------------------------------- --- grant read/write/delete/add/remove permission on all Bitstreams of Bundle -- --- ORIGINAL to reviewers -- -------------------------------------------------------------------------------- -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, eperson_id, resource_id) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '0' AS resource_type_id, - '0' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - wfi.owner AS eperson_id, - b2b.bitstream_id AS dspace_object - FROM workflowitem wfi - JOIN item2bundle i2b - ON i2b.item_id = wfi.item_id - JOIN bundle2bitstream b2b - ON b2b.bundle_id = i2b.bundle_id - JOIN metadatavalue mv - ON mv.resource_id = i2b.bundle_id - JOIN metadatafieldregistry mfr - ON mv.metadata_field_id = mfr.metadata_field_id - JOIN metadataschemaregistry msr - ON mfr.metadata_schema_id = msr.metadata_schema_id - WHERE - msr.namespace = 'http://dublincore.org/documents/dcmi-terms/' - AND mfr.element = 'title' - AND mfr.qualifier IS NULL - AND mv.text_value LIKE 'ORIGINAL' - AND wfi.owner IS NOT NULL - AND (wfi.state = 2 OR wfi.state = 4 OR wfi.state = 6) - AND NOT EXISTS( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 0 AND action_id = 0 AND resourcepolicy.eperson_id = owner AND resourcepolicy.resource_id = b2b.bitstream_id - ); - -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, eperson_id, resource_id) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '0' AS resource_type_id, - '1' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - wfi.owner AS eperson_id, - b2b.bitstream_id AS dspace_object - FROM workflowitem wfi - JOIN item2bundle i2b - ON i2b.item_id = wfi.item_id - JOIN bundle2bitstream b2b - ON b2b.bundle_id = i2b.bundle_id - JOIN metadatavalue mv - ON mv.resource_id = i2b.bundle_id - JOIN metadatafieldregistry mfr - ON mv.metadata_field_id = mfr.metadata_field_id - JOIN metadataschemaregistry msr - ON mfr.metadata_schema_id = msr.metadata_schema_id - WHERE - msr.namespace = 'http://dublincore.org/documents/dcmi-terms/' - AND mfr.element = 'title' - AND mfr.qualifier IS NULL - AND mv.text_value LIKE 'ORIGINAL' - AND wfi.owner IS NOT NULL - AND (wfi.state = 2 OR wfi.state = 4 OR wfi.state = 6) - AND NOT EXISTS( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 0 AND action_id = 1 AND resourcepolicy.eperson_id = owner AND resourcepolicy.resource_id = b2b.bitstream_id - ); - -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, eperson_id, resource_id) - SELECT - resourcepolicy_seq.NEXTVAL policy_id, - '0' AS resource_type_id, - '2' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - wfi.owner AS eperson_id, - b2b.bitstream_id AS dspace_object - FROM workflowitem wfi - JOIN item2bundle i2b - ON i2b.item_id = wfi.item_id - JOIN bundle2bitstream b2b - ON b2b.bundle_id = i2b.bundle_id - JOIN metadatavalue mv - ON mv.resource_id = i2b.bundle_id - JOIN metadatafieldregistry mfr - ON mv.metadata_field_id = mfr.metadata_field_id - JOIN metadataschemaregistry msr - ON mfr.metadata_schema_id = msr.metadata_schema_id - WHERE - msr.namespace = 'http://dublincore.org/documents/dcmi-terms/' - AND mfr.element = 'title' - AND mfr.qualifier IS NULL - AND mv.text_value LIKE 'ORIGINAL' - AND wfi.owner IS NOT NULL - AND (wfi.state = 2 OR wfi.state = 4 OR wfi.state = 6) - AND NOT EXISTS( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 0 AND action_id = 2 AND resourcepolicy.eperson_id = owner AND resourcepolicy.resource_id = b2b.bitstream_id - ); - -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, eperson_id, resource_id) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '0' AS resource_type_id, - '3' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - wfi.owner AS eperson_id, - b2b.bitstream_id AS dspace_object - FROM workflowitem wfi - JOIN item2bundle i2b - ON i2b.item_id = wfi.item_id - JOIN bundle2bitstream b2b - ON b2b.bundle_id = i2b.bundle_id - JOIN metadatavalue mv - ON mv.resource_id = i2b.bundle_id - JOIN metadatafieldregistry mfr - ON mv.metadata_field_id = mfr.metadata_field_id - JOIN metadataschemaregistry msr - ON mfr.metadata_schema_id = msr.metadata_schema_id - WHERE - msr.namespace = 'http://dublincore.org/documents/dcmi-terms/' - AND mfr.element = 'title' - AND mfr.qualifier IS NULL - AND mv.text_value LIKE 'ORIGINAL' - AND wfi.owner IS NOT NULL - AND (wfi.state = 2 OR wfi.state = 4 OR wfi.state = 6) - AND NOT EXISTS( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 0 AND action_id = 3 AND resourcepolicy.eperson_id = owner AND resourcepolicy.resource_id = b2b.bitstream_id - ); - -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, eperson_id, resource_id) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '0' AS resource_type_id, - '4' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - wfi.owner AS eperson_id, - b2b.bitstream_id AS dspace_object - FROM workflowitem wfi - JOIN item2bundle i2b - ON i2b.item_id = wfi.item_id - JOIN bundle2bitstream b2b - ON b2b.bundle_id = i2b.bundle_id - JOIN metadatavalue mv - ON mv.resource_id = i2b.bundle_id - JOIN metadatafieldregistry mfr - ON mv.metadata_field_id = mfr.metadata_field_id - JOIN metadataschemaregistry msr - ON mfr.metadata_schema_id = msr.metadata_schema_id - WHERE - msr.namespace = 'http://dublincore.org/documents/dcmi-terms/' - AND mfr.element = 'title' - AND mfr.qualifier IS NULL - AND mv.text_value LIKE 'ORIGINAL' - AND wfi.owner IS NOT NULL - AND (wfi.state = 2 OR wfi.state = 4 OR wfi.state = 6) - AND NOT EXISTS( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 0 AND action_id = 4 AND resourcepolicy.eperson_id = owner AND resourcepolicy.resource_id = b2b.bitstream_id - ); diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/workflow/oracle/basicWorkflow/V6.0_2015.08.11__DS-2701_Basic_Workflow_Migration.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/workflow/oracle/basicWorkflow/V6.0_2015.08.11__DS-2701_Basic_Workflow_Migration.sql deleted file mode 100644 index 917078594c..0000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/workflow/oracle/basicWorkflow/V6.0_2015.08.11__DS-2701_Basic_Workflow_Migration.sql +++ /dev/null @@ -1,37 +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/ --- - ------------------------------------------------------- --- DS-2701 Service based API / Hibernate integration ------------------------------------------------------- --- Alter workflow item -ALTER TABLE workflowitem RENAME COLUMN item_id to item_legacy_id; -ALTER TABLE workflowitem ADD item_id RAW(16) REFERENCES Item(uuid); -UPDATE workflowitem SET item_id = (SELECT item.uuid FROM item WHERE workflowitem.item_legacy_id = item.item_id); -ALTER TABLE workflowitem DROP COLUMN item_legacy_id; - --- Migrate task list item -ALTER TABLE TasklistItem RENAME COLUMN eperson_id to eperson_legacy_id; -ALTER TABLE TasklistItem ADD eperson_id RAW(16) REFERENCES EPerson(uuid); -UPDATE TasklistItem SET eperson_id = (SELECT eperson.uuid FROM eperson WHERE TasklistItem.eperson_legacy_id = eperson.eperson_id); -ALTER TABLE TasklistItem DROP COLUMN eperson_legacy_id; - --- Migrate task workflow item -ALTER TABLE workflowitem RENAME COLUMN collection_id to collection_legacy_id; -ALTER TABLE workflowitem ADD collection_id RAW(16) REFERENCES Collection(uuid); -UPDATE workflowitem SET collection_id = (SELECT collection.uuid FROM collection WHERE workflowitem.collection_legacy_id = collection.collection_id); -ALTER TABLE workflowitem DROP COLUMN collection_legacy_id; -ALTER TABLE workflowitem RENAME COLUMN owner to owner_legacy_id; -ALTER TABLE workflowitem ADD owner RAW(16) REFERENCES EPerson (uuid); -UPDATE workflowitem SET owner = (SELECT eperson.uuid FROM eperson WHERE workflowitem.owner_legacy_id = eperson.eperson_id); -ALTER TABLE workflowitem DROP COLUMN owner_legacy_id; -UPDATE workflowitem SET state = -1 WHERE state IS NULL; -UPDATE workflowitem SET multiple_titles = '0' WHERE multiple_titles IS NULL; -UPDATE workflowitem SET published_before = '0' WHERE published_before IS NULL; -UPDATE workflowitem SET multiple_files = '0' WHERE multiple_files IS NULL; - diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/workflow/oracle/basicWorkflow/V6.1_2017.01.03__DS-3431.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/workflow/oracle/basicWorkflow/V6.1_2017.01.03__DS-3431.sql deleted file mode 100644 index b3887a5af4..0000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/workflow/oracle/basicWorkflow/V6.1_2017.01.03__DS-3431.sql +++ /dev/null @@ -1,503 +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/ --- - -------------------------------------------------------------------------- --- DS-3431 Workflow system is vulnerable to unauthorized manipulations -- -------------------------------------------------------------------------- - ------------------------------------------------------------------------ --- grant claiming permissions to all workflow step groups (step 1-3) -- ------------------------------------------------------------------------ -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, epersongroup_id, dspace_object) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '3' AS resource_type_id, - '5' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - workflow_step_1 AS epersongroup_id, - uuid AS dspace_object - FROM collection - WHERE workflow_step_1 IS NOT NULL - AND NOT EXISTS ( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 3 AND action_id = 5 AND epersongroup_id = workflow_step_1 and dspace_object = uuid - ); - -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, epersongroup_id, dspace_object) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '3' AS resource_type_id, - '6' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - workflow_step_2 AS epersongroup_id, - uuid AS dspace_object - FROM collection - WHERE workflow_step_2 IS NOT NULL - AND NOT EXISTS ( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 3 AND action_id = 6 AND epersongroup_id = workflow_step_2 and dspace_object = uuid - ); - -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, epersongroup_id, dspace_object) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '3' AS resource_type_id, - '7' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - workflow_step_3 AS epersongroup_id, - uuid AS dspace_object - FROM collection - WHERE workflow_step_3 IS NOT NULL - AND NOT EXISTS ( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 3 AND action_id = 7 AND epersongroup_id = workflow_step_3 and dspace_object = uuid - ); - ------------------------------------------------------------------------ --- grant add permissions to all workflow step groups (step 1-3) -- ------------------------------------------------------------------------ -INSERT INTO resourcepolicy -(policy_id, resource_type_id, action_id, rptype, epersongroup_id, dspace_object) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '3' AS resource_type_id, - '3' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - workflow_step_1 AS epersongroup_id, - uuid AS dspace_object - FROM collection - WHERE workflow_step_1 IS NOT NULL - AND NOT EXISTS ( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 3 AND action_id = 3 AND epersongroup_id = workflow_step_1 and dspace_object = uuid - ); - -INSERT INTO resourcepolicy -(policy_id, resource_type_id, action_id, rptype, epersongroup_id, dspace_object) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '3' AS resource_type_id, - '3' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - workflow_step_2 AS epersongroup_id, - uuid AS dspace_object - FROM collection - WHERE workflow_step_2 IS NOT NULL - AND NOT EXISTS ( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 3 AND action_id = 3 AND epersongroup_id = workflow_step_2 and dspace_object = uuid - ); - -INSERT INTO resourcepolicy -(policy_id, resource_type_id, action_id, rptype, epersongroup_id, dspace_object) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '3' AS resource_type_id, - '3' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - workflow_step_3 AS epersongroup_id, - uuid AS dspace_object - FROM collection - WHERE workflow_step_3 IS NOT NULL - AND NOT EXISTS ( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 3 AND action_id = 3 AND epersongroup_id = workflow_step_3 and dspace_object = uuid - ); - ----------------------------------------------------------------------------------- --- grant read/write/delete/add/remove permission on workflow items to reviewers -- ----------------------------------------------------------------------------------- -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, eperson_id, dspace_object) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '2' AS resource_type_id, - '0' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - owner AS eperson_id, - item_id AS dspace_object - FROM workflowitem - WHERE - owner IS NOT NULL - AND (state = 2 OR state = 4 OR state = 6) - AND NOT EXISTS ( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 2 AND action_id = 0 AND eperson_id = owner AND dspace_object = item_id - ); - -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, eperson_id, dspace_object) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '2' AS resource_type_id, - '1' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - owner AS eperson_id, - item_id AS dspace_object - FROM workflowitem - WHERE - owner IS NOT NULL - AND (state = 2 OR state = 4 OR state = 6) - AND NOT EXISTS ( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 2 AND action_id = 1 AND eperson_id = owner AND dspace_object = item_id - ); - -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, eperson_id, dspace_object) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '2' AS resource_type_id, - '2' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - owner AS eperson_id, - item_id AS dspace_object - FROM workflowitem - WHERE - owner IS NOT NULL - AND (state = 2 OR state = 4 OR state = 6) - AND NOT EXISTS ( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 2 AND action_id = 2 AND eperson_id = owner AND dspace_object = item_id - ); - -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, eperson_id, dspace_object) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '2' AS resource_type_id, - '3' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - owner AS eperson_id, - item_id AS dspace_object - FROM workflowitem - WHERE - owner IS NOT NULL - AND (state = 2 OR state = 4 OR state = 6) - AND NOT EXISTS ( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 2 AND action_id = 3 AND eperson_id = owner AND dspace_object = item_id - ); - -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, eperson_id, dspace_object) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '2' AS resource_type_id, - '4' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - owner AS eperson_id, - item_id AS dspace_object - FROM workflowitem - WHERE - owner IS NOT NULL - AND (state = 2 OR state = 4 OR state = 6) - AND NOT EXISTS ( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 2 AND action_id = 4 AND eperson_id = owner AND dspace_object = item_id - ); - ------------------------------------------------------------------------------------ --- grant read/write/delete/add/remove permission on Bundle ORIGINAL to reviewers -- ------------------------------------------------------------------------------------ -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, eperson_id, dspace_object) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '1' AS resource_type_id, - '0' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - wfi.owner AS eperson_id, - i2b.bundle_id AS dspace_object - FROM workflowitem wfi - JOIN item2bundle i2b - ON i2b.item_id = wfi.item_id - JOIN metadatavalue mv - ON mv.dspace_object_id = i2b.bundle_id - JOIN metadatafieldregistry mfr - ON mv.metadata_field_id = mfr.metadata_field_id - JOIN metadataschemaregistry msr - ON mfr.metadata_schema_id = msr.metadata_schema_id - WHERE - msr.namespace = 'http://dublincore.org/documents/dcmi-terms/' - AND mfr.element = 'title' - AND mfr.qualifier IS NULL - AND mv.text_value LIKE 'ORIGINAL' - AND wfi.owner IS NOT NULL - AND (wfi.state = 2 OR wfi.state = 4 OR wfi.state = 6) - AND NOT EXISTS( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 1 AND action_id = 0 AND resourcepolicy.eperson_id = owner AND resourcepolicy.dspace_object = i2b.bundle_id - ); - -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, eperson_id, dspace_object) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '1' AS resource_type_id, - '1' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - wfi.owner AS eperson_id, - i2b.bundle_id AS dspace_object - FROM workflowitem wfi - JOIN item2bundle i2b - ON i2b.item_id = wfi.item_id - JOIN metadatavalue mv - ON mv.dspace_object_id = i2b.bundle_id - JOIN metadatafieldregistry mfr - ON mv.metadata_field_id = mfr.metadata_field_id - JOIN metadataschemaregistry msr - ON mfr.metadata_schema_id = msr.metadata_schema_id - WHERE - msr.namespace = 'http://dublincore.org/documents/dcmi-terms/' - AND mfr.element = 'title' - AND mfr.qualifier IS NULL - AND mv.text_value LIKE 'ORIGINAL' - AND wfi.owner IS NOT NULL - AND (wfi.state = 2 OR wfi.state = 4 OR wfi.state = 6) - AND NOT EXISTS( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 1 AND action_id = 1 AND resourcepolicy.eperson_id = owner AND resourcepolicy.dspace_object = i2b.bundle_id - ); - -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, eperson_id, dspace_object) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '1' AS resource_type_id, - '2' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - wfi.owner AS eperson_id, - i2b.bundle_id AS dspace_object - FROM workflowitem wfi - JOIN item2bundle i2b - ON i2b.item_id = wfi.item_id - JOIN metadatavalue mv - ON mv.dspace_object_id = i2b.bundle_id - JOIN metadatafieldregistry mfr - ON mv.metadata_field_id = mfr.metadata_field_id - JOIN metadataschemaregistry msr - ON mfr.metadata_schema_id = msr.metadata_schema_id - WHERE - msr.namespace = 'http://dublincore.org/documents/dcmi-terms/' - AND mfr.element = 'title' - AND mfr.qualifier IS NULL - AND mv.text_value LIKE 'ORIGINAL' - AND wfi.owner IS NOT NULL - AND (wfi.state = 2 OR wfi.state = 4 OR wfi.state = 6) - AND NOT EXISTS( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 1 AND action_id = 2 AND resourcepolicy.eperson_id = owner AND resourcepolicy.dspace_object = i2b.bundle_id - ); - -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, eperson_id, dspace_object) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '1' AS resource_type_id, - '3' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - wfi.owner AS eperson_id, - i2b.bundle_id AS dspace_object - FROM workflowitem wfi - JOIN item2bundle i2b - ON i2b.item_id = wfi.item_id - JOIN metadatavalue mv - ON mv.dspace_object_id = i2b.bundle_id - JOIN metadatafieldregistry mfr - ON mv.metadata_field_id = mfr.metadata_field_id - JOIN metadataschemaregistry msr - ON mfr.metadata_schema_id = msr.metadata_schema_id - WHERE - msr.namespace = 'http://dublincore.org/documents/dcmi-terms/' - AND mfr.element = 'title' - AND mfr.qualifier IS NULL - AND mv.text_value LIKE 'ORIGINAL' - AND wfi.owner IS NOT NULL - AND (wfi.state = 2 OR wfi.state = 4 OR wfi.state = 6) - AND NOT EXISTS( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 1 AND action_id = 3 AND resourcepolicy.eperson_id = owner AND resourcepolicy.dspace_object = i2b.bundle_id - ); - -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, eperson_id, dspace_object) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '1' AS resource_type_id, - '4' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - wfi.owner AS eperson_id, - i2b.bundle_id AS dspace_object - FROM workflowitem wfi - JOIN item2bundle i2b - ON i2b.item_id = wfi.item_id - JOIN metadatavalue mv - ON mv.dspace_object_id = i2b.bundle_id - JOIN metadatafieldregistry mfr - ON mv.metadata_field_id = mfr.metadata_field_id - JOIN metadataschemaregistry msr - ON mfr.metadata_schema_id = msr.metadata_schema_id - WHERE - msr.namespace = 'http://dublincore.org/documents/dcmi-terms/' - AND mfr.element = 'title' - AND mfr.qualifier IS NULL - AND mv.text_value LIKE 'ORIGINAL' - AND wfi.owner IS NOT NULL - AND (wfi.state = 2 OR wfi.state = 4 OR wfi.state = 6) - AND NOT EXISTS( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 1 AND action_id = 4 AND resourcepolicy.eperson_id = owner AND resourcepolicy.dspace_object = i2b.bundle_id - ); - - -------------------------------------------------------------------------------- --- grant read/write/delete/add/remove permission on all Bitstreams of Bundle -- --- ORIGINAL to reviewers -- -------------------------------------------------------------------------------- -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, eperson_id, dspace_object) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '0' AS resource_type_id, - '0' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - wfi.owner AS eperson_id, - b2b.bitstream_id AS dspace_object - FROM workflowitem wfi - JOIN item2bundle i2b - ON i2b.item_id = wfi.item_id - JOIN bundle2bitstream b2b - ON b2b.bundle_id = i2b.bundle_id - JOIN metadatavalue mv - ON mv.dspace_object_id = i2b.bundle_id - JOIN metadatafieldregistry mfr - ON mv.metadata_field_id = mfr.metadata_field_id - JOIN metadataschemaregistry msr - ON mfr.metadata_schema_id = msr.metadata_schema_id - WHERE - msr.namespace = 'http://dublincore.org/documents/dcmi-terms/' - AND mfr.element = 'title' - AND mfr.qualifier IS NULL - AND mv.text_value LIKE 'ORIGINAL' - AND wfi.owner IS NOT NULL - AND (wfi.state = 2 OR wfi.state = 4 OR wfi.state = 6) - AND NOT EXISTS( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 0 AND action_id = 0 AND resourcepolicy.eperson_id = owner AND resourcepolicy.dspace_object = b2b.bitstream_id - ); - -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, eperson_id, dspace_object) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '0' AS resource_type_id, - '1' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - wfi.owner AS eperson_id, - b2b.bitstream_id AS dspace_object - FROM workflowitem wfi - JOIN item2bundle i2b - ON i2b.item_id = wfi.item_id - JOIN bundle2bitstream b2b - ON b2b.bundle_id = i2b.bundle_id - JOIN metadatavalue mv - ON mv.dspace_object_id = i2b.bundle_id - JOIN metadatafieldregistry mfr - ON mv.metadata_field_id = mfr.metadata_field_id - JOIN metadataschemaregistry msr - ON mfr.metadata_schema_id = msr.metadata_schema_id - WHERE - msr.namespace = 'http://dublincore.org/documents/dcmi-terms/' - AND mfr.element = 'title' - AND mfr.qualifier IS NULL - AND mv.text_value LIKE 'ORIGINAL' - AND wfi.owner IS NOT NULL - AND (wfi.state = 2 OR wfi.state = 4 OR wfi.state = 6) - AND NOT EXISTS( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 0 AND action_id = 1 AND resourcepolicy.eperson_id = owner AND resourcepolicy.dspace_object = b2b.bitstream_id - ); - -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, eperson_id, dspace_object) - SELECT - resourcepolicy_seq.NEXTVAL policy_id, - '0' AS resource_type_id, - '2' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - wfi.owner AS eperson_id, - b2b.bitstream_id AS dspace_object - FROM workflowitem wfi - JOIN item2bundle i2b - ON i2b.item_id = wfi.item_id - JOIN bundle2bitstream b2b - ON b2b.bundle_id = i2b.bundle_id - JOIN metadatavalue mv - ON mv.dspace_object_id = i2b.bundle_id - JOIN metadatafieldregistry mfr - ON mv.metadata_field_id = mfr.metadata_field_id - JOIN metadataschemaregistry msr - ON mfr.metadata_schema_id = msr.metadata_schema_id - WHERE - msr.namespace = 'http://dublincore.org/documents/dcmi-terms/' - AND mfr.element = 'title' - AND mfr.qualifier IS NULL - AND mv.text_value LIKE 'ORIGINAL' - AND wfi.owner IS NOT NULL - AND (wfi.state = 2 OR wfi.state = 4 OR wfi.state = 6) - AND NOT EXISTS( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 0 AND action_id = 2 AND resourcepolicy.eperson_id = owner AND resourcepolicy.dspace_object = b2b.bitstream_id - ); - -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, eperson_id, dspace_object) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '0' AS resource_type_id, - '3' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - wfi.owner AS eperson_id, - b2b.bitstream_id AS dspace_object - FROM workflowitem wfi - JOIN item2bundle i2b - ON i2b.item_id = wfi.item_id - JOIN bundle2bitstream b2b - ON b2b.bundle_id = i2b.bundle_id - JOIN metadatavalue mv - ON mv.dspace_object_id = i2b.bundle_id - JOIN metadatafieldregistry mfr - ON mv.metadata_field_id = mfr.metadata_field_id - JOIN metadataschemaregistry msr - ON mfr.metadata_schema_id = msr.metadata_schema_id - WHERE - msr.namespace = 'http://dublincore.org/documents/dcmi-terms/' - AND mfr.element = 'title' - AND mfr.qualifier IS NULL - AND mv.text_value LIKE 'ORIGINAL' - AND wfi.owner IS NOT NULL - AND (wfi.state = 2 OR wfi.state = 4 OR wfi.state = 6) - AND NOT EXISTS( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 0 AND action_id = 3 AND resourcepolicy.eperson_id = owner AND resourcepolicy.dspace_object = b2b.bitstream_id - ); - -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, eperson_id, dspace_object) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '0' AS resource_type_id, - '4' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - wfi.owner AS eperson_id, - b2b.bitstream_id AS dspace_object - FROM workflowitem wfi - JOIN item2bundle i2b - ON i2b.item_id = wfi.item_id - JOIN bundle2bitstream b2b - ON b2b.bundle_id = i2b.bundle_id - JOIN metadatavalue mv - ON mv.dspace_object_id = i2b.bundle_id - JOIN metadatafieldregistry mfr - ON mv.metadata_field_id = mfr.metadata_field_id - JOIN metadataschemaregistry msr - ON mfr.metadata_schema_id = msr.metadata_schema_id - WHERE - msr.namespace = 'http://dublincore.org/documents/dcmi-terms/' - AND mfr.element = 'title' - AND mfr.qualifier IS NULL - AND mv.text_value LIKE 'ORIGINAL' - AND wfi.owner IS NOT NULL - AND (wfi.state = 2 OR wfi.state = 4 OR wfi.state = 6) - AND NOT EXISTS( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 0 AND action_id = 4 AND resourcepolicy.eperson_id = owner AND resourcepolicy.dspace_object = b2b.bitstream_id - ); diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/workflow/oracle/xmlworkflow/V6.0_2015.08.11__DS-2701_Xml_Workflow_Migration.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/workflow/oracle/xmlworkflow/V6.0_2015.08.11__DS-2701_Xml_Workflow_Migration.sql deleted file mode 100644 index 7a992836ee..0000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/workflow/oracle/xmlworkflow/V6.0_2015.08.11__DS-2701_Xml_Workflow_Migration.sql +++ /dev/null @@ -1,141 +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/ --- - ------------------------------------------------------- --- DS-2701 Service based API / Hibernate integration ------------------------------------------------------- -UPDATE collection SET workflow_step_1 = null; -UPDATE collection SET workflow_step_2 = null; -UPDATE collection SET workflow_step_3 = null; - --- cwf_workflowitem - -DROP INDEX cwf_workflowitem_coll_fk_idx; - -ALTER TABLE cwf_workflowitem RENAME COLUMN item_id to item_legacy_id; -ALTER TABLE cwf_workflowitem ADD item_id RAW(16) REFERENCES Item(uuid); -UPDATE cwf_workflowitem SET item_id = (SELECT item.uuid FROM item WHERE cwf_workflowitem.item_legacy_id = item.item_id); -ALTER TABLE cwf_workflowitem DROP COLUMN item_legacy_id; - -ALTER TABLE cwf_workflowitem RENAME COLUMN collection_id to collection_legacy_id; -ALTER TABLE cwf_workflowitem ADD collection_id RAW(16) REFERENCES Collection(uuid); -UPDATE cwf_workflowitem SET collection_id = (SELECT collection.uuid FROM collection WHERE cwf_workflowitem.collection_legacy_id = collection.collection_id); -ALTER TABLE cwf_workflowitem DROP COLUMN collection_legacy_id; - -UPDATE cwf_workflowitem SET multiple_titles = '0' WHERE multiple_titles IS NULL; -UPDATE cwf_workflowitem SET published_before = '0' WHERE published_before IS NULL; -UPDATE cwf_workflowitem SET multiple_files = '0' WHERE multiple_files IS NULL; - -CREATE INDEX cwf_workflowitem_coll_fk_idx ON cwf_workflowitem(collection_id); - --- cwf_collectionrole - -ALTER TABLE cwf_collectionrole DROP CONSTRAINT cwf_collectionrole_unique; -DROP INDEX cwf_cr_coll_role_fk_idx; -DROP INDEX cwf_cr_coll_fk_idx; - -ALTER TABLE cwf_collectionrole RENAME COLUMN collection_id to collection_legacy_id; -ALTER TABLE cwf_collectionrole ADD collection_id RAW(16) REFERENCES Collection(uuid); -UPDATE cwf_collectionrole SET collection_id = (SELECT collection.uuid FROM collection WHERE cwf_collectionrole.collection_legacy_id = collection.collection_id); -ALTER TABLE cwf_collectionrole DROP COLUMN collection_legacy_id; - -ALTER TABLE cwf_collectionrole RENAME COLUMN group_id to group_legacy_id; -ALTER TABLE cwf_collectionrole ADD group_id RAW(16) REFERENCES epersongroup(uuid); -UPDATE cwf_collectionrole SET group_id = (SELECT epersongroup.uuid FROM epersongroup WHERE cwf_collectionrole.group_legacy_id = epersongroup.eperson_group_id); -ALTER TABLE cwf_collectionrole DROP COLUMN group_legacy_id; - -ALTER TABLE cwf_collectionrole -ADD CONSTRAINT cwf_collectionrole_unique UNIQUE (role_id, collection_id, group_id); - -CREATE INDEX cwf_cr_coll_role_fk_idx ON cwf_collectionrole(collection_id,role_id); -CREATE INDEX cwf_cr_coll_fk_idx ON cwf_collectionrole(collection_id); - - --- cwf_workflowitemrole - -ALTER TABLE cwf_workflowitemrole DROP CONSTRAINT cwf_workflowitemrole_unique; -DROP INDEX cwf_wfir_item_role_fk_idx; -DROP INDEX cwf_wfir_item_fk_idx; - -ALTER TABLE cwf_workflowitemrole RENAME COLUMN group_id to group_legacy_id; -ALTER TABLE cwf_workflowitemrole ADD group_id RAW(16) REFERENCES epersongroup(uuid); -UPDATE cwf_workflowitemrole SET group_id = (SELECT epersongroup.uuid FROM epersongroup WHERE cwf_workflowitemrole.group_legacy_id = epersongroup.eperson_group_id); -ALTER TABLE cwf_workflowitemrole DROP COLUMN group_legacy_id; - -ALTER TABLE cwf_workflowitemrole RENAME COLUMN eperson_id to eperson_legacy_id; -ALTER TABLE cwf_workflowitemrole ADD eperson_id RAW(16) REFERENCES eperson(uuid); -UPDATE cwf_workflowitemrole SET eperson_id = (SELECT eperson.uuid FROM eperson WHERE cwf_workflowitemrole.eperson_legacy_id = eperson.eperson_id); -ALTER TABLE cwf_workflowitemrole DROP COLUMN eperson_legacy_id; - - -ALTER TABLE cwf_workflowitemrole -ADD CONSTRAINT cwf_workflowitemrole_unique UNIQUE (role_id, workflowitem_id, eperson_id, group_id); - -CREATE INDEX cwf_wfir_item_role_fk_idx ON cwf_workflowitemrole(workflowitem_id,role_id); -CREATE INDEX cwf_wfir_item_fk_idx ON cwf_workflowitemrole(workflowitem_id); - --- cwf_pooltask - -DROP INDEX cwf_pt_eperson_fk_idx; -DROP INDEX cwf_pt_workflow_eperson_fk_idx; - -ALTER TABLE cwf_pooltask RENAME COLUMN group_id to group_legacy_id; -ALTER TABLE cwf_pooltask ADD group_id RAW(16) REFERENCES epersongroup(uuid); -UPDATE cwf_pooltask SET group_id = (SELECT epersongroup.uuid FROM epersongroup WHERE cwf_pooltask.group_legacy_id = epersongroup.eperson_group_id); -ALTER TABLE cwf_pooltask DROP COLUMN group_legacy_id; - -ALTER TABLE cwf_pooltask RENAME COLUMN eperson_id to eperson_legacy_id; -ALTER TABLE cwf_pooltask ADD eperson_id RAW(16) REFERENCES eperson(uuid); -UPDATE cwf_pooltask SET eperson_id = (SELECT eperson.uuid FROM eperson WHERE cwf_pooltask.eperson_legacy_id = eperson.eperson_id); -ALTER TABLE cwf_pooltask DROP COLUMN eperson_legacy_id; - -CREATE INDEX cwf_pt_eperson_fk_idx ON cwf_pooltask(eperson_id); -CREATE INDEX cwf_pt_workflow_eperson_fk_idx ON cwf_pooltask(eperson_id,workflowitem_id); - --- cwf_claimtask - -ALTER TABLE cwf_claimtask DROP CONSTRAINT cwf_claimtask_unique; -DROP INDEX cwf_ct_workflow_fk_idx; -DROP INDEX cwf_ct_workflow_eperson_fk_idx; -DROP INDEX cwf_ct_eperson_fk_idx; -DROP INDEX cwf_ct_wfs_fk_idx; -DROP INDEX cwf_ct_wfs_action_fk_idx; -DROP INDEX cwf_ct_wfs_action_e_fk_idx; - -ALTER TABLE cwf_claimtask RENAME COLUMN owner_id to eperson_legacy_id; -ALTER TABLE cwf_claimtask ADD owner_id RAW(16) REFERENCES eperson(uuid); -UPDATE cwf_claimtask SET owner_id = (SELECT eperson.uuid FROM eperson WHERE cwf_claimtask.eperson_legacy_id = eperson.eperson_id); -ALTER TABLE cwf_claimtask DROP COLUMN eperson_legacy_id; - -ALTER TABLE cwf_claimtask -ADD CONSTRAINT cwf_claimtask_unique UNIQUE (step_id, workflowitem_id, workflow_id, owner_id, action_id); - -CREATE INDEX cwf_ct_workflow_fk_idx ON cwf_claimtask(workflowitem_id); -CREATE INDEX cwf_ct_workflow_eperson_fk_idx ON cwf_claimtask(workflowitem_id,owner_id); -CREATE INDEX cwf_ct_eperson_fk_idx ON cwf_claimtask(owner_id); -CREATE INDEX cwf_ct_wfs_fk_idx ON cwf_claimtask(workflowitem_id,step_id); -CREATE INDEX cwf_ct_wfs_action_fk_idx ON cwf_claimtask(workflowitem_id,step_id,action_id); -CREATE INDEX cwf_ct_wfs_action_e_fk_idx ON cwf_claimtask(workflowitem_id,step_id,action_id,owner_id); - --- cwf_in_progress_user - -ALTER TABLE cwf_in_progress_user DROP CONSTRAINT cwf_in_progress_user_unique; -DROP INDEX cwf_ipu_workflow_fk_idx; -DROP INDEX cwf_ipu_eperson_fk_idx; - -ALTER TABLE cwf_in_progress_user RENAME COLUMN user_id to eperson_legacy_id; -ALTER TABLE cwf_in_progress_user ADD user_id RAW(16) REFERENCES eperson(uuid); -UPDATE cwf_in_progress_user SET user_id = (SELECT eperson.uuid FROM eperson WHERE cwf_in_progress_user.eperson_legacy_id = eperson.eperson_id); -ALTER TABLE cwf_in_progress_user DROP COLUMN eperson_legacy_id; -UPDATE cwf_in_progress_user SET finished = '0' WHERE finished IS NULL; - -ALTER TABLE cwf_in_progress_user -ADD CONSTRAINT cwf_in_progress_user_unique UNIQUE (workflowitem_id, user_id); - -CREATE INDEX cwf_ipu_workflow_fk_idx ON cwf_in_progress_user(workflowitem_id); -CREATE INDEX cwf_ipu_eperson_fk_idx ON cwf_in_progress_user(user_id); \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/workflow/oracle/xmlworkflow/V7.0_2018.04.03__upgrade_workflow_policy.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/workflow/oracle/xmlworkflow/V7.0_2018.04.03__upgrade_workflow_policy.sql deleted file mode 100644 index 0402fc9948..0000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/workflow/oracle/xmlworkflow/V7.0_2018.04.03__upgrade_workflow_policy.sql +++ /dev/null @@ -1,27 +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/ --- - --- UPDATE policies for claimtasks --- Item -UPDATE RESOURCEPOLICY SET rptype = 'TYPE_WORKFLOW' WHERE dspace_object in (SELECT cwf_workflowitem.item_id FROM cwf_workflowitem INNER JOIN cwf_claimtask ON cwf_workflowitem.workflowitem_id = cwf_claimtask.workflowitem_id JOIN item ON cwf_workflowitem.item_id = item.uuid) AND eperson_id not in (SELECT item.submitter_id FROM cwf_workflowitem JOIN item ON cwf_workflowitem.item_id = item.uuid); - --- Bundles -UPDATE RESOURCEPOLICY SET rptype = 'TYPE_WORKFLOW' WHERE dspace_object in (SELECT item2bundle.bundle_id FROM cwf_workflowitem INNER JOIN cwf_claimtask ON cwf_workflowitem.workflowitem_id = cwf_claimtask.workflowitem_id INNER JOIN item2bundle ON cwf_workflowitem.item_id = item2bundle.item_id) AND eperson_id not in (SELECT item.submitter_id FROM cwf_workflowitem JOIN item ON cwf_workflowitem.item_id = item.uuid); - --- Bitstreams -UPDATE RESOURCEPOLICY SET rptype = 'TYPE_WORKFLOW' WHERE dspace_object in (SELECT bundle2bitstream.bitstream_id FROM cwf_workflowitem INNER JOIN cwf_claimtask ON cwf_workflowitem.workflowitem_id = cwf_claimtask.workflowitem_id INNER JOIN item2bundle ON cwf_workflowitem.item_id = item2bundle.item_id INNER JOIN bundle2bitstream ON item2bundle.bundle_id = bundle2bitstream.bundle_id) AND eperson_id not in (SELECT item.submitter_id FROM cwf_workflowitem JOIN item ON cwf_workflowitem.item_id = item.uuid); - --- Create policies for pooled tasks --- Item -UPDATE RESOURCEPOLICY SET rptype = 'TYPE_WORKFLOW' WHERE dspace_object in (SELECT cwf_workflowitem.item_id FROM cwf_workflowitem INNER JOIN cwf_pooltask ON cwf_workflowitem.workflowitem_id = cwf_pooltask.workflowitem_id) AND eperson_id not in (SELECT item.submitter_id FROM cwf_workflowitem JOIN item ON cwf_workflowitem.item_id = item.uuid); - --- Bundles -UPDATE RESOURCEPOLICY SET rptype = 'TYPE_WORKFLOW' WHERE dspace_object in (SELECT cwf_workflowitem.item_id FROM cwf_workflowitem INNER JOIN cwf_pooltask ON cwf_workflowitem.workflowitem_id = cwf_pooltask.workflowitem_id INNER JOIN item2bundle ON cwf_workflowitem.item_id = item2bundle.item_id) AND eperson_id not in (SELECT item.submitter_id FROM cwf_workflowitem JOIN item ON cwf_workflowitem.item_id = item.uuid); - --- Bitstreams -UPDATE RESOURCEPOLICY SET rptype = 'TYPE_WORKFLOW' WHERE dspace_object in (SELECT cwf_workflowitem.item_id FROM cwf_workflowitem INNER JOIN cwf_pooltask ON cwf_workflowitem.workflowitem_id = cwf_pooltask.workflowitem_id INNER JOIN item2bundle ON cwf_workflowitem.item_id = item2bundle.item_id INNER JOIN bundle2bitstream ON item2bundle.bundle_id = bundle2bitstream.bundle_id) AND eperson_id not in (SELECT item.submitter_id FROM cwf_workflowitem JOIN item ON cwf_workflowitem.item_id = item.uuid); diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/xmlworkflow/oracle/data_workflow_migration.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/xmlworkflow/oracle/data_workflow_migration.sql deleted file mode 100644 index f582f37c69..0000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/xmlworkflow/oracle/data_workflow_migration.sql +++ /dev/null @@ -1,377 +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/ --- - ----------------------------------------------------- --- Data Migration for XML/Configurable Workflow --- --- This file will automatically migrate existing --- classic workflows to XML/Configurable workflows. --- NOTE however that the corresponding --- "xml_workflow_migration.sql" script must FIRST be --- called to create the appropriate database tables. --- --- This script is called automatically by the following --- Flyway Java migration class: --- org.dspace.storage.rdbms.migration.V5_0_2014_01_01__XMLWorkflow_Migration ----------------------------------------------------- - --- Convert workflow groups: --- TODO: is 'to_number' ok? do not forget to change role_id values - -INSERT INTO cwf_collectionrole (collectionrole_id, role_id, group_id, collection_id) -SELECT -cwf_collectionrole_seq.nextval as collectionrole_id, -'reviewer' AS role_id, -collection.workflow_step_1 AS group_id, -collection.collection_id AS collection_id -FROM collection -WHERE collection.workflow_step_1 IS NOT NULL; - -INSERT INTO cwf_collectionrole (collectionrole_id, role_id, group_id, collection_id) -SELECT -cwf_collectionrole_seq.nextval as collectionrole_id, -'editor' AS role_id, -collection.workflow_step_2 AS group_id, -collection.collection_id AS collection_id -FROM collection -WHERE collection.workflow_step_2 IS NOT NULL; - -INSERT INTO cwf_collectionrole (collectionrole_id, role_id, group_id, collection_id) -SELECT -cwf_collectionrole_seq.nextval as collectionrole_id, -'finaleditor' AS role_id, -collection.workflow_step_3 AS group_id, -collection.collection_id AS collection_id -FROM collection -WHERE collection.workflow_step_3 IS NOT NULL; - - --- Migrate workflow items -INSERT INTO cwf_workflowitem (workflowitem_id, item_id, collection_id, multiple_titles, published_before, multiple_files) -SELECT -workflow_id AS workflowitem_id, -item_id, -collection_id, -multiple_titles, -published_before, -multiple_files -FROM workflowitem; - - --- Migrate claimed tasks -INSERT INTO cwf_claimtask (claimtask_id,workflowitem_id, workflow_id, step_id, action_id, owner_id) -SELECT -cwf_claimtask_seq.nextval AS claimtask_id, -workflow_id AS workflowitem_id, -'default' AS workflow_id, -'reviewstep' AS step_id, -'reviewaction' AS action_id, -owner AS owner_id -FROM workflowitem WHERE owner IS NOT NULL AND state = 2; - -INSERT INTO cwf_claimtask (claimtask_id,workflowitem_id, workflow_id, step_id, action_id, owner_id) -SELECT -cwf_claimtask_seq.nextval AS claimtask_id, -workflow_id AS workflowitem_id, -'default' AS workflow_id, -'editstep' AS step_id, -'editaction' AS action_id, -owner AS owner_id -FROM workflowitem WHERE owner IS NOT NULL AND state = 4; - -INSERT INTO cwf_claimtask (claimtask_id,workflowitem_id, workflow_id, step_id, action_id, owner_id) -SELECT -cwf_claimtask_seq.nextval AS claimtask_id, -workflow_id AS workflowitem_id, -'default' AS workflow_id, -'finaleditstep' AS step_id, -'finaleditaction' AS action_id, -owner AS owner_id -FROM workflowitem WHERE owner IS NOT NULL AND state = 6; - - --- Migrate pooled tasks -INSERT INTO cwf_pooltask (pooltask_id,workflowitem_id, workflow_id, step_id, action_id, group_id) -SELECT -cwf_pooltask_seq.nextval AS pooltask_id, -workflowitem.workflow_id AS workflowitem_id, -'default' AS workflow_id, -'reviewstep' AS step_id, -'claimaction' AS action_id, -cwf_collectionrole.group_id AS group_id -FROM workflowitem INNER JOIN cwf_collectionrole ON workflowitem.collection_id = cwf_collectionrole.collection_id -WHERE workflowitem.owner IS NULL AND workflowitem.state = 1 AND cwf_collectionrole.role_id = 'reviewer'; - -INSERT INTO cwf_pooltask (pooltask_id,workflowitem_id, workflow_id, step_id, action_id, group_id) -SELECT -cwf_pooltask_seq.nextval AS pooltask_id, -workflowitem.workflow_id AS workflowitem_id, -'default' AS workflow_id, -'editstep' AS step_id, -'claimaction' AS action_id, -cwf_collectionrole.group_id AS group_id -FROM workflowitem INNER JOIN cwf_collectionrole ON workflowitem.collection_id = cwf_collectionrole.collection_id -WHERE workflowitem.owner IS NULL AND workflowitem.state = 3 AND cwf_collectionrole.role_id = 'editor'; - -INSERT INTO cwf_pooltask (pooltask_id,workflowitem_id, workflow_id, step_id, action_id, group_id) -SELECT -cwf_pooltask_seq.nextval AS pooltask_id, -workflowitem.workflow_id AS workflowitem_id, -'default' AS workflow_id, -'finaleditstep' AS step_id, -'claimaction' AS action_id, -cwf_collectionrole.group_id AS group_id -FROM workflowitem INNER JOIN cwf_collectionrole ON workflowitem.collection_id = cwf_collectionrole.collection_id -WHERE workflowitem.owner IS NULL AND workflowitem.state = 5 AND cwf_collectionrole.role_id = 'finaleditor'; - --- Delete resource policies for workflowitems before creating new ones -DELETE FROM resourcepolicy -WHERE resource_type_id = 2 AND resource_id IN - (SELECT item_id FROM workflowitem); - -DELETE FROM resourcepolicy -WHERE resource_type_id = 1 AND resource_id IN - (SELECT item2bundle.bundle_id FROM - (workflowitem INNER JOIN item2bundle ON workflowitem.item_id = item2bundle.item_id)); - -DELETE FROM resourcepolicy -WHERE resource_type_id = 0 AND resource_id IN - (SELECT bundle2bitstream.bitstream_id FROM - ((workflowitem INNER JOIN item2bundle ON workflowitem.item_id = item2bundle.item_id) - INNER JOIN bundle2bitstream ON item2bundle.bundle_id = bundle2bitstream.bundle_id)); --- Create policies for claimtasks --- public static final int BITSTREAM = 0; --- public static final int BUNDLE = 1; --- public static final int ITEM = 2; - --- public static final int READ = 0; --- public static final int WRITE = 1; --- public static final int DELETE = 2; --- public static final int ADD = 3; --- public static final int REMOVE = 4; --- Item --- TODO: getnextID == SELECT sequence.nextval FROM DUAL!! --- Create a temporarty table with action ID's -CREATE TABLE temptable( - action_id INTEGER PRIMARY KEY -); -INSERT ALL - INTO temptable (action_id) VALUES (0) - INTO temptable (action_id) VALUES (1) - INTO temptable (action_id) VALUES (2) - INTO temptable (action_id) VALUES (3) - INTO temptable (action_id) VALUES (4) -SELECT * FROM DUAL; - -INSERT INTO resourcepolicy (policy_id, resource_type_id, resource_id, action_id, eperson_id) -SELECT -resourcepolicy_seq.nextval AS policy_id, -2 AS resource_type_id, -cwf_workflowitem.item_id AS resource_id, -temptable.action_id AS action_id, -cwf_claimtask.owner_id AS eperson_id -FROM (cwf_workflowitem INNER JOIN cwf_claimtask ON cwf_workflowitem.workflowitem_id = cwf_claimtask.workflowitem_id), -temptable; - --- Bundles -INSERT INTO resourcepolicy (policy_id, resource_type_id, resource_id, action_id, eperson_id) -SELECT -resourcepolicy_seq.nextval AS policy_id, -1 AS resource_type_id, -item2bundle.bundle_id AS resource_id, -temptable.action_id AS action_id, -cwf_claimtask.owner_id AS eperson_id -FROM -( - (cwf_workflowitem INNER JOIN cwf_claimtask ON cwf_workflowitem.workflowitem_id = cwf_claimtask.workflowitem_id) - INNER JOIN item2bundle ON cwf_workflowitem.item_id = item2bundle.item_id -), temptable; - - --- Bitstreams -INSERT INTO resourcepolicy (policy_id, resource_type_id, resource_id, action_id, eperson_id) -SELECT -resourcepolicy_seq.nextval AS policy_id, -0 AS resource_type_id, -bundle2bitstream.bitstream_id AS resource_id, -temptable.action_id AS action_id, -cwf_claimtask.owner_id AS eperson_id -FROM -( - ((cwf_workflowitem INNER JOIN cwf_claimtask ON cwf_workflowitem.workflowitem_id = cwf_claimtask.workflowitem_id) - INNER JOIN item2bundle ON cwf_workflowitem.item_id = item2bundle.item_id) - INNER JOIN bundle2bitstream ON item2bundle.bundle_id = bundle2bitstream.bundle_id -), temptable; - - --- Create policies for pooled tasks - -INSERT INTO resourcepolicy (policy_id, resource_type_id, resource_id, action_id, epersongroup_id) -SELECT -resourcepolicy_seq.nextval AS policy_id, -2 AS resource_type_id, -cwf_workflowitem.item_id AS resource_id, -temptable.action_id AS action_id, -cwf_pooltask.group_id AS epersongroup_id -FROM (cwf_workflowitem INNER JOIN cwf_pooltask ON cwf_workflowitem.workflowitem_id = cwf_pooltask.workflowitem_id), -temptable; - --- Bundles -INSERT INTO resourcepolicy (policy_id, resource_type_id, resource_id, action_id, epersongroup_id) -SELECT -resourcepolicy_seq.nextval AS policy_id, -1 AS resource_type_id, -item2bundle.bundle_id AS resource_id, -temptable.action_id AS action_id, -cwf_pooltask.group_id AS epersongroup_id -FROM -( - (cwf_workflowitem INNER JOIN cwf_pooltask ON cwf_workflowitem.workflowitem_id = cwf_pooltask.workflowitem_id) - INNER JOIN item2bundle ON cwf_workflowitem.item_id = item2bundle.item_id -), temptable; - --- Bitstreams -INSERT INTO resourcepolicy (policy_id, resource_type_id, resource_id, action_id, epersongroup_id) -SELECT -resourcepolicy_seq.nextval AS policy_id, -0 AS resource_type_id, -bundle2bitstream.bitstream_id AS resource_id, -temptable.action_id AS action_id, -cwf_pooltask.group_id AS epersongroup_id -FROM -( - ((cwf_workflowitem INNER JOIN cwf_pooltask ON cwf_workflowitem.workflowitem_id = cwf_pooltask.workflowitem_id) - INNER JOIN item2bundle ON cwf_workflowitem.item_id = item2bundle.item_id) - INNER JOIN bundle2bitstream ON item2bundle.bundle_id = bundle2bitstream.bundle_id -), temptable; - --- Drop the temporary table with the action ID's -DROP TABLE temptable; - --- Create policies for submitter --- TODO: only add if unique -INSERT INTO resourcepolicy (policy_id, resource_type_id, resource_id, action_id, eperson_id) -SELECT -resourcepolicy_seq.nextval AS policy_id, -2 AS resource_type_id, -cwf_workflowitem.item_id AS resource_id, -0 AS action_id, -item.submitter_id AS eperson_id -FROM (cwf_workflowitem INNER JOIN item ON cwf_workflowitem.item_id = item.item_id); - -INSERT INTO resourcepolicy (policy_id, resource_type_id, resource_id, action_id, eperson_id) -SELECT -resourcepolicy_seq.nextval AS policy_id, -1 AS resource_type_id, -item2bundle.bundle_id AS resource_id, -0 AS action_id, -item.submitter_id AS eperson_id -FROM ((cwf_workflowitem INNER JOIN item ON cwf_workflowitem.item_id = item.item_id) - INNER JOIN item2bundle ON cwf_workflowitem.item_id = item2bundle.item_id - ); - -INSERT INTO resourcepolicy (policy_id, resource_type_id, resource_id, action_id, eperson_id) -SELECT -resourcepolicy_seq.nextval AS policy_id, -0 AS resource_type_id, -bundle2bitstream.bitstream_id AS resource_id, -0 AS action_id, -item.submitter_id AS eperson_id -FROM (((cwf_workflowitem INNER JOIN item ON cwf_workflowitem.item_id = item.item_id) - INNER JOIN item2bundle ON cwf_workflowitem.item_id = item2bundle.item_id) - INNER JOIN bundle2bitstream ON item2bundle.bundle_id = bundle2bitstream.bundle_id -); - --- TODO: not tested yet -INSERT INTO cwf_in_progress_user (in_progress_user_id, workflowitem_id, user_id, finished) -SELECT - cwf_in_progress_user_seq.nextval AS in_progress_user_id, - cwf_workflowitem.workflowitem_id AS workflowitem_id, - cwf_claimtask.owner_id AS user_id, - 0 as finished -FROM - (cwf_claimtask INNER JOIN cwf_workflowitem ON cwf_workflowitem.workflowitem_id = cwf_claimtask.workflowitem_id); - --- TODO: improve this, important is NVL(curr, 1)!! without this function, empty tables (max = [null]) will only result in sequence deletion -DECLARE - curr NUMBER := 0; -BEGIN - SELECT max(workflowitem_id) INTO curr FROM cwf_workflowitem; - - curr := curr + 1; - - EXECUTE IMMEDIATE 'DROP SEQUENCE cwf_workflowitem_seq'; - - EXECUTE IMMEDIATE 'CREATE SEQUENCE cwf_workflowitem_seq START WITH ' || NVL(curr, 1); -END; -/ - -DECLARE - curr NUMBER := 0; -BEGIN - SELECT max(collectionrole_id) INTO curr FROM cwf_collectionrole; - - curr := curr + 1; - - EXECUTE IMMEDIATE 'DROP SEQUENCE cwf_collectionrole_seq'; - - EXECUTE IMMEDIATE 'CREATE SEQUENCE cwf_collectionrole_seq START WITH ' || NVL(curr, 1); -END; -/ - -DECLARE - curr NUMBER := 0; -BEGIN - SELECT max(workflowitemrole_id) INTO curr FROM cwf_workflowitemrole; - - curr := curr + 1; - - EXECUTE IMMEDIATE 'DROP SEQUENCE cwf_workflowitemrole_seq'; - - EXECUTE IMMEDIATE 'CREATE SEQUENCE cwf_workflowitemrole_seq START WITH ' || NVL(curr, 1); -END; -/ - -DECLARE - curr NUMBER := 0; -BEGIN - SELECT max(pooltask_id) INTO curr FROM cwf_pooltask; - - curr := curr + 1; - - EXECUTE IMMEDIATE 'DROP SEQUENCE cwf_pooltask_seq'; - - EXECUTE IMMEDIATE 'CREATE SEQUENCE cwf_pooltask_seq START WITH ' || NVL(curr, 1); -END; -/ - -DECLARE - curr NUMBER := 0; -BEGIN - SELECT max(claimtask_id) INTO curr FROM cwf_claimtask; - - curr := curr + 1; - - EXECUTE IMMEDIATE 'DROP SEQUENCE cwf_claimtask_seq'; - - EXECUTE IMMEDIATE 'CREATE SEQUENCE cwf_claimtask_seq START WITH ' || NVL(curr, 1); -END; -/ - -DECLARE - curr NUMBER := 0; -BEGIN - SELECT max(in_progress_user_id) INTO curr FROM cwf_in_progress_user; - - curr := curr + 1; - - EXECUTE IMMEDIATE 'DROP SEQUENCE cwf_in_progress_user_seq'; - - EXECUTE IMMEDIATE 'CREATE SEQUENCE cwf_in_progress_user_seq START WITH ' || NVL(curr, 1); -END; -/ diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/xmlworkflow/oracle/v6.0__DS-2701_data_workflow_migration.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/xmlworkflow/oracle/v6.0__DS-2701_data_workflow_migration.sql deleted file mode 100644 index 70eb419d8f..0000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/xmlworkflow/oracle/v6.0__DS-2701_data_workflow_migration.sql +++ /dev/null @@ -1,377 +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/ --- - ----------------------------------------------------- --- Data Migration for XML/Configurable Workflow --- --- This file will automatically migrate existing --- classic workflows to XML/Configurable workflows. --- NOTE however that the corresponding --- "xml_workflow_migration.sql" script must FIRST be --- called to create the appropriate database tables. --- --- This script is called automatically by the following --- Flyway Java migration class: --- org.dspace.storage.rdbms.xmlworkflow.V6_0_2015_09_01__DS_2701_Enable_XMLWorkflow_Migration ----------------------------------------------------- - --- Convert workflow groups: --- TODO: is 'to_number' ok? do not forget to change role_id values - -INSERT INTO cwf_collectionrole (collectionrole_id, role_id, group_id, collection_id) -SELECT -cwf_collectionrole_seq.nextval as collectionrole_id, -'reviewer' AS role_id, -collection.workflow_step_1 AS group_id, -collection.uuid AS collection_id -FROM collection -WHERE collection.workflow_step_1 IS NOT NULL; - -INSERT INTO cwf_collectionrole (collectionrole_id, role_id, group_id, collection_id) -SELECT -cwf_collectionrole_seq.nextval as collectionrole_id, -'editor' AS role_id, -collection.workflow_step_2 AS group_id, -collection.uuid AS collection_id -FROM collection -WHERE collection.workflow_step_2 IS NOT NULL; - -INSERT INTO cwf_collectionrole (collectionrole_id, role_id, group_id, collection_id) -SELECT -cwf_collectionrole_seq.nextval as collectionrole_id, -'finaleditor' AS role_id, -collection.workflow_step_3 AS group_id, -collection.uuid AS collection_id -FROM collection -WHERE collection.workflow_step_3 IS NOT NULL; - - --- Migrate workflow items -INSERT INTO cwf_workflowitem (workflowitem_id, item_id, collection_id, multiple_titles, published_before, multiple_files) -SELECT -workflow_id AS workflowitem_id, -item_id, -collection_id, -multiple_titles, -published_before, -multiple_files -FROM workflowitem; - - --- Migrate claimed tasks -INSERT INTO cwf_claimtask (claimtask_id,workflowitem_id, workflow_id, step_id, action_id, owner_id) -SELECT -cwf_claimtask_seq.nextval AS claimtask_id, -workflow_id AS workflowitem_id, -'default' AS workflow_id, -'reviewstep' AS step_id, -'reviewaction' AS action_id, -owner AS owner_id -FROM workflowitem WHERE owner IS NOT NULL AND state = 2; - -INSERT INTO cwf_claimtask (claimtask_id,workflowitem_id, workflow_id, step_id, action_id, owner_id) -SELECT -cwf_claimtask_seq.nextval AS claimtask_id, -workflow_id AS workflowitem_id, -'default' AS workflow_id, -'editstep' AS step_id, -'editaction' AS action_id, -owner AS owner_id -FROM workflowitem WHERE owner IS NOT NULL AND state = 4; - -INSERT INTO cwf_claimtask (claimtask_id,workflowitem_id, workflow_id, step_id, action_id, owner_id) -SELECT -cwf_claimtask_seq.nextval AS claimtask_id, -workflow_id AS workflowitem_id, -'default' AS workflow_id, -'finaleditstep' AS step_id, -'finaleditaction' AS action_id, -owner AS owner_id -FROM workflowitem WHERE owner IS NOT NULL AND state = 6; - - --- Migrate pooled tasks -INSERT INTO cwf_pooltask (pooltask_id,workflowitem_id, workflow_id, step_id, action_id, group_id) -SELECT -cwf_pooltask_seq.nextval AS pooltask_id, -workflowitem.workflow_id AS workflowitem_id, -'default' AS workflow_id, -'reviewstep' AS step_id, -'claimaction' AS action_id, -cwf_collectionrole.group_id AS group_id -FROM workflowitem INNER JOIN cwf_collectionrole ON workflowitem.collection_id = cwf_collectionrole.collection_id -WHERE workflowitem.owner IS NULL AND workflowitem.state = 1 AND cwf_collectionrole.role_id = 'reviewer'; - -INSERT INTO cwf_pooltask (pooltask_id,workflowitem_id, workflow_id, step_id, action_id, group_id) -SELECT -cwf_pooltask_seq.nextval AS pooltask_id, -workflowitem.workflow_id AS workflowitem_id, -'default' AS workflow_id, -'editstep' AS step_id, -'claimaction' AS action_id, -cwf_collectionrole.group_id AS group_id -FROM workflowitem INNER JOIN cwf_collectionrole ON workflowitem.collection_id = cwf_collectionrole.collection_id -WHERE workflowitem.owner IS NULL AND workflowitem.state = 3 AND cwf_collectionrole.role_id = 'editor'; - -INSERT INTO cwf_pooltask (pooltask_id,workflowitem_id, workflow_id, step_id, action_id, group_id) -SELECT -cwf_pooltask_seq.nextval AS pooltask_id, -workflowitem.workflow_id AS workflowitem_id, -'default' AS workflow_id, -'finaleditstep' AS step_id, -'claimaction' AS action_id, -cwf_collectionrole.group_id AS group_id -FROM workflowitem INNER JOIN cwf_collectionrole ON workflowitem.collection_id = cwf_collectionrole.collection_id -WHERE workflowitem.owner IS NULL AND workflowitem.state = 5 AND cwf_collectionrole.role_id = 'finaleditor'; - --- Delete resource policies for workflowitems before creating new ones -DELETE FROM resourcepolicy -WHERE dspace_object IN - (SELECT item_id FROM workflowitem); - -DELETE FROM resourcepolicy -WHERE dspace_object IN - (SELECT item2bundle.bundle_id FROM - (workflowitem INNER JOIN item2bundle ON workflowitem.item_id = item2bundle.item_id)); - -DELETE FROM resourcepolicy -WHERE dspace_object IN - (SELECT bundle2bitstream.bitstream_id FROM - ((workflowitem INNER JOIN item2bundle ON workflowitem.item_id = item2bundle.item_id) - INNER JOIN bundle2bitstream ON item2bundle.bundle_id = bundle2bitstream.bundle_id)); --- Create policies for claimtasks --- public static final int BITSTREAM = 0; --- public static final int BUNDLE = 1; --- public static final int ITEM = 2; - --- public static final int READ = 0; --- public static final int WRITE = 1; --- public static final int DELETE = 2; --- public static final int ADD = 3; --- public static final int REMOVE = 4; --- Item --- TODO: getnextID == SELECT sequence.nextval FROM DUAL!! --- Create a temporarty table with action ID's -CREATE TABLE temptable( - action_id INTEGER PRIMARY KEY -); -INSERT ALL - INTO temptable (action_id) VALUES (0) - INTO temptable (action_id) VALUES (1) - INTO temptable (action_id) VALUES (2) - INTO temptable (action_id) VALUES (3) - INTO temptable (action_id) VALUES (4) -SELECT * FROM DUAL; - -INSERT INTO resourcepolicy (policy_id, resource_type_id, dspace_object, action_id, eperson_id) -SELECT -resourcepolicy_seq.nextval AS policy_id, -2 AS resource_type_id, -cwf_workflowitem.item_id AS dspace_object, -temptable.action_id AS action_id, -cwf_claimtask.owner_id AS eperson_id -FROM (cwf_workflowitem INNER JOIN cwf_claimtask ON cwf_workflowitem.workflowitem_id = cwf_claimtask.workflowitem_id), -temptable; - --- Bundles -INSERT INTO resourcepolicy (policy_id, resource_type_id, dspace_object, action_id, eperson_id) -SELECT -resourcepolicy_seq.nextval AS policy_id, -1 AS resource_type_id, -item2bundle.bundle_id AS dspace_object, -temptable.action_id AS action_id, -cwf_claimtask.owner_id AS eperson_id -FROM -( - (cwf_workflowitem INNER JOIN cwf_claimtask ON cwf_workflowitem.workflowitem_id = cwf_claimtask.workflowitem_id) - INNER JOIN item2bundle ON cwf_workflowitem.item_id = item2bundle.item_id -), temptable; - - --- Bitstreams -INSERT INTO resourcepolicy (policy_id, resource_type_id, dspace_object, action_id, eperson_id) -SELECT -resourcepolicy_seq.nextval AS policy_id, -0 AS resource_type_id, -bundle2bitstream.bitstream_id AS dspace_object, -temptable.action_id AS action_id, -cwf_claimtask.owner_id AS eperson_id -FROM -( - ((cwf_workflowitem INNER JOIN cwf_claimtask ON cwf_workflowitem.workflowitem_id = cwf_claimtask.workflowitem_id) - INNER JOIN item2bundle ON cwf_workflowitem.item_id = item2bundle.item_id) - INNER JOIN bundle2bitstream ON item2bundle.bundle_id = bundle2bitstream.bundle_id -), temptable; - - --- Create policies for pooled tasks - -INSERT INTO resourcepolicy (policy_id, resource_type_id, dspace_object, action_id, epersongroup_id) -SELECT -resourcepolicy_seq.nextval AS policy_id, -2 AS resource_type_id, -cwf_workflowitem.item_id AS dspace_object, -temptable.action_id AS action_id, -cwf_pooltask.group_id AS epersongroup_id -FROM (cwf_workflowitem INNER JOIN cwf_pooltask ON cwf_workflowitem.workflowitem_id = cwf_pooltask.workflowitem_id), -temptable; - --- Bundles -INSERT INTO resourcepolicy (policy_id, resource_type_id, dspace_object, action_id, epersongroup_id) -SELECT -resourcepolicy_seq.nextval AS policy_id, -1 AS resource_type_id, -item2bundle.bundle_id AS dspace_object, -temptable.action_id AS action_id, -cwf_pooltask.group_id AS epersongroup_id -FROM -( - (cwf_workflowitem INNER JOIN cwf_pooltask ON cwf_workflowitem.workflowitem_id = cwf_pooltask.workflowitem_id) - INNER JOIN item2bundle ON cwf_workflowitem.item_id = item2bundle.item_id -), temptable; - --- Bitstreams -INSERT INTO resourcepolicy (policy_id, resource_type_id, dspace_object, action_id, epersongroup_id) -SELECT -resourcepolicy_seq.nextval AS policy_id, -0 AS resource_type_id, -bundle2bitstream.bitstream_id AS dspace_object, -temptable.action_id AS action_id, -cwf_pooltask.group_id AS epersongroup_id -FROM -( - ((cwf_workflowitem INNER JOIN cwf_pooltask ON cwf_workflowitem.workflowitem_id = cwf_pooltask.workflowitem_id) - INNER JOIN item2bundle ON cwf_workflowitem.item_id = item2bundle.item_id) - INNER JOIN bundle2bitstream ON item2bundle.bundle_id = bundle2bitstream.bundle_id -), temptable; - --- Drop the temporary table with the action ID's -DROP TABLE temptable; - --- Create policies for submitter --- TODO: only add if unique -INSERT INTO resourcepolicy (policy_id, resource_type_id, dspace_object, action_id, eperson_id) -SELECT -resourcepolicy_seq.nextval AS policy_id, -2 AS resource_type_id, -cwf_workflowitem.item_id AS dspace_object, -0 AS action_id, -item.submitter_id AS eperson_id -FROM (cwf_workflowitem INNER JOIN item ON cwf_workflowitem.item_id = item.uuid); - -INSERT INTO resourcepolicy (policy_id, resource_type_id, dspace_object, action_id, eperson_id) -SELECT -resourcepolicy_seq.nextval AS policy_id, -1 AS resource_type_id, -item2bundle.bundle_id AS dspace_object, -0 AS action_id, -item.submitter_id AS eperson_id -FROM ((cwf_workflowitem INNER JOIN item ON cwf_workflowitem.item_id = item.uuid) - INNER JOIN item2bundle ON cwf_workflowitem.item_id = item2bundle.item_id - ); - -INSERT INTO resourcepolicy (policy_id, resource_type_id, dspace_object, action_id, eperson_id) -SELECT -resourcepolicy_seq.nextval AS policy_id, -0 AS resource_type_id, -bundle2bitstream.bitstream_id AS dspace_object, -0 AS action_id, -item.submitter_id AS eperson_id -FROM (((cwf_workflowitem INNER JOIN item ON cwf_workflowitem.item_id = item.uuid) - INNER JOIN item2bundle ON cwf_workflowitem.item_id = item2bundle.item_id) - INNER JOIN bundle2bitstream ON item2bundle.bundle_id = bundle2bitstream.bundle_id -); - --- TODO: not tested yet -INSERT INTO cwf_in_progress_user (in_progress_user_id, workflowitem_id, user_id, finished) -SELECT - cwf_in_progress_user_seq.nextval AS in_progress_user_id, - cwf_workflowitem.workflowitem_id AS workflowitem_id, - cwf_claimtask.owner_id AS user_id, - 0 as finished -FROM - (cwf_claimtask INNER JOIN cwf_workflowitem ON cwf_workflowitem.workflowitem_id = cwf_claimtask.workflowitem_id); - --- TODO: improve this, important is NVL(curr, 1)!! without this function, empty tables (max = [null]) will only result in sequence deletion -DECLARE - curr NUMBER := 0; -BEGIN - SELECT max(workflowitem_id) INTO curr FROM cwf_workflowitem; - - curr := curr + 1; - - EXECUTE IMMEDIATE 'DROP SEQUENCE cwf_workflowitem_seq'; - - EXECUTE IMMEDIATE 'CREATE SEQUENCE cwf_workflowitem_seq START WITH ' || NVL(curr, 1); -END; -/ - -DECLARE - curr NUMBER := 0; -BEGIN - SELECT max(collectionrole_id) INTO curr FROM cwf_collectionrole; - - curr := curr + 1; - - EXECUTE IMMEDIATE 'DROP SEQUENCE cwf_collectionrole_seq'; - - EXECUTE IMMEDIATE 'CREATE SEQUENCE cwf_collectionrole_seq START WITH ' || NVL(curr, 1); -END; -/ - -DECLARE - curr NUMBER := 0; -BEGIN - SELECT max(workflowitemrole_id) INTO curr FROM cwf_workflowitemrole; - - curr := curr + 1; - - EXECUTE IMMEDIATE 'DROP SEQUENCE cwf_workflowitemrole_seq'; - - EXECUTE IMMEDIATE 'CREATE SEQUENCE cwf_workflowitemrole_seq START WITH ' || NVL(curr, 1); -END; -/ - -DECLARE - curr NUMBER := 0; -BEGIN - SELECT max(pooltask_id) INTO curr FROM cwf_pooltask; - - curr := curr + 1; - - EXECUTE IMMEDIATE 'DROP SEQUENCE cwf_pooltask_seq'; - - EXECUTE IMMEDIATE 'CREATE SEQUENCE cwf_pooltask_seq START WITH ' || NVL(curr, 1); -END; -/ - -DECLARE - curr NUMBER := 0; -BEGIN - SELECT max(claimtask_id) INTO curr FROM cwf_claimtask; - - curr := curr + 1; - - EXECUTE IMMEDIATE 'DROP SEQUENCE cwf_claimtask_seq'; - - EXECUTE IMMEDIATE 'CREATE SEQUENCE cwf_claimtask_seq START WITH ' || NVL(curr, 1); -END; -/ - -DECLARE - curr NUMBER := 0; -BEGIN - SELECT max(in_progress_user_id) INTO curr FROM cwf_in_progress_user; - - curr := curr + 1; - - EXECUTE IMMEDIATE 'DROP SEQUENCE cwf_in_progress_user_seq'; - - EXECUTE IMMEDIATE 'CREATE SEQUENCE cwf_in_progress_user_seq START WITH ' || NVL(curr, 1); -END; -/ diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/xmlworkflow/oracle/v6.0__DS-2701_xml_workflow_migration.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/xmlworkflow/oracle/v6.0__DS-2701_xml_workflow_migration.sql deleted file mode 100644 index 541af73dfe..0000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/xmlworkflow/oracle/v6.0__DS-2701_xml_workflow_migration.sql +++ /dev/null @@ -1,124 +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/ --- - ----------------------------------------------------- --- Database Schema Update for XML/Configurable Workflow (for DSpace 6.0) --- --- This file will automatically create/update your --- DSpace Database tables to support XML/Configurable workflows. --- However, it does NOT migrate your existing classic --- workflows. That step is performed by the corresponding --- "data_workflow_migration.sql" script. --- --- This script is called automatically by the following --- Flyway Java migration class: --- org.dspace.storage.rdbms.xmlworkflow.V6_0_2015_09_01__DS_2701_Enable_XMLWorkflow_Migration ----------------------------------------------------- - -CREATE SEQUENCE cwf_workflowitem_seq; -CREATE SEQUENCE cwf_collectionrole_seq; -CREATE SEQUENCE cwf_workflowitemrole_seq; -CREATE SEQUENCE cwf_claimtask_seq; -CREATE SEQUENCE cwf_in_progress_user_seq; -CREATE SEQUENCE cwf_pooltask_seq; - - -CREATE TABLE cwf_workflowitem -( - workflowitem_id INTEGER PRIMARY KEY, - item_id RAW(16) REFERENCES item(uuid) UNIQUE, - collection_id RAW(16) REFERENCES collection(uuid), - -- - -- Answers to questions on first page of submit UI - multiple_titles NUMBER(1), - published_before NUMBER(1), - multiple_files NUMBER(1) - -- Note: stage reached not applicable here - people involved in workflow - -- can always jump around submission UI -); - - -CREATE INDEX cwf_workflowitem_coll_fk_idx ON cwf_workflowitem(collection_id); - - -CREATE TABLE cwf_collectionrole ( -collectionrole_id INTEGER PRIMARY KEY, -role_id VARCHAR2(256), -collection_id RAW(16) REFERENCES collection(uuid), -group_id RAW(16) REFERENCES epersongroup(uuid) -); -ALTER TABLE cwf_collectionrole -ADD CONSTRAINT cwf_collectionrole_unique UNIQUE (role_id, collection_id, group_id); - -CREATE INDEX cwf_cr_coll_role_fk_idx ON cwf_collectionrole(collection_id,role_id); -CREATE INDEX cwf_cr_coll_fk_idx ON cwf_collectionrole(collection_id); - - -CREATE TABLE cwf_workflowitemrole ( - workflowitemrole_id INTEGER PRIMARY KEY, - role_id VARCHAR2(256), - workflowitem_id integer REFERENCES cwf_workflowitem(workflowitem_id), - eperson_id RAW(16) REFERENCES eperson(uuid), - group_id RAW(16) REFERENCES epersongroup(uuid) -); -ALTER TABLE cwf_workflowitemrole -ADD CONSTRAINT cwf_workflowitemrole_unique UNIQUE (role_id, workflowitem_id, eperson_id, group_id); - -CREATE INDEX cwf_wfir_item_role_fk_idx ON cwf_workflowitemrole(workflowitem_id,role_id); -CREATE INDEX cwf_wfir_item_fk_idx ON cwf_workflowitemrole(workflowitem_id); - - -CREATE TABLE cwf_pooltask ( - pooltask_id INTEGER PRIMARY KEY, - workflowitem_id INTEGER REFERENCES cwf_workflowitem(workflowitem_id), - workflow_id VARCHAR2(256), - step_id VARCHAR2(256), - action_id VARCHAR2(256), - eperson_id RAW(16) REFERENCES EPerson(uuid), - group_id RAW(16) REFERENCES epersongroup(uuid) -); - -CREATE INDEX cwf_pt_eperson_fk_idx ON cwf_pooltask(eperson_id); -CREATE INDEX cwf_pt_workflow_fk_idx ON cwf_pooltask(workflowitem_id); -CREATE INDEX cwf_pt_workflow_eperson_fk_idx ON cwf_pooltask(eperson_id,workflowitem_id); - - - -CREATE TABLE cwf_claimtask ( - claimtask_id INTEGER PRIMARY KEY, - workflowitem_id integer REFERENCES cwf_workflowitem(workflowitem_id), - workflow_id VARCHAR2(256), - step_id VARCHAR2(256), - action_id VARCHAR2(256), - owner_id RAW(16) REFERENCES eperson(uuid) -); - -ALTER TABLE cwf_claimtask -ADD CONSTRAINT cwf_claimtask_unique UNIQUE (step_id, workflowitem_id, workflow_id, owner_id, action_id); - -CREATE INDEX cwf_ct_workflow_fk_idx ON cwf_claimtask(workflowitem_id); -CREATE INDEX cwf_ct_workflow_eperson_fk_idx ON cwf_claimtask(workflowitem_id,owner_id); -CREATE INDEX cwf_ct_eperson_fk_idx ON cwf_claimtask(owner_id); -CREATE INDEX cwf_ct_wfs_fk_idx ON cwf_claimtask(workflowitem_id,step_id); -CREATE INDEX cwf_ct_wfs_action_fk_idx ON cwf_claimtask(workflowitem_id,step_id,action_id); -CREATE INDEX cwf_ct_wfs_action_e_fk_idx ON cwf_claimtask(workflowitem_id,step_id,action_id,owner_id); - - -CREATE TABLE cwf_in_progress_user ( - in_progress_user_id INTEGER PRIMARY KEY, - workflowitem_id integer REFERENCES cwf_workflowitem(workflowitem_id), - user_id RAW(16) REFERENCES eperson(uuid), - finished NUMBER(1) DEFAULT 0 -); - -ALTER TABLE cwf_in_progress_user -ADD CONSTRAINT cwf_in_progress_user_unique UNIQUE (workflowitem_id, user_id); - -CREATE INDEX cwf_ipu_workflow_fk_idx ON cwf_in_progress_user(workflowitem_id); -CREATE INDEX cwf_ipu_eperson_fk_idx ON cwf_in_progress_user(user_id); - diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/xmlworkflow/oracle/xml_workflow_migration.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/xmlworkflow/oracle/xml_workflow_migration.sql deleted file mode 100644 index f8f0e564e8..0000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/xmlworkflow/oracle/xml_workflow_migration.sql +++ /dev/null @@ -1,124 +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/ --- - ----------------------------------------------------- --- Database Schema Update for XML/Configurable Workflow --- --- This file will automatically create/update your --- DSpace Database tables to support XML/Configurable workflows. --- However, it does NOT migrate your existing classic --- workflows. That step is performed by the corresponding --- "data_workflow_migration.sql" script. --- --- This script is called automatically by the following --- Flyway Java migration class: --- org.dspace.storage.rdbms.migration.V5_0_2014_01_01__XMLWorkflow_Migration ----------------------------------------------------- - -CREATE SEQUENCE cwf_workflowitem_seq; -CREATE SEQUENCE cwf_collectionrole_seq; -CREATE SEQUENCE cwf_workflowitemrole_seq; -CREATE SEQUENCE cwf_claimtask_seq; -CREATE SEQUENCE cwf_in_progress_user_seq; -CREATE SEQUENCE cwf_pooltask_seq; - - -CREATE TABLE cwf_workflowitem -( - workflowitem_id INTEGER PRIMARY KEY, - item_id INTEGER REFERENCES item(item_id) UNIQUE, - collection_id INTEGER REFERENCES collection(collection_id), - -- - -- Answers to questions on first page of submit UI - multiple_titles NUMBER(1), - published_before NUMBER(1), - multiple_files NUMBER(1) - -- Note: stage reached not applicable here - people involved in workflow - -- can always jump around submission UI -); - - -CREATE INDEX cwf_workflowitem_coll_fk_idx ON cwf_workflowitem(collection_id); - - -CREATE TABLE cwf_collectionrole ( -collectionrole_id INTEGER PRIMARY KEY, -role_id VARCHAR2(256), -collection_id integer REFERENCES collection(collection_id), -group_id integer REFERENCES epersongroup(eperson_group_id) -); -ALTER TABLE cwf_collectionrole -ADD CONSTRAINT cwf_collectionrole_unique UNIQUE (role_id, collection_id, group_id); - -CREATE INDEX cwf_cr_coll_role_fk_idx ON cwf_collectionrole(collection_id,role_id); -CREATE INDEX cwf_cr_coll_fk_idx ON cwf_collectionrole(collection_id); - - -CREATE TABLE cwf_workflowitemrole ( - workflowitemrole_id INTEGER PRIMARY KEY, - role_id VARCHAR2(256), - workflowitem_id integer REFERENCES cwf_workflowitem(workflowitem_id), - eperson_id integer REFERENCES eperson(eperson_id), - group_id integer REFERENCES epersongroup(eperson_group_id) -); -ALTER TABLE cwf_workflowitemrole -ADD CONSTRAINT cwf_workflowitemrole_unique UNIQUE (role_id, workflowitem_id, eperson_id, group_id); - -CREATE INDEX cwf_wfir_item_role_fk_idx ON cwf_workflowitemrole(workflowitem_id,role_id); -CREATE INDEX cwf_wfir_item_fk_idx ON cwf_workflowitemrole(workflowitem_id); - - -CREATE TABLE cwf_pooltask ( - pooltask_id INTEGER PRIMARY KEY, - workflowitem_id INTEGER REFERENCES cwf_workflowitem(workflowitem_id), - workflow_id VARCHAR2(256), - step_id VARCHAR2(256), - action_id VARCHAR2(256), - eperson_id INTEGER REFERENCES EPerson(eperson_id), - group_id INTEGER REFERENCES epersongroup(eperson_group_id) -); - -CREATE INDEX cwf_pt_eperson_fk_idx ON cwf_pooltask(eperson_id); -CREATE INDEX cwf_pt_workflow_fk_idx ON cwf_pooltask(workflowitem_id); -CREATE INDEX cwf_pt_workflow_eperson_fk_idx ON cwf_pooltask(eperson_id,workflowitem_id); - - - -CREATE TABLE cwf_claimtask ( - claimtask_id INTEGER PRIMARY KEY, - workflowitem_id integer REFERENCES cwf_workflowitem(workflowitem_id), - workflow_id VARCHAR2(256), - step_id VARCHAR2(256), - action_id VARCHAR2(256), - owner_id integer REFERENCES eperson(eperson_id) -); - -ALTER TABLE cwf_claimtask -ADD CONSTRAINT cwf_claimtask_unique UNIQUE (step_id, workflowitem_id, workflow_id, owner_id, action_id); - -CREATE INDEX cwf_ct_workflow_fk_idx ON cwf_claimtask(workflowitem_id); -CREATE INDEX cwf_ct_workflow_eperson_fk_idx ON cwf_claimtask(workflowitem_id,owner_id); -CREATE INDEX cwf_ct_eperson_fk_idx ON cwf_claimtask(owner_id); -CREATE INDEX cwf_ct_wfs_fk_idx ON cwf_claimtask(workflowitem_id,step_id); -CREATE INDEX cwf_ct_wfs_action_fk_idx ON cwf_claimtask(workflowitem_id,step_id,action_id); -CREATE INDEX cwf_ct_wfs_action_e_fk_idx ON cwf_claimtask(workflowitem_id,step_id,action_id,owner_id); - - -CREATE TABLE cwf_in_progress_user ( - in_progress_user_id INTEGER PRIMARY KEY, - workflowitem_id integer REFERENCES cwf_workflowitem(workflowitem_id), - user_id integer REFERENCES eperson(eperson_id), - finished NUMBER(1) DEFAULT 0 -); - -ALTER TABLE cwf_in_progress_user -ADD CONSTRAINT cwf_in_progress_user_unique UNIQUE (workflowitem_id, user_id); - -CREATE INDEX cwf_ipu_workflow_fk_idx ON cwf_in_progress_user(workflowitem_id); -CREATE INDEX cwf_ipu_eperson_fk_idx ON cwf_in_progress_user(user_id); - 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 d5ba4bd462..6b0ef3e9b9 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 @@ -34,6 +34,14 @@ + + + + + + @@ -49,6 +57,7 @@ + xml @@ -191,4 +200,4 @@ - \ No newline at end of file + diff --git a/dspace-api/src/main/resources/spring/spring-dspace-core-services.xml b/dspace-api/src/main/resources/spring/spring-dspace-core-services.xml index 87bfcbc86c..3ce641d99c 100644 --- a/dspace-api/src/main/resources/spring/spring-dspace-core-services.xml +++ b/dspace-api/src/main/resources/spring/spring-dspace-core-services.xml @@ -13,15 +13,6 @@ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> - - - - @@ -31,12 +22,6 @@ - - - - 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 f40298db30..6d8ae0c2f0 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/item-submission.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/item-submission.xml @@ -143,6 +143,12 @@ org.dspace.app.rest.submit.step.SherpaPolicyStep sherpaPolicy + + + submit.progressbar.identifiers + org.dspace.app.rest.submit.step.ShowIdentifiersStep + identifiers + @@ -169,6 +175,8 @@ + + diff --git a/dspace-api/src/test/data/dspaceFolder/config/local.cfg b/dspace-api/src/test/data/dspaceFolder/config/local.cfg index 9cc6b7ebea..c009acb30e 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/local.cfg +++ b/dspace-api/src/test/data/dspaceFolder/config/local.cfg @@ -43,7 +43,7 @@ dspace.server.url = http://localhost db.driver = org.h2.Driver db.dialect=org.hibernate.dialect.H2Dialect # Use a 10 second database lock timeout to avoid occasional JDBC lock timeout errors -db.url = jdbc:h2:mem:test;LOCK_TIMEOUT=10000; +db.url = jdbc:h2:mem:test;LOCK_TIMEOUT=10000;NON_KEYWORDS=VALUE db.username = sa db.password = # H2's default schema is PUBLIC @@ -78,7 +78,7 @@ handle.remote-resolver.enabled = true # Whether to enable the DSpace listhandles resolver that lists all available # handles for this DSpace installation. # Defaults to "false" which means is possible to obtain the list of handles -# of this DSpace installation, whenever the `handle.remote-resolver.enabled = true`. +# of this DSpace installation, whenever the `handle.remote-resolver.enabled = true`. handle.hide.listhandles = false ##################### @@ -171,3 +171,6 @@ choices.plugin.dspace.object.owner = EPersonAuthority choices.presentation.dspace.object.owner = suggest 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.* \ No newline at end of file diff --git a/dspace-api/src/test/data/dspaceFolder/config/modules/identifiers.cfg b/dspace-api/src/test/data/dspaceFolder/config/modules/identifiers.cfg new file mode 100644 index 0000000000..64512572ff --- /dev/null +++ b/dspace-api/src/test/data/dspaceFolder/config/modules/identifiers.cfg @@ -0,0 +1,49 @@ +#----------------------------------------------------------------------# +#---------------------IDENTIFIER CONFIGURATIONS------------------------# +#----------------------------------------------------------------------# +# These configs are used for additional identifier configuration such # +# as the Show Identifiers step which can "pre-mint" DOIs and Handles # +#----------------------------------------------------------------------# + +# Should configured identifiers (eg handle and DOI) be minted for (future) registration at workspace item creation? +# A handle created at this stage will act just like a regular handle created at archive time. +# A DOI created at this stage will be in a 'PENDING' status while in workspace and workflow. +# At the time of item install, the DOI filter (if any) will be applied and if the item matches the filter, the DOI +# status will be updated to TO_BE_REGISTERED. An administrator can also manually progress the DOI status, overriding +# any filters, in the item status page. +# This option doesn't require the Show Identifiers submission step to be visible. +# Default: false +identifiers.submission.register = false + +# This configuration property can be set to a filter name to determine if a PENDING DOI for an item +# should be queued for registration. If the filter doesn't match, the DOI will stay in PENDING or MINTED status +# so that the identifier itself persists in case it is considered for registration in the future. +# See doi-filter and other example filters in item-filters.xml. +# Default (always_true_filter) +identifiers.submission.filter.install = doi_filter + +# This optional configuration property can be set to a filter name, in case there are some initial rules to apply +# when first deciding whether a DOI should be be created for a new workspace item with a PENDING status. +# This filter is only applied if identifiers.submission.register is true. +# This filter is updated as submission data is saved. +# Default: (always_true_filter) +identifiers.submission.filter.workspace = doi_filter + +# If true, the workspace filter will be applied as submission data is saved. If the filter no longer +# matches the item, the DOI will be shifted into a MINTED status and not displayed in the submission section. +# If false, then once a DOI has been created with PENDING status it will remain that way until final item install +# Default: true +#identifiers.submission.strip_pending_during_submission = true + +# This configuration property can be set to a filter name to determine if an item processed by RegisterDOI curation +# task should be eligible for a DOI +identifiers.submission.filter.curation = always_true_filter + +# Show Register DOI button in item status page? +# Default: false +identifiers.item-status.register-doi = true + +# Which identifier types to show in submission step? +# Default: handle, doi (currently the only supported identifier 'types') +identifiers.submission.display = handle +identifiers.submission.display = doi \ No newline at end of file diff --git a/dspace-api/src/test/data/dspaceFolder/config/spring/api/external-services.xml b/dspace-api/src/test/data/dspaceFolder/config/spring/api/external-services.xml index bd6da8ad8a..37e1fb5089 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/spring/api/external-services.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/spring/api/external-services.xml @@ -91,4 +91,16 @@ - \ No newline at end of file + + + + + + + Publication + none + + + + + diff --git a/dspace-api/src/test/data/dspaceFolder/config/spring/api/identifier-service.xml b/dspace-api/src/test/data/dspaceFolder/config/spring/api/identifier-service.xml index 206b801d08..8f7cc297d7 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/spring/api/identifier-service.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/spring/api/identifier-service.xml @@ -19,7 +19,18 @@ + scope="singleton"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + article$ + bachelorThesis$ + masterThesis$ + doctoralThesis$ + book$ + bookPart$ + review$ + conferenceObject$ + lecture$ + workingPaper$ + preprint$ + report$ + annotation$ + contributionToPeriodical$ + patent$ + dataset$ + other$ + + + + + + + + + + + + + 123456789/20 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 123456789/3 + 123456789/4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dspace-api/src/test/data/dspaceFolder/config/spring/api/workflow-actions.xml b/dspace-api/src/test/data/dspaceFolder/config/spring/api/workflow-actions.xml index 318d1ad3d7..0d07436227 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/spring/api/workflow-actions.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/spring/api/workflow-actions.xml @@ -15,9 +15,12 @@ - + + + + - + @@ -63,6 +66,12 @@ + + + + + + diff --git a/dspace-api/src/test/data/dspaceFolder/config/spring/api/workflow.xml b/dspace-api/src/test/data/dspaceFolder/config/spring/api/workflow.xml index 6e987ae8b0..a83be3fa33 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/spring/api/workflow.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/spring/api/workflow.xml @@ -153,6 +153,7 @@ + diff --git a/dspace-api/src/test/java/org/dspace/alerts/SystemWideAlertServiceTest.java b/dspace-api/src/test/java/org/dspace/alerts/SystemWideAlertServiceTest.java new file mode 100644 index 0000000000..5d8d6ac594 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/alerts/SystemWideAlertServiceTest.java @@ -0,0 +1,202 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.alerts; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.logging.log4j.Logger; +import org.dspace.alerts.dao.SystemWideAlertDAO; +import org.dspace.alerts.service.SystemWideAlertService; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class SystemWideAlertServiceTest { + + private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(SystemWideAlertService.class); + + @InjectMocks + private SystemWideAlertServiceImpl systemWideAlertService; + + @Mock + private SystemWideAlertDAO systemWideAlertDAO; + + @Mock + private AuthorizeService authorizeService; + + @Mock + private Context context; + + @Mock + private SystemWideAlert systemWideAlert; + + @Mock + private EPerson eperson; + + + @Test + public void testCreate() throws Exception { + // Mock admin state + when(authorizeService.isAdmin(context)).thenReturn(true); + + // Declare objects utilized in unit test + SystemWideAlert systemWideAlert = new SystemWideAlert(); + systemWideAlert.setMessage("Test message"); + systemWideAlert.setAllowSessions(AllowSessionsEnum.ALLOW_ALL_SESSIONS); + systemWideAlert.setCountdownTo(null); + systemWideAlert.setActive(true); + + // Mock DAO to return our defined SystemWideAlert + when(systemWideAlertDAO.create(any(), any())).thenReturn(systemWideAlert); + + // The newly created SystemWideAlert's message should match our mocked SystemWideAlert's message + SystemWideAlert result = systemWideAlertService.create(context, "Test message", + AllowSessionsEnum.ALLOW_ALL_SESSIONS, null, true); + assertEquals("TestCreate 0", systemWideAlert.getMessage(), result.getMessage()); + // The newly created SystemWideAlert should match our mocked SystemWideAlert + assertEquals("TestCreate 1", systemWideAlert, result); + } + + + @Test + public void testFindAll() throws Exception { + // Declare objects utilized in unit test + List systemWideAlertList = new ArrayList<>(); + + // The SystemWideAlert(s) reported from our mocked state should match our systemWideAlertList + assertEquals("TestFindAll 0", systemWideAlertList, systemWideAlertService.findAll(context)); + } + + @Test + public void testFind() throws Exception { + // Mock DAO to return our mocked SystemWideAlert + when(systemWideAlertService.find(context, 0)).thenReturn(systemWideAlert); + + // The SystemWideAlert reported from our ID should match our mocked SystemWideAlert + assertEquals("TestFind 0", systemWideAlert, systemWideAlertService.find(context, 0)); + } + + @Test + public void testFindAllActive() throws Exception { + // Declare objects utilized in unit test + List systemWideAlertList = new ArrayList<>(); + + // The SystemWideAlert(s) reported from our mocked state should match our systemWideAlertList + assertEquals("TestFindAllActive 0", systemWideAlertList, systemWideAlertService.findAllActive(context, 10, 0)); + } + + + @Test + public void testUpdate() throws Exception { + // Mock admin state + when(authorizeService.isAdmin(context)).thenReturn(true); + + // Invoke impl of method update() + systemWideAlertService.update(context, systemWideAlert); + + // Verify systemWideAlertDAO.save was invoked twice to confirm proper invocation of both impls of update() + Mockito.verify(systemWideAlertDAO, times(1)).save(context, systemWideAlert); + } + + @Test + public void testDelete() throws Exception { + // Mock admin state + when(authorizeService.isAdmin(context)).thenReturn(true); + + // Invoke method delete() + systemWideAlertService.delete(context, systemWideAlert); + + // Verify systemWideAlertDAO.delete() ran once to confirm proper invocation of delete() + Mockito.verify(systemWideAlertDAO, times(1)).delete(context, systemWideAlert); + } + + @Test + public void canNonAdminUserLoginTrueTest() throws Exception { + // Mock the alert state + when(systemWideAlert.getAllowSessions()).thenReturn(AllowSessionsEnum.ALLOW_ALL_SESSIONS); + + // Mock DAO to return our defined systemWideAlertList + List systemWideAlertList = new ArrayList<>(); + systemWideAlertList.add(systemWideAlert); + when(systemWideAlertDAO.findAllActive(context, 1, 0)).thenReturn(systemWideAlertList); + + // Assert the non admin users can log in + assertTrue("CanNonAdminUserLogin 0", systemWideAlertService.canNonAdminUserLogin(context)); + } + + @Test + public void canNonAdminUserLoginFalseTest() throws Exception { + // Mock the alert state + when(systemWideAlert.getAllowSessions()).thenReturn(AllowSessionsEnum.ALLOW_ADMIN_SESSIONS_ONLY); + + // Mock DAO to return our defined systemWideAlertList + List systemWideAlertList = new ArrayList<>(); + systemWideAlertList.add(systemWideAlert); + when(systemWideAlertDAO.findAllActive(context, 1, 0)).thenReturn(systemWideAlertList); + + // Assert the non admin users can log in + assertFalse("CanNonAdminUserLogin 1", systemWideAlertService.canNonAdminUserLogin(context)); + } + + @Test + public void canUserMaintainSessionAdminTest() throws Exception { + // Assert the admin user can log in + assertTrue("CanUserMaintainSession 0", systemWideAlertService.canNonAdminUserLogin(context)); + } + @Test + public void canUserMaintainSessionTrueTest() throws Exception { + // Mock admin state + when(authorizeService.isAdmin(context, eperson)).thenReturn(false); + + // Mock the alert state + when(systemWideAlert.getAllowSessions()).thenReturn(AllowSessionsEnum.ALLOW_CURRENT_SESSIONS_ONLY); + + // Mock DAO to return our defined systemWideAlertList + List systemWideAlertList = new ArrayList<>(); + systemWideAlertList.add(systemWideAlert); + when(systemWideAlertDAO.findAllActive(context, 1, 0)).thenReturn(systemWideAlertList); + + // Assert the non admin users can main session + assertTrue("CanUserMaintainSession 1", systemWideAlertService.canUserMaintainSession(context, eperson)); + } + + @Test + public void canUserMaintainSessionFalseTest() throws Exception { + // Mock admin state + when(authorizeService.isAdmin(context, eperson)).thenReturn(false); + + // Mock the alert state + when(systemWideAlert.getAllowSessions()).thenReturn(AllowSessionsEnum.ALLOW_ADMIN_SESSIONS_ONLY); + + // Mock DAO to return our defined systemWideAlertList + List systemWideAlertList = new ArrayList<>(); + systemWideAlertList.add(systemWideAlert); + when(systemWideAlertDAO.findAllActive(context, 1, 0)).thenReturn(systemWideAlertList); + + // Assert the non admin users cannot main session + assertFalse("CanUserMaintainSession 2", systemWideAlertService.canUserMaintainSession(context, eperson)); + } + + + +} 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 new file mode 100644 index 0000000000..b03d7576f9 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/app/requestitem/RequestItemHelpdeskStrategyTest.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.requestitem; + +import static org.junit.Assert.assertEquals; + +import java.sql.SQLException; +import java.util.List; + +import org.dspace.AbstractUnitTest; +import org.dspace.builder.AbstractBuilder; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.EPersonBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.Item; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.factory.EPersonServiceFactory; +import org.dspace.eperson.service.EPersonService; +import org.dspace.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Ignore; +import org.junit.Test; + +/** + * + * @author mwood + */ +public class RequestItemHelpdeskStrategyTest + extends AbstractUnitTest { + private static final String HELPDESK_ADDRESS = "helpdesk@example.com"; + private static final String AUTHOR_ADDRESS = "john.doe@example.com"; + + private static ConfigurationService configurationService; + private static EPersonService epersonService; + private static EPerson johnDoe; + + private Item item; + + @BeforeClass + public static void setUpClass() + throws SQLException { + AbstractBuilder.init(); // AbstractUnitTest doesn't do this for us. + + configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); + epersonService = EPersonServiceFactory.getInstance().getEPersonService(); + + Context ctx = new Context(); + ctx.turnOffAuthorisationSystem(); + johnDoe = EPersonBuilder.createEPerson(ctx) + .withEmail(AUTHOR_ADDRESS) + .withNameInMetadata("John", "Doe") + .build(); + ctx.restoreAuthSystemState(); + ctx.complete(); + } + + @AfterClass + public static void tearDownClass() { + AbstractBuilder.destroy(); // AbstractUnitTest doesn't do this for us. + } + + @Before + public void setUp() { + context = new Context(); + context.setCurrentUser(johnDoe); + context.turnOffAuthorisationSystem(); + Community community = CommunityBuilder.createCommunity(context).build(); + Collection collection = CollectionBuilder.createCollection(context, community).build(); + item = ItemBuilder.createItem(context, collection) + .build(); + context.restoreAuthSystemState(); + context.setCurrentUser(null); + } + + /** + * Test of getRequestItemAuthor method, of class RequestItemHelpdeskStrategy. + * @throws java.lang.Exception passed through. + */ + @Test + public void testGetRequestItemAuthor() + throws Exception { + RequestItemHelpdeskStrategy instance = new RequestItemHelpdeskStrategy(); + instance.configurationService = configurationService; + instance.ePersonService = epersonService; + + // Check with help desk enabled. + configurationService.setProperty(RequestItemHelpdeskStrategy.P_HELPDESK_OVERRIDE, "true"); + configurationService.setProperty(RequestItemHelpdeskStrategy.P_MAIL_HELPDESK, HELPDESK_ADDRESS); + List authors = instance.getRequestItemAuthor(context, item); + assertEquals("Wrong author address", HELPDESK_ADDRESS, authors.get(0).getEmail()); + + // Check with help desk disabled. + configurationService.setProperty(RequestItemHelpdeskStrategy.P_HELPDESK_OVERRIDE, "false"); + authors = instance.getRequestItemAuthor(context, item); + assertEquals("Wrong author address", AUTHOR_ADDRESS, authors.get(0).getEmail()); + } + + /** + * Test of getHelpDeskPerson method, of class RequestItemHelpdeskStrategy. + * @throws java.lang.Exception passed through. + */ + @Ignore + @Test + public void testGetHelpDeskPerson() throws Exception { + } +} 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 new file mode 100644 index 0000000000..f485a591b0 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/app/requestitem/RequestItemSubmitterStrategyTest.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.requestitem; + +import static org.junit.Assert.assertEquals; + +import java.sql.SQLException; +import java.util.List; + +import org.dspace.AbstractUnitTest; +import org.dspace.builder.AbstractBuilder; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.EPersonBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.Item; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +/** + * + * @author mwood + */ +public class RequestItemSubmitterStrategyTest + extends AbstractUnitTest { + private static final String AUTHOR_ADDRESS = "john.doe@example.com"; + + private static EPerson johnDoe; + + private Item item; + + @BeforeClass + public static void setUpClass() + throws SQLException { + AbstractBuilder.init(); // AbstractUnitTest doesn't do this for us. + + Context ctx = new Context(); + ctx.turnOffAuthorisationSystem(); + johnDoe = EPersonBuilder.createEPerson(ctx) + .withEmail(AUTHOR_ADDRESS) + .withNameInMetadata("John", "Doe") + .build(); + ctx.restoreAuthSystemState(); + ctx.complete(); + } + + @AfterClass + public static void tearDownClass() { + AbstractBuilder.destroy(); // AbstractUnitTest doesn't do this for us. + } + + @Before + public void setUp() { + context = new Context(); + context.setCurrentUser(johnDoe); + context.turnOffAuthorisationSystem(); + Community community = CommunityBuilder.createCommunity(context).build(); + Collection collection = CollectionBuilder.createCollection(context, community).build(); + item = ItemBuilder.createItem(context, collection) + .build(); + context.restoreAuthSystemState(); + context.setCurrentUser(null); + } + + /** + * Test of getRequestItemAuthor method, of class RequestItemSubmitterStrategy. + * @throws java.lang.Exception passed through. + */ + @Test + public void testGetRequestItemAuthor() + throws Exception { + RequestItemSubmitterStrategy instance = new RequestItemSubmitterStrategy(); + List author = instance.getRequestItemAuthor(context, item); + assertEquals("Wrong author address", AUTHOR_ADDRESS, author.get(0).getEmail()); + } +} diff --git a/dspace-api/src/test/java/org/dspace/app/util/RegexPatternUtilsTest.java b/dspace-api/src/test/java/org/dspace/app/util/RegexPatternUtilsTest.java new file mode 100644 index 0000000000..30a9100ad4 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/app/util/RegexPatternUtilsTest.java @@ -0,0 +1,214 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.util; + +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.assertThrows; +import static org.junit.Assert.assertTrue; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +import org.dspace.AbstractUnitTest; +import org.junit.Test; + +/** + * Tests for RegexPatternUtils + * + * @author Vincenzo Mecca (vins01-4science - vincenzo.mecca at 4science.com) + * + */ +public class RegexPatternUtilsTest extends AbstractUnitTest { + + @Test + public void testValidRegexWithFlag() { + final String insensitiveWord = "/[a-z]+/i"; + Pattern computePattern = Pattern.compile(insensitiveWord); + assertNotNull(computePattern); + + Matcher matcher = computePattern.matcher("Hello"); + assertFalse(matcher.matches()); + matcher = computePattern.matcher("DSpace"); + assertFalse(matcher.matches()); + matcher = computePattern.matcher("Community"); + assertFalse(matcher.matches()); + matcher = computePattern.matcher("/wrongpattern/i"); + assertTrue(matcher.matches()); + matcher = computePattern.matcher("001"); + assertFalse(matcher.matches()); + matcher = computePattern.matcher("?/'`}{][<>.,"); + assertFalse(matcher.matches()); + computePattern = RegexPatternUtils.computePattern(insensitiveWord); + assertNotNull(computePattern); + + matcher = computePattern.matcher("Hello"); + assertTrue(matcher.matches()); + matcher = computePattern.matcher("DSpace"); + assertTrue(matcher.matches()); + matcher = computePattern.matcher("Community"); + assertTrue(matcher.matches()); + matcher = computePattern.matcher("/wrong-pattern/i"); + assertFalse(matcher.matches()); + matcher = computePattern.matcher("001"); + assertFalse(matcher.matches()); + matcher = computePattern.matcher("?/'`}{][<>.,"); + assertFalse(matcher.matches()); + } + + @Test + public void testRegexWithoutFlag() { + final String sensitiveWord = "[a-z]+"; + Pattern computePattern = RegexPatternUtils.computePattern(sensitiveWord); + assertNotNull(computePattern); + + Matcher matcher = computePattern.matcher("hello"); + assertTrue(matcher.matches()); + matcher = computePattern.matcher("dspace"); + assertTrue(matcher.matches()); + matcher = computePattern.matcher("community"); + assertTrue(matcher.matches()); + matcher = computePattern.matcher("Hello"); + assertFalse(matcher.matches()); + matcher = computePattern.matcher("DSpace"); + assertFalse(matcher.matches()); + matcher = computePattern.matcher("Community"); + assertFalse(matcher.matches()); + matcher = computePattern.matcher("/wrongpattern/i"); + assertFalse(matcher.matches()); + matcher = computePattern.matcher("001"); + assertFalse(matcher.matches()); + matcher = computePattern.matcher("?/'`}{][<>.,"); + assertFalse(matcher.matches()); + + final String sensitiveWordWithDelimiter = "/[a-z]+/"; + computePattern = RegexPatternUtils.computePattern(sensitiveWordWithDelimiter); + assertNotNull(computePattern); + + matcher = computePattern.matcher("hello"); + assertTrue(matcher.matches()); + matcher = computePattern.matcher("dspace"); + assertTrue(matcher.matches()); + matcher = computePattern.matcher("community"); + assertTrue(matcher.matches()); + matcher = computePattern.matcher("Hello"); + assertFalse(matcher.matches()); + matcher = computePattern.matcher("DSpace"); + assertFalse(matcher.matches()); + matcher = computePattern.matcher("Community"); + assertFalse(matcher.matches()); + matcher = computePattern.matcher("/wrongpattern/i"); + assertFalse(matcher.matches()); + matcher = computePattern.matcher("001"); + assertFalse(matcher.matches()); + matcher = computePattern.matcher("?/'`}{][<>.,"); + assertFalse(matcher.matches()); + } + + @Test + public void testWithFuzzyRegex() { + String fuzzyRegex = "/[a-z]+"; + Pattern computePattern = RegexPatternUtils.computePattern(fuzzyRegex); + assertNotNull(computePattern); + + Matcher matcher = computePattern.matcher("/hello"); + assertTrue(matcher.matches()); + matcher = computePattern.matcher("hello"); + assertFalse(matcher.matches()); + matcher = computePattern.matcher("Hello"); + assertFalse(matcher.matches()); + + fuzzyRegex = "[a-z]+/"; + computePattern = RegexPatternUtils.computePattern(fuzzyRegex); + matcher = computePattern.matcher("hello/"); + assertTrue(matcher.matches()); + matcher = computePattern.matcher("/hello"); + assertFalse(matcher.matches()); + matcher = computePattern.matcher("hello"); + assertFalse(matcher.matches()); + matcher = computePattern.matcher("Hello"); + assertFalse(matcher.matches()); + + // equals to pattern \\[a-z]+\\ -> searching for a word delimited by '\' + fuzzyRegex = "\\\\[a-z]+\\\\"; + computePattern = RegexPatternUtils.computePattern(fuzzyRegex); + // equals to '\hello\' + matcher = computePattern.matcher("\\hello\\"); + assertTrue(matcher.matches()); + matcher = computePattern.matcher("/hello"); + assertFalse(matcher.matches()); + matcher = computePattern.matcher("hello"); + assertFalse(matcher.matches()); + matcher = computePattern.matcher("Hello"); + assertFalse(matcher.matches()); + + // equals to pattern /[a-z]+/ -> searching for a string delimited by '/' + fuzzyRegex = "\\/[a-z]+\\/"; + computePattern = RegexPatternUtils.computePattern(fuzzyRegex); + matcher = computePattern.matcher("/hello/"); + assertTrue(matcher.matches()); + matcher = computePattern.matcher("/hello"); + assertFalse(matcher.matches()); + matcher = computePattern.matcher("hello"); + assertFalse(matcher.matches()); + matcher = computePattern.matcher("Hello"); + assertFalse(matcher.matches()); + } + + @Test + public void testInvalidRegex() { + String invalidSensitive = "[a-z+"; + assertThrows(PatternSyntaxException.class, () -> RegexPatternUtils.computePattern(invalidSensitive)); + + String invalidRange = "a{1-"; + assertThrows(PatternSyntaxException.class, () -> RegexPatternUtils.computePattern(invalidRange)); + + String invalidGroupPattern = "(abc"; + assertThrows(PatternSyntaxException.class, () -> RegexPatternUtils.computePattern(invalidGroupPattern)); + + String emptyPattern = ""; + Pattern computePattern = RegexPatternUtils.computePattern(emptyPattern); + assertNull(computePattern); + + String blankPattern = " "; + computePattern = RegexPatternUtils.computePattern(blankPattern); + assertNull(computePattern); + + String nullPattern = null; + computePattern = RegexPatternUtils.computePattern(nullPattern); + assertNull(computePattern); + } + + @Test + public void testMultiFlagRegex() { + String multilineSensitive = "/[a-z]+/gi"; + Pattern computePattern = RegexPatternUtils.computePattern(multilineSensitive); + assertNotNull(computePattern); + Matcher matcher = computePattern.matcher("hello"); + assertTrue(matcher.matches()); + matcher = computePattern.matcher("Hello"); + assertTrue(matcher.matches()); + + multilineSensitive = "/[a-z]+/gim"; + computePattern = RegexPatternUtils.computePattern(multilineSensitive); + assertNotNull(computePattern); + matcher = computePattern.matcher("Hello" + System.lineSeparator() + "Everyone"); + assertTrue(matcher.find()); + assertEquals("Hello", matcher.group()); + assertTrue(matcher.find()); + assertEquals("Everyone", matcher.group()); + + matcher = computePattern.matcher("hello"); + assertTrue(matcher.matches()); + matcher = computePattern.matcher("HELLO"); + assertTrue(matcher.matches()); + } +} 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 3306ced8f4..ca2d11c68d 100644 --- a/dspace-api/src/test/java/org/dspace/builder/AbstractBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/AbstractBuilder.java @@ -13,6 +13,7 @@ import java.util.List; import org.apache.commons.collections4.CollectionUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.dspace.alerts.service.SystemWideAlertService; import org.dspace.app.requestitem.factory.RequestItemServiceFactory; import org.dspace.app.requestitem.service.RequestItemService; import org.dspace.authorize.AuthorizeException; @@ -42,6 +43,7 @@ import org.dspace.eperson.factory.EPersonServiceFactory; import org.dspace.eperson.service.EPersonService; import org.dspace.eperson.service.GroupService; import org.dspace.eperson.service.RegistrationDataService; +import org.dspace.eperson.service.SubscribeService; import org.dspace.orcid.factory.OrcidServiceFactory; import org.dspace.orcid.service.OrcidHistoryService; import org.dspace.orcid.service.OrcidQueueService; @@ -49,6 +51,8 @@ import org.dspace.orcid.service.OrcidTokenService; import org.dspace.scripts.factory.ScriptServiceFactory; import org.dspace.scripts.service.ProcessService; import org.dspace.services.factory.DSpaceServicesFactory; +import org.dspace.supervision.factory.SupervisionOrderServiceFactory; +import org.dspace.supervision.service.SupervisionOrderService; import org.dspace.versioning.factory.VersionServiceFactory; import org.dspace.versioning.service.VersionHistoryService; import org.dspace.versioning.service.VersioningService; @@ -102,6 +106,10 @@ public abstract class AbstractBuilder { static OrcidHistoryService orcidHistoryService; static OrcidQueueService orcidQueueService; static OrcidTokenService orcidTokenService; + static SystemWideAlertService systemWideAlertService; + static SubscribeService subscribeService; + static SupervisionOrderService supervisionOrderService; + protected Context context; @@ -161,6 +169,10 @@ public abstract class AbstractBuilder { orcidHistoryService = OrcidServiceFactory.getInstance().getOrcidHistoryService(); orcidQueueService = OrcidServiceFactory.getInstance().getOrcidQueueService(); orcidTokenService = OrcidServiceFactory.getInstance().getOrcidTokenService(); + systemWideAlertService = DSpaceServicesFactory.getInstance().getServiceManager() + .getServicesByType(SystemWideAlertService.class).get(0); + subscribeService = ContentServiceFactory.getInstance().getSubscribeService(); + supervisionOrderService = SupervisionOrderServiceFactory.getInstance().getSupervisionOrderService(); } @@ -194,6 +206,9 @@ public abstract class AbstractBuilder { requestItemService = null; versioningService = null; orcidTokenService = null; + systemWideAlertService = null; + subscribeService = null; + supervisionOrderService = null; } diff --git a/dspace-api/src/test/java/org/dspace/builder/ItemBuilder.java b/dspace-api/src/test/java/org/dspace/builder/ItemBuilder.java index e729982e81..70dea309f2 100644 --- a/dspace-api/src/test/java/org/dspace/builder/ItemBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/ItemBuilder.java @@ -397,12 +397,17 @@ public class ItemBuilder extends AbstractDSpaceObjectBuilder { try (Context c = new Context()) { c.setDispatcher("noindex"); c.turnOffAuthorisationSystem(); + // If the workspaceItem used to create this item still exists, delete it + workspaceItem = c.reloadEntity(workspaceItem); + if (workspaceItem != null) { + workspaceItemService.deleteAll(c, workspaceItem); + } // Ensure object and any related objects are reloaded before checking to see what needs cleanup item = c.reloadEntity(item); if (item != null) { delete(c, item); - c.complete(); } + c.complete(); } } diff --git a/dspace-api/src/test/java/org/dspace/builder/SubscribeBuilder.java b/dspace-api/src/test/java/org/dspace/builder/SubscribeBuilder.java new file mode 100644 index 0000000000..40e890a8c9 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/builder/SubscribeBuilder.java @@ -0,0 +1,111 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.builder; + +import java.sql.SQLException; +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.core.Context; +import org.dspace.discovery.SearchServiceException; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.Subscription; +import org.dspace.eperson.SubscriptionParameter; +import org.dspace.eperson.service.SubscribeService; + +public class SubscribeBuilder extends AbstractBuilder { + + /* Log4j logger*/ + private static final Logger log = LogManager.getLogger(); + + private Subscription subscription; + + protected SubscribeBuilder(Context context) { + super(context); + } + + @Override + protected SubscribeService getService() { + return subscribeService; + } + + @Override + public void cleanup() throws Exception { + try (Context c = new Context()) { + c.turnOffAuthorisationSystem(); + // Ensure object and any related objects are reloaded before checking to see what needs cleanup + subscription = c.reloadEntity(subscription); + if (subscription != null) { + delete(c, subscription); + } + c.complete(); + indexingService.commit(); + } + } + + public static void deleteSubscription(int id) throws Exception { + try (Context c = new Context()) { + c.turnOffAuthorisationSystem(); + Subscription subscription = subscribeService.findById(c, id); + if (Objects.nonNull(subscription)) { + try { + subscribeService.deleteSubscription(c, subscription); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + c.complete(); + } + indexingService.commit(); + } + + @Override + public Subscription build() { + try { + + context.dispatchEvents(); + + indexingService.commit(); + } catch (SearchServiceException e) { + log.error(e); + } + return subscription; + } + + public static SubscribeBuilder subscribeBuilder(final Context context, String type, DSpaceObject dSpaceObject, + EPerson ePerson, List subscriptionParameterList) { + SubscribeBuilder builder = new SubscribeBuilder(context); + return builder.create(context, type, dSpaceObject, ePerson, subscriptionParameterList); + } + + private SubscribeBuilder create(Context context, String type, DSpaceObject dSpaceObject, EPerson ePerson, + List subscriptionParameterList) { + try { + + this.context = context; + this.subscription = subscribeService.subscribe(context, ePerson, dSpaceObject, + subscriptionParameterList, type); + + } catch (SQLException | AuthorizeException e) { + log.warn("Failed to create the Subscription", e); + } + return this; + } + + @Override + public void delete(Context c, Subscription dso) throws Exception { + if (Objects.nonNull(dso)) { + getService().deleteSubscription(c, dso); + } + } + +} \ No newline at end of file diff --git a/dspace-api/src/test/java/org/dspace/builder/SupervisionOrderBuilder.java b/dspace-api/src/test/java/org/dspace/builder/SupervisionOrderBuilder.java new file mode 100644 index 0000000000..849e4cd4ff --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/builder/SupervisionOrderBuilder.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.builder; + +import java.sql.SQLException; +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.Item; +import org.dspace.core.Context; +import org.dspace.eperson.Group; +import org.dspace.supervision.SupervisionOrder; +import org.dspace.supervision.service.SupervisionOrderService; + +/** + * Abstract builder to construct SupervisionOrder Objects + * + * @author Mohamed Eskander (mohamed.eskander at 4science dot it) + */ +public class SupervisionOrderBuilder + extends AbstractBuilder { + + private static final Logger log = LogManager.getLogger(SupervisionOrderBuilder.class); + + private SupervisionOrder supervisionOrder; + + protected SupervisionOrderBuilder(Context context) { + super(context); + } + + public static SupervisionOrderBuilder createSupervisionOrder(Context context, Item item, Group group) { + SupervisionOrderBuilder builder = new SupervisionOrderBuilder(context); + return builder.create(context, item, group); + } + + private SupervisionOrderBuilder create(Context context, Item item, Group group) { + try { + this.context = context; + this.supervisionOrder = getService().create(context, item, group); + } catch (Exception e) { + log.error("Error in SupervisionOrderBuilder.create(..), error: ", e); + } + return this; + } + + @Override + public void cleanup() throws Exception { + delete(supervisionOrder); + } + + @Override + public SupervisionOrder build() throws SQLException, AuthorizeException { + try { + getService().update(context, supervisionOrder); + context.dispatchEvents(); + indexingService.commit(); + } catch (Exception e) { + log.error("Error in SupervisionOrderBuilder.build(), error: ", e); + } + return supervisionOrder; + } + + @Override + public void delete(Context context, SupervisionOrder supervisionOrder) throws Exception { + if (Objects.nonNull(supervisionOrder)) { + getService().delete(context, supervisionOrder); + } + } + + @Override + protected SupervisionOrderService getService() { + return supervisionOrderService; + } + + private void delete(SupervisionOrder supervisionOrder) throws Exception { + try (Context context = new Context()) { + context.turnOffAuthorisationSystem(); + context.setDispatcher("noindex"); + SupervisionOrder attached = context.reloadEntity(supervisionOrder); + if (attached != null) { + getService().delete(context, attached); + } + context.complete(); + indexingService.commit(); + } + } +} diff --git a/dspace-api/src/test/java/org/dspace/builder/SystemWideAlertBuilder.java b/dspace-api/src/test/java/org/dspace/builder/SystemWideAlertBuilder.java new file mode 100644 index 0000000000..cb64898152 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/builder/SystemWideAlertBuilder.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.builder; + +import java.sql.SQLException; +import java.util.Date; + +import org.dspace.alerts.AllowSessionsEnum; +import org.dspace.alerts.SystemWideAlert; +import org.dspace.alerts.service.SystemWideAlertService; +import org.dspace.authorize.AuthorizeException; +import org.dspace.core.Context; + +public class SystemWideAlertBuilder extends AbstractBuilder { + + private SystemWideAlert systemWideAlert; + + protected SystemWideAlertBuilder(Context context) { + super(context); + } + + public static SystemWideAlertBuilder createSystemWideAlert(Context context, String message) + throws SQLException, AuthorizeException { + SystemWideAlertBuilder systemWideAlertBuilder = new SystemWideAlertBuilder(context); + return systemWideAlertBuilder.create(context, message, AllowSessionsEnum.ALLOW_ALL_SESSIONS, null, false); + } + + private SystemWideAlertBuilder create(Context context, String message, AllowSessionsEnum allowSessionsType, + Date countdownTo, boolean active) + throws SQLException, AuthorizeException { + this.context = context; + this.systemWideAlert = systemWideAlertService.create(context, message, allowSessionsType, countdownTo, active); + return this; + } + + public SystemWideAlertBuilder withAllowSessions(AllowSessionsEnum allowSessionsType) { + systemWideAlert.setAllowSessions(allowSessionsType); + return this; + } + + public SystemWideAlertBuilder withCountdownDate(Date countdownTo) { + systemWideAlert.setCountdownTo(countdownTo); + return this; + } + + public SystemWideAlertBuilder isActive(boolean isActive) { + systemWideAlert.setActive(isActive); + return this; + } + + @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 + systemWideAlert = c.reloadEntity(systemWideAlert); + if (systemWideAlert != null) { + delete(c, systemWideAlert); + } + c.complete(); + indexingService.commit(); + } + } + + @Override + public SystemWideAlert build() { + try { + systemWideAlertService.update(context, systemWideAlert); + context.dispatchEvents(); + indexingService.commit(); + } catch (Exception e) { + return null; + } + return systemWideAlert; + } + + + @Override + protected SystemWideAlertService getService() { + return systemWideAlertService; + } + + public void delete(Context c, SystemWideAlert alert) throws Exception { + if (alert != null) { + getService().delete(c, alert); + } + } +} diff --git a/dspace-api/src/test/java/org/dspace/content/SupervisedItemTest.java b/dspace-api/src/test/java/org/dspace/content/SupervisedItemTest.java deleted file mode 100644 index aece739f25..0000000000 --- a/dspace-api/src/test/java/org/dspace/content/SupervisedItemTest.java +++ /dev/null @@ -1,200 +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.content; - -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.CoreMatchers.notNullValue; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - -import java.io.IOException; -import java.sql.SQLException; -import java.util.List; -import java.util.UUID; - -import org.apache.logging.log4j.Logger; -import org.dspace.AbstractUnitTest; -import org.dspace.authorize.AuthorizeException; -import org.dspace.content.factory.ContentServiceFactory; -import org.dspace.content.service.CollectionService; -import org.dspace.content.service.CommunityService; -import org.dspace.content.service.SupervisedItemService; -import org.dspace.content.service.WorkspaceItemService; -import org.dspace.core.Context; -import org.dspace.eperson.EPerson; -import org.dspace.eperson.Group; -import org.dspace.eperson.factory.EPersonServiceFactory; -import org.dspace.eperson.service.EPersonService; -import org.dspace.eperson.service.GroupService; -import org.dspace.eperson.service.SupervisorService; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -/** - * @author pvillega - */ -public class SupervisedItemTest extends AbstractUnitTest { - - /** - * log4j category - */ - private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(SupervisedItemTest.class); - - protected CommunityService communityService = ContentServiceFactory.getInstance().getCommunityService(); - protected CollectionService collectionService = ContentServiceFactory.getInstance().getCollectionService(); - protected EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService(); - protected GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); - protected SupervisedItemService supervisedItemService = ContentServiceFactory.getInstance() - .getSupervisedItemService(); - protected WorkspaceItemService workspaceItemService = ContentServiceFactory.getInstance().getWorkspaceItemService(); - protected SupervisorService supervisorService = EPersonServiceFactory.getInstance().getSupervisorService(); - - protected UUID communityId; - protected UUID groupId; - protected int workspaceItemId; - - - /** - * This method will be run before every test as per @Before. It will - * initialize resources required for the tests. - * - * Other methods can be annotated with @Before here or in subclasses - * but no execution order is guaranteed - */ - @Before - @Override - public void init() { - super.init(); - try { - //we have to create a new community in the database - context.turnOffAuthorisationSystem(); - Community owningCommunity = communityService.create(null, context); - Collection collection = collectionService.create(context, owningCommunity); - WorkspaceItem si = workspaceItemService.create(context, collection, false); - Group gr = groupService.create(context); - EPerson currentUser = context.getCurrentUser(); - groupService.addMember(context, gr, currentUser); - groupService.update(context, gr); - - //set a supervisor as editor - supervisorService.add(context, gr, si, 1); - - communityId = owningCommunity.getID(); - workspaceItemId = si.getID(); - groupId = gr.getID(); - - //we need to commit the changes so we don't block the table for testing - context.restoreAuthSystemState(); - context.complete(); - context = new Context(); - context.setCurrentUser(currentUser); - } catch (AuthorizeException ex) { - log.error("Authorization Error in init", ex); - fail("Authorization Error in init: " + ex.getMessage()); - } catch (SQLException ex) { - log.error("SQL Error in init", ex); - fail("SQL Error in init"); - } - } - - /** - * This method will be run after every test as per @After. It will - * clean resources initialized by the @Before methods. - * - * Other methods can be annotated with @After here or in subclasses - * but no execution order is guaranteed - */ - @After - @Override - public void destroy() { - try { - context.turnOffAuthorisationSystem(); - communityService.delete(context, communityService.find(context, communityId)); - context.restoreAuthSystemState(); - } catch (SQLException | AuthorizeException | IOException ex) { - log.error("SQL Error in destroy", ex); - fail("SQL Error in destroy: " + ex.getMessage()); - } - super.destroy(); - } - - /** - * Test of getAll method, of class SupervisedItem. - */ - @Test - public void testGetAll() throws Exception { - List found = supervisedItemService.getAll(context); - assertThat("testGetAll 0", found, notNullValue()); - assertTrue("testGetAll 1", found.size() >= 1); - - boolean added = false; - for (WorkspaceItem sia : found) { - if (sia.getID() == workspaceItemId) { - added = true; - } - } - assertTrue("testGetAll 2", added); - } - - /** - * Test of getSupervisorGroups method, of class SupervisedItem. - */ - @Test - public void testGetSupervisorGroups_Context_int() throws Exception { - List found = workspaceItemService.find(context, workspaceItemId).getSupervisorGroups(); - assertThat("testGetSupervisorGroups_Context_int 0", found, notNullValue()); - assertTrue("testGetSupervisorGroups_Context_int 1", found.size() == 1); - assertThat("testGetSupervisorGroups_Context_int 2", found.get(0).getID(), equalTo(groupId)); - } - - /** - * Test of getSupervisorGroups method, of class SupervisedItem. - */ - @Test - public void testGetSupervisorGroups_0args() throws Exception { - List found = workspaceItemService.find(context, workspaceItemId).getSupervisorGroups(); - assertThat("testGetSupervisorGroups_0args 0", found, notNullValue()); - assertTrue("testGetSupervisorGroups_0args 1", found.size() == 1); - - boolean added = false; - for (Group g : found) { - if (g.getID().equals(groupId)) { - added = true; - } - } - assertTrue("testGetSupervisorGroups_0args 2", added); - } - - /** - * Test of findbyEPerson method, of class SupervisedItem. - */ - @Test - public void testFindbyEPerson() throws Exception { - context.turnOffAuthorisationSystem(); - List found = supervisedItemService.findbyEPerson(context, ePersonService.create(context)); - assertThat("testFindbyEPerson 0", found, notNullValue()); - assertTrue("testFindbyEPerson 1", found.size() == 0); - - found = supervisedItemService.findbyEPerson(context, context.getCurrentUser()); - assertThat("testFindbyEPerson 2", found, notNullValue()); - assertTrue("testFindbyEPerson 3", found.size() >= 1); - - boolean added = false; - for (WorkspaceItem sia : found) { - if (sia.getID() == workspaceItemId) { - added = true; - } - } - assertTrue("testFindbyEPerson 4", added); - - context.restoreAuthSystemState(); - } - -} diff --git a/dspace-api/src/test/java/org/dspace/content/authority/DSpaceControlledVocabularyTest.java b/dspace-api/src/test/java/org/dspace/content/authority/DSpaceControlledVocabularyTest.java index 7ade9c582d..255b070e5e 100644 --- a/dspace-api/src/test/java/org/dspace/content/authority/DSpaceControlledVocabularyTest.java +++ b/dspace-api/src/test/java/org/dspace/content/authority/DSpaceControlledVocabularyTest.java @@ -86,7 +86,7 @@ public class DSpaceControlledVocabularyTest extends AbstractDSpaceTest { CoreServiceFactory.getInstance().getPluginService().getNamedPlugin(Class.forName(PLUGIN_INTERFACE), "farm"); assertNotNull(instance); Choices result = instance.getMatches(text, start, limit, locale); - assertEquals("the farm::north 40", result.values[0].value); + assertEquals("north 40", result.values[0].value); } /** diff --git a/dspace-api/src/test/java/org/dspace/content/service/ItemServiceTest.java b/dspace-api/src/test/java/org/dspace/content/service/ItemServiceTest.java index 267d66ac2f..50b4d3f3b4 100644 --- a/dspace-api/src/test/java/org/dspace/content/service/ItemServiceTest.java +++ b/dspace-api/src/test/java/org/dspace/content/service/ItemServiceTest.java @@ -9,9 +9,13 @@ package org.dspace.content.service; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasSize; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; import static org.junit.Assert.fail; +import java.io.ByteArrayInputStream; +import java.io.InputStream; import java.sql.SQLException; import java.util.Comparator; import java.util.List; @@ -19,13 +23,20 @@ import java.util.stream.Collectors; import org.apache.logging.log4j.Logger; import org.dspace.AbstractIntegrationTestWithDatabase; +import org.dspace.app.requestitem.RequestItem; import org.dspace.authorize.AuthorizeException; +import org.dspace.authorize.ResourcePolicy; +import org.dspace.builder.BitstreamBuilder; import org.dspace.builder.CollectionBuilder; import org.dspace.builder.CommunityBuilder; import org.dspace.builder.EntityTypeBuilder; +import org.dspace.builder.GroupBuilder; import org.dspace.builder.ItemBuilder; import org.dspace.builder.RelationshipBuilder; import org.dspace.builder.RelationshipTypeBuilder; +import org.dspace.builder.RequestItemBuilder; +import org.dspace.builder.ResourcePolicyBuilder; +import org.dspace.content.Bitstream; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.EntityType; @@ -35,6 +46,8 @@ import org.dspace.content.Relationship; import org.dspace.content.RelationshipType; import org.dspace.content.WorkspaceItem; import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.core.Constants; +import org.dspace.eperson.Group; import org.dspace.versioning.Version; import org.dspace.versioning.factory.VersionServiceFactory; import org.dspace.versioning.service.VersioningService; @@ -100,6 +113,177 @@ public class ItemServiceTest extends AbstractIntegrationTestWithDatabase { } } + @Test + public void preserveMetadataOrder() throws Exception { + context.turnOffAuthorisationSystem(); + itemService + .addMetadata( + context, item, dcSchema, contributorElement, authorQualifier, null, "test, one", null, 0, 2 + ); + MetadataValue placeZero = + itemService + .addMetadata( + context, item, dcSchema, contributorElement, authorQualifier, null, "test, two", null, 0, 0 + ); + itemService + .addMetadata( + context, item, dcSchema, contributorElement, authorQualifier, null, "test, three", null, 0, 1 + ); + + context.commit(); + context.restoreAuthSystemState(); + + // check the correct order using default method `getMetadata` + List defaultMetadata = + this.itemService.getMetadata(item, dcSchema, contributorElement, authorQualifier, Item.ANY); + + assertThat(defaultMetadata,hasSize(3)); + + assertMetadataValue( + authorQualifier, contributorElement, dcSchema, "test, two", null, 0, defaultMetadata.get(0) + ); + assertMetadataValue( + authorQualifier, contributorElement, dcSchema, "test, three", null, 1, defaultMetadata.get(1) + ); + assertMetadataValue( + authorQualifier, contributorElement, dcSchema, "test, one", null, 2, defaultMetadata.get(2) + ); + + // check the correct order using the method `getMetadata` without virtual fields + List nonVirtualMetadatas = + this.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); + + assertThat(nonVirtualMetadatas,hasSize(3)); + + assertMetadataValue( + authorQualifier, contributorElement, dcSchema, "test, two", null, 0, nonVirtualMetadatas.get(0) + ); + assertMetadataValue( + authorQualifier, contributorElement, dcSchema, "test, three", null, 1, nonVirtualMetadatas.get(1) + ); + assertMetadataValue( + authorQualifier, contributorElement, dcSchema, "test, one", null, 2, nonVirtualMetadatas.get(2) + ); + + context.turnOffAuthorisationSystem(); + + 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 + ); + // now just remove first metadata + this.itemService.removeMetadataValues(context, item, List.of(placeZero)); + // now just add one metadata to place 0 + this.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); + + // check correct places + assertThat(defaultMetadata,hasSize(4)); + + assertMetadataValue( + authorQualifier, contributorElement, dcSchema, "test, new", null, 0, defaultMetadata.get(0) + ); + assertMetadataValue( + authorQualifier, contributorElement, dcSchema, "test, three", null, 1, defaultMetadata.get(1) + ); + assertMetadataValue( + authorQualifier, contributorElement, dcSchema, "test, one", null, 2, defaultMetadata.get(2) + ); + assertMetadataValue( + authorQualifier, contributorElement, dcSchema, "test, latest", null, 3, defaultMetadata.get(3) + ); + + // check metadata using nonVirtualMethod + nonVirtualMetadatas = + this.itemService.getMetadata(item, dcSchema, contributorElement, authorQualifier, Item.ANY, false); + + // check correct places + assertThat(nonVirtualMetadatas,hasSize(4)); + + assertMetadataValue( + authorQualifier, contributorElement, dcSchema, "test, new", null, 0, nonVirtualMetadatas.get(0) + ); + assertMetadataValue( + authorQualifier, contributorElement, dcSchema, "test, three", null, 1, nonVirtualMetadatas.get(1) + ); + assertMetadataValue( + authorQualifier, contributorElement, dcSchema, "test, one", null, 2, nonVirtualMetadatas.get(2) + ); + assertMetadataValue( + authorQualifier, contributorElement, dcSchema, "test, latest", null, 3, nonVirtualMetadatas.get(3) + ); + + // check both lists + assertThat(defaultMetadata.size(), equalTo(nonVirtualMetadatas.size())); + assertThat(defaultMetadata.get(0), equalTo(nonVirtualMetadatas.get(0))); + assertThat(defaultMetadata.get(1), equalTo(nonVirtualMetadatas.get(1))); + assertThat(defaultMetadata.get(2), equalTo(nonVirtualMetadatas.get(2))); + assertThat(defaultMetadata.get(3), equalTo(nonVirtualMetadatas.get(3))); + + context.commit(); + context.restoreAuthSystemState(); + + item = context.reloadEntity(item); + + // check after commit + defaultMetadata = + this.itemService.getMetadata(item, dcSchema, contributorElement, authorQualifier, Item.ANY); + + // check correct places + assertThat(defaultMetadata,hasSize(4)); + + assertMetadataValue( + authorQualifier, contributorElement, dcSchema, "test, new", null, 0, defaultMetadata.get(0) + ); + assertMetadataValue( + authorQualifier, contributorElement, dcSchema, "test, three", null, 1, defaultMetadata.get(1) + ); + assertMetadataValue( + authorQualifier, contributorElement, dcSchema, "test, one", null, 2, defaultMetadata.get(2) + ); + assertMetadataValue( + authorQualifier, contributorElement, dcSchema, "test, latest", null, 3, defaultMetadata.get(3) + ); + + // check metadata using nonVirtualMethod + nonVirtualMetadatas = + this.itemService.getMetadata(item, dcSchema, contributorElement, authorQualifier, Item.ANY, false); + + // check correct places + assertThat(nonVirtualMetadatas,hasSize(4)); + + assertMetadataValue( + authorQualifier, contributorElement, dcSchema, "test, new", null, 0, nonVirtualMetadatas.get(0) + ); + assertMetadataValue( + authorQualifier, contributorElement, dcSchema, "test, three", null, 1, nonVirtualMetadatas.get(1) + ); + assertMetadataValue( + authorQualifier, contributorElement, dcSchema, "test, one", null, 2, nonVirtualMetadatas.get(2) + ); + assertMetadataValue( + authorQualifier, contributorElement, dcSchema, "test, latest", null, 3, nonVirtualMetadatas.get(3) + ); + + // check both lists + assertThat(defaultMetadata.size(), equalTo(nonVirtualMetadatas.size())); + assertThat(defaultMetadata.get(0), equalTo(nonVirtualMetadatas.get(0))); + assertThat(defaultMetadata.get(1), equalTo(nonVirtualMetadatas.get(1))); + assertThat(defaultMetadata.get(2), equalTo(nonVirtualMetadatas.get(2))); + assertThat(defaultMetadata.get(3), equalTo(nonVirtualMetadatas.get(3))); + + } + @Test public void InsertAndMoveMetadataShiftPlaceTest() throws Exception { context.turnOffAuthorisationSystem(); @@ -473,6 +657,101 @@ public class ItemServiceTest extends AbstractIntegrationTestWithDatabase { context.restoreAuthSystemState(); } + @Test + public void testFindItemsWithEditNoRights() throws Exception { + context.setCurrentUser(eperson); + List result = itemService.findItemsWithEdit(context, 0, 10); + int count = itemService.countItemsWithEdit(context); + assertThat(result.size(), equalTo(0)); + assertThat(count, equalTo(0)); + } + + @Test + public void testFindAndCountItemsWithEditEPerson() throws Exception { + ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context) + .withUser(eperson) + .withDspaceObject(item) + .withAction(Constants.WRITE) + .build(); + context.setCurrentUser(eperson); + List result = itemService.findItemsWithEdit(context, 0, 10); + int count = itemService.countItemsWithEdit(context); + assertThat(result.size(), equalTo(1)); + assertThat(count, equalTo(1)); + } + + @Test + public void testFindAndCountItemsWithAdminEPerson() throws Exception { + ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context) + .withUser(eperson) + .withDspaceObject(item) + .withAction(Constants.ADMIN) + .build(); + context.setCurrentUser(eperson); + List result = itemService.findItemsWithEdit(context, 0, 10); + int count = itemService.countItemsWithEdit(context); + assertThat(result.size(), equalTo(1)); + assertThat(count, equalTo(1)); + } + + @Test + public void testFindAndCountItemsWithEditGroup() throws Exception { + context.turnOffAuthorisationSystem(); + Group group = GroupBuilder.createGroup(context) + .addMember(eperson) + .build(); + context.restoreAuthSystemState(); + + ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context) + .withGroup(group) + .withDspaceObject(item) + .withAction(Constants.WRITE) + .build(); + context.setCurrentUser(eperson); + List result = itemService.findItemsWithEdit(context, 0, 10); + int count = itemService.countItemsWithEdit(context); + assertThat(result.size(), equalTo(1)); + assertThat(count, equalTo(1)); + } + + @Test + public void testFindAndCountItemsWithAdminGroup() throws Exception { + context.turnOffAuthorisationSystem(); + Group group = GroupBuilder.createGroup(context) + .addMember(eperson) + .build(); + context.restoreAuthSystemState(); + + ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context) + .withGroup(group) + .withDspaceObject(item) + .withAction(Constants.ADMIN) + .build(); + context.setCurrentUser(eperson); + List result = itemService.findItemsWithEdit(context, 0, 10); + int count = itemService.countItemsWithEdit(context); + assertThat(result.size(), equalTo(1)); + assertThat(count, equalTo(1)); + } + + @Test + public void testRemoveItemThatHasRequests() throws Exception { + context.turnOffAuthorisationSystem(); + Item item = ItemBuilder.createItem(context, collection1) + .withTitle("Test") + .build(); + InputStream is = new ByteArrayInputStream(new byte[0]); + Bitstream bitstream = BitstreamBuilder.createBitstream(context, item, is) + .build(); + RequestItem requestItem = RequestItemBuilder.createRequestItem(context, item, bitstream) + .build(); + + itemService.delete(context, item); + context.dispatchEvents(); + context.restoreAuthSystemState(); + + assertNull(itemService.find(context, item.getID())); + } private void assertMetadataValue(String authorQualifier, String contributorElement, String dcSchema, String value, String authority, int place, MetadataValue metadataValue) { assertThat(metadataValue.getValue(), equalTo(value)); 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 383f87bbd7..2a07799dee 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 @@ -17,10 +17,12 @@ import org.dspace.builder.CommunityBuilder; import org.dspace.builder.ItemBuilder; import org.dspace.content.Collection; import org.dspace.content.Item; +import org.dspace.core.factory.CoreServiceFactory; import org.dspace.curate.Curator; import org.dspace.identifier.VersionedHandleIdentifierProviderWithCanonicalHandles; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; +import org.junit.After; import org.junit.Test; /** @@ -37,10 +39,11 @@ public class CreateMissingIdentifiersIT @Test public void testPerform() throws IOException { - ConfigurationService configurationService - = DSpaceServicesFactory.getInstance().getConfigurationService(); - configurationService.setProperty(P_TASK_DEF, null); - configurationService.addPropertyValue(P_TASK_DEF, + // Must remove any cached named plugins before creating a new one + CoreServiceFactory.getInstance().getPluginService().clearNamedPluginClasses(); + ConfigurationService configurationService = kernelImpl.getConfigurationService(); + // Define a new task dynamically + configurationService.setProperty(P_TASK_DEF, CreateMissingIdentifiers.class.getCanonicalName() + " = " + TASK_NAME); Curator curator = new Curator(); @@ -48,11 +51,11 @@ public class CreateMissingIdentifiersIT context.setCurrentUser(admin); parentCommunity = CommunityBuilder.createCommunity(context) - .build(); + .build(); Collection collection = CollectionBuilder.createCollection(context, parentCommunity) - .build(); + .build(); Item item = ItemBuilder.createItem(context, collection) - .build(); + .build(); /* * Curate with regular test configuration -- should succeed. @@ -76,4 +79,11 @@ public class CreateMissingIdentifiersIT assertEquals("Curation should fail", Curator.CURATE_ERROR, curator.getStatus(TASK_NAME)); } + + @Override + @After + public void destroy() throws Exception { + super.destroy(); + DSpaceServicesFactory.getInstance().getServiceManager().getApplicationContext().refresh(); + } } diff --git a/dspace-api/src/test/java/org/dspace/eperson/SubscribeServiceIT.java b/dspace-api/src/test/java/org/dspace/eperson/SubscribeServiceIT.java new file mode 100644 index 0000000000..945dd481d0 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/eperson/SubscribeServiceIT.java @@ -0,0 +1,417 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ + +package org.dspace.eperson; + +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.dspace.builder.SubscribeBuilder.subscribeBuilder; +import static org.dspace.matcher.SubscribeMatcher.matches; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertEquals; + +import java.sql.SQLException; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import org.apache.commons.lang.StringUtils; +import org.dspace.AbstractIntegrationTestWithDatabase; +import org.dspace.authorize.AuthorizeException; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.EPersonBuilder; +import org.dspace.builder.SubscribeBuilder; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.DSpaceObject; +import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.eperson.service.SubscribeService; +import org.junit.Before; +import org.junit.Test; + +public class SubscribeServiceIT extends AbstractIntegrationTestWithDatabase { + + private final SubscribeService subscribeService = ContentServiceFactory.getInstance().getSubscribeService(); + + private Collection firstCollection; + private Collection secondCollection; + + @Before + public void init() throws Exception { + context.turnOffAuthorisationSystem(); + Community parentCommunity = CommunityBuilder.createCommunity(context).build(); + firstCollection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("First Collection").build(); + secondCollection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Second Collection").build(); + context.restoreAuthSystemState(); + } + + @Test + public void findAllWithoutAndWithLimit() throws Exception { + + String resourceType = "Collection"; + + EPerson subscribingUser = context.getCurrentUser(); + + createSubscription("content", firstCollection, subscribingUser, weekly()); + createSubscription("content", secondCollection, subscribingUser, daily(), annual()); + + // unlimited search returns all subscriptions + + List subscriptions = subscribeService.findAll(context, resourceType, 10, 0); + assertThat(subscriptions, containsInAnyOrder( + asList(matches(firstCollection, subscribingUser, "content", + singletonList(weekly())), + matches(secondCollection, subscribingUser, "content", + asList(daily(), annual()))))); + + // limited search returns first + + subscriptions = subscribeService.findAll(context, resourceType, 1, 0); + + assertThat(subscriptions, containsInAnyOrder( + singletonList(matches(firstCollection, subscribingUser, "content", + singletonList(weekly()))))); + + // search with offset returns second + + subscriptions = subscribeService.findAll(context, resourceType, 100, 1); + + assertThat(subscriptions, containsInAnyOrder( + singletonList(matches(secondCollection, subscribingUser, "content", + asList(daily(), annual()))))); + + // lookup without resource type + subscriptions = subscribeService.findAll(context, StringUtils.EMPTY, 100, 0); + + assertThat(subscriptions, containsInAnyOrder( + asList(matches(firstCollection, subscribingUser, "content", + singletonList(weekly())), + matches(secondCollection, subscribingUser, "content", + asList(daily(), annual()))))); + + } + + private static SubscriptionParameter annual() { + return createSubscriptionParameter("frequency", "A"); + } + + private static SubscriptionParameter daily() { + return createSubscriptionParameter("frequency", "D"); + } + + @Test(expected = Exception.class) + public void findAllWithInvalidResource() throws Exception { + + String resourceType = "INVALID"; + Integer limit = 10; + Integer offset = 0; + + createSubscription("content", firstCollection, context.getCurrentUser(), + weekly()); + + subscribeService.findAll(context, resourceType, limit, offset); + + } + + @Test + public void newSubscriptionCreatedByAdmin() throws Exception { + + SubscriptionParameter monthly = createSubscriptionParameter("frequency", "M"); + + List parameters = Collections.singletonList( + monthly); + + EPerson currentUser = context.getCurrentUser(); + context.setCurrentUser(admin); + Subscription subscription = subscribeService.subscribe(context, eperson, + firstCollection, parameters, "content"); + + assertThat(subscription, is(matches(firstCollection, eperson, + "content", singletonList(monthly)))); + + SubscribeBuilder.deleteSubscription(subscription.getID()); + context.setCurrentUser(currentUser); + + } + + @Test + public void newSubscriptionCreatedByCurrentUser() throws Exception { + + EPerson currentUser = context.getCurrentUser(); + Subscription subscription = subscribeService.subscribe(context, currentUser, + secondCollection, + asList(daily(), weekly()), "content"); + + assertThat(subscription, matches(secondCollection, currentUser, "content", + asList(daily(), weekly()))); + + SubscribeBuilder.deleteSubscription(subscription.getID()); + } + + @Test(expected = AuthorizeException.class) + public void nonAdminDifferentUserTriesToSubscribe() throws Exception { + context.turnOffAuthorisationSystem(); + EPerson notAdmin = EPersonBuilder.createEPerson(context).withEmail("not-admin@example.com").build(); + context.restoreAuthSystemState(); + EPerson currentUser = context.getCurrentUser(); + context.setCurrentUser(notAdmin); + try { + subscribeService.subscribe(context, admin, firstCollection, + singletonList( + daily()), "content"); + } finally { + context.setCurrentUser(currentUser); + } + + } + + @Test + public void unsubscribeByAdmin() throws Exception { + + EPerson subscribingUser = context.getCurrentUser(); + createSubscription("content", secondCollection, subscribingUser, + weekly()); + + List subscriptions = + subscribeService.findSubscriptionsByEPersonAndDso(context, subscribingUser, + secondCollection, 100, 0); + + assertEquals(subscriptions.size(), 1); + + context.setCurrentUser(admin); + subscribeService.unsubscribe(context, subscribingUser, secondCollection); + context.setCurrentUser(subscribingUser); + + subscriptions = + subscribeService.findSubscriptionsByEPersonAndDso(context, subscribingUser, + secondCollection, 100, 0); + + assertEquals(subscriptions.size(), 0); + } + + @Test + public void subscribingUserUnsubscribesTheirSubscription() throws Exception { + + EPerson subscribingUser = context.getCurrentUser(); + createSubscription("content", secondCollection, subscribingUser, + weekly()); + + List subscriptions = + subscribeService.findSubscriptionsByEPersonAndDso(context, subscribingUser, + secondCollection, 100, 0); + + assertEquals(subscriptions.size(), 1); + + + subscribeService.unsubscribe(context, subscribingUser, secondCollection); + + subscriptions = + subscribeService.findSubscriptionsByEPersonAndDso(context, subscribingUser, + secondCollection, 100, 0); + + assertEquals(subscriptions.size(), 0); + } + + @Test(expected = AuthorizeException.class) + public void nonAdminDifferentUserTriesToUnSubscribeAnotherUser() throws Exception { + EPerson subscribingUser = context.getCurrentUser(); + Subscription subscription = createSubscription("content", secondCollection, subscribingUser, + weekly()); + + context.turnOffAuthorisationSystem(); + EPerson nonAdmin = EPersonBuilder.createEPerson(context).build(); + context.restoreAuthSystemState(); + + + try { + context.setCurrentUser(nonAdmin); + subscribeService.unsubscribe(context, subscribingUser, secondCollection); + } finally { + context.setCurrentUser(subscribingUser); + SubscribeBuilder.deleteSubscription(subscription.getID()); + } + + } + + @Test + public void updateSubscription() throws Exception { + + EPerson currentUser = context.getCurrentUser(); + Subscription subscription = createSubscription("original", + firstCollection, currentUser, + createSubscriptionParameter("frequency", "M")); + + String updatedType = "updated"; + List updatedParameters = Collections.singletonList( + annual() + ); + + try { + Subscription updated = subscribeService.updateSubscription(context, subscription.getID(), + updatedType, updatedParameters); + + assertThat(updated, is(matches(firstCollection, currentUser, updatedType, updatedParameters))); + + List subscriptions = + subscribeService.findSubscriptionsByEPersonAndDso(context, currentUser, firstCollection, 10, 0); + + assertThat(subscriptions, contains( + matches(firstCollection, currentUser, updatedType, updatedParameters))); + + } finally { + SubscribeBuilder.deleteSubscription(subscription.getID()); + } + + } + + @Test + public void parametersAdditionAndRemoval() throws Exception { + + SubscriptionParameter firstParameter = createSubscriptionParameter("key1", "value1"); + SubscriptionParameter secondParameter = createSubscriptionParameter("key2", "value2"); + + EPerson currentUser = context.getCurrentUser(); + Subscription subscription = createSubscription("type", secondCollection, currentUser, + firstParameter, secondParameter); + int subscriptionId = subscription.getID(); + + SubscriptionParameter addedParameter = createSubscriptionParameter("added", "add"); + + + try { + Subscription updatedSubscription = subscribeService.addSubscriptionParameter(context, subscriptionId, + addedParameter); + assertThat(updatedSubscription, is(matches(secondCollection, currentUser, "type", + asList(firstParameter, secondParameter, addedParameter)))); + updatedSubscription = subscribeService.removeSubscriptionParameter(context, subscriptionId, + secondParameter); + assertThat(updatedSubscription, is(matches(secondCollection, currentUser, "type", + asList(firstParameter, addedParameter)))); + } finally { + SubscribeBuilder.deleteSubscription(subscriptionId); + } + } + + @Test + public void findersAndDeletionsTest() throws SQLException { + // method to test all find and delete methods exposed by SubscribeService + context.turnOffAuthorisationSystem(); + EPerson firstSubscriber = EPersonBuilder.createEPerson(context).withEmail("first-user@example.com").build(); + EPerson secondSubscriber = EPersonBuilder.createEPerson(context).withEmail("second-user@example.com").build(); + EPerson thirdSubscriber = EPersonBuilder.createEPerson(context).withEmail("third-user@example.com").build(); + context.restoreAuthSystemState(); + + EPerson currentUser = context.getCurrentUser(); + try { + context.setCurrentUser(firstSubscriber); + createSubscription("type1", firstCollection, firstSubscriber, daily(), + weekly()); + createSubscription("type1", secondCollection, firstSubscriber, + daily(), + annual()); + createSubscription("type2", secondCollection, firstSubscriber, + daily()); + + context.setCurrentUser(secondSubscriber); + createSubscription("type1", firstCollection, secondSubscriber, + daily()); + createSubscription("type1", secondCollection, secondSubscriber, + daily(), + annual()); + + context.setCurrentUser(thirdSubscriber); + createSubscription("type1", firstCollection, thirdSubscriber, daily()); + createSubscription("type1", secondCollection, thirdSubscriber, + daily(), + annual()); + + } finally { + context.setCurrentUser(currentUser); + } + + List firstUserSubscriptions = + subscribeService.findSubscriptionsByEPerson(context, firstSubscriber, 100, 0); + + assertThat(firstUserSubscriptions, containsInAnyOrder( + matches(firstCollection, firstSubscriber, "type1", asList(daily(), + weekly())), + matches(secondCollection, firstSubscriber, "type1", asList(daily(), + annual())), + matches(secondCollection, firstSubscriber, "type2", singletonList( + daily())) + )); + + List firstUserSubscriptionsLimited = + subscribeService.findSubscriptionsByEPerson(context, firstSubscriber, 1, 0); + + assertThat(firstUserSubscriptionsLimited.size(), is(1)); + + List firstUserSubscriptionsWithOffset = + subscribeService.findSubscriptionsByEPerson(context, firstSubscriber, 100, 1); + + assertThat(firstUserSubscriptionsWithOffset.size(), is(2)); + + subscribeService.deleteByEPerson(context, firstSubscriber); + assertThat(subscribeService.findSubscriptionsByEPerson(context, firstSubscriber, 100, 0), + is(List.of())); + + List secondSubscriberSecondCollectionSubscriptions = + subscribeService.findSubscriptionsByEPersonAndDso(context, secondSubscriber, firstCollection, 10, 0); + + assertThat(secondSubscriberSecondCollectionSubscriptions, contains( + matches(firstCollection, secondSubscriber, "type1", singletonList(daily())) + )); + + List byTypeAndFrequency = + subscribeService.findAllSubscriptionsBySubscriptionTypeAndFrequency(context, "type1", + "D"); + assertThat(byTypeAndFrequency, containsInAnyOrder( + matches(firstCollection, secondSubscriber, "type1", singletonList( + daily())), + matches(secondCollection, secondSubscriber, "type1", asList(daily(), + annual())), + matches(firstCollection, thirdSubscriber, "type1", singletonList( + daily())), + matches(secondCollection, thirdSubscriber, "type1", asList(daily(), + annual())) + )); + + assertThat(subscribeService.countAll(context), is(4L)); + assertThat(subscribeService.countByEPersonAndDSO(context, secondSubscriber, secondCollection), is(1L)); + assertThat(subscribeService.countSubscriptionsByEPerson(context, thirdSubscriber), is(2L)); + + + } + + private static SubscriptionParameter weekly() { + return createSubscriptionParameter("frequency", "W"); + } + + private Subscription createSubscription(String type, DSpaceObject dso, EPerson ePerson, + SubscriptionParameter... parameters) { + return subscribeBuilder(context, type, + dso, ePerson, + Arrays.stream(parameters).collect(Collectors.toList())).build(); + } + + + private static SubscriptionParameter createSubscriptionParameter(String name, String value) { + SubscriptionParameter parameter = new SubscriptionParameter(); + parameter.setName(name); + parameter.setValue(value); + return parameter; + } + +} \ No newline at end of file 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 b9dbbba647..09387acd3e 100644 --- a/dspace-api/src/test/java/org/dspace/identifier/DOIIdentifierProviderTest.java +++ b/dspace-api/src/test/java/org/dspace/identifier/DOIIdentifierProviderTest.java @@ -9,7 +9,9 @@ package org.dspace.identifier; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; 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.junit.Assume.assumeNotNull; @@ -36,6 +38,7 @@ import org.dspace.content.WorkspaceItem; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.logic.DefaultFilter; import org.dspace.content.logic.LogicalStatement; +import org.dspace.content.logic.TrueFilter; import org.dspace.content.service.CollectionService; import org.dspace.content.service.CommunityService; import org.dspace.content.service.ItemService; @@ -128,7 +131,7 @@ public class DOIIdentifierProviderTest provider.itemService = itemService; provider.setConfigurationService(config); provider.setDOIConnector(connector); - provider.setFilterService(null); + provider.setFilter(null); } catch (AuthorizeException ex) { log.error("Authorization Error in init", ex); fail("Authorization Error in init: " + ex.getMessage()); @@ -504,7 +507,7 @@ public class DOIIdentifierProviderTest String doi = null; try { // get a DOI (skipping any filters) - doi = provider.mint(context, item, true); + doi = provider.mint(context, item); } catch (IdentifierException e) { e.printStackTrace(System.err); fail("Got an IdentifierException: " + e.getMessage()); @@ -544,23 +547,18 @@ public class DOIIdentifierProviderTest Item item = newItem(); boolean wasFiltered = false; try { - // Temporarily set the provider to have a filter that always returns false for an item - // (therefore, the item should be 'filtered' out and not apply to this minting request) + // Mint this with the filter DefaultFilter doiFilter = new DefaultFilter(); LogicalStatement alwaysFalse = (context, i) -> false; doiFilter.setStatement(alwaysFalse); - provider.setFilterService(doiFilter); // get a DOI with the method that applies filters by default - provider.mint(context, item); + provider.mint(context, item, doiFilter); } catch (DOIIdentifierNotApplicableException e) { // This is what we wanted to see - we can return safely wasFiltered = true; } catch (IdentifierException e) { e.printStackTrace(); fail("Got an IdentifierException: " + e.getMessage()); - } finally { - // Set filter service back to null - provider.setFilterService(null); } // Fail the test if the filter didn't throw a "not applicable" exception assertTrue("DOI minting attempt was not filtered by filter service", wasFiltered); @@ -583,17 +581,14 @@ public class DOIIdentifierProviderTest DefaultFilter doiFilter = new DefaultFilter(); LogicalStatement alwaysTrue = (context, i) -> true; doiFilter.setStatement(alwaysTrue); - provider.setFilterService(doiFilter); // get a DOI with the method that applies filters by default - doi = provider.mint(context, item); + doi = provider.mint(context, item, doiFilter); } catch (DOIIdentifierNotApplicableException e) { // This is what we wanted to see - we can return safely wasFiltered = true; } catch (IdentifierException e) { e.printStackTrace(); fail("Got an IdentifierException: " + e.getMessage()); - } finally { - provider.setFilterService(null); } // If the attempt was filtered, fail assertFalse("DOI minting attempt was incorrectly filtered by filter service", wasFiltered); @@ -665,7 +660,9 @@ public class DOIIdentifierProviderTest Item item = newItem(); // Register, skipping the filter - String doi = provider.register(context, item, true); + String doi = provider.register(context, item, + DSpaceServicesFactory.getInstance().getServiceManager().getServiceByName( + "always_true_filter", TrueFilter.class)); // we want the created DOI to be returned in the following format: // doi:10./. @@ -763,6 +760,104 @@ public class DOIIdentifierProviderTest DOIIdentifierProvider.TO_BE_DELETED.equals(doiRow2.getStatus())); } + @Test + public void testUpdateMetadataSkippedForPending() + throws SQLException, AuthorizeException, IOException, IdentifierException, IllegalAccessException, + WorkflowException { + context.turnOffAuthorisationSystem(); + Item item = newItem(); + // Mint a new DOI with PENDING status + String doi1 = this.createDOI(item, DOIIdentifierProvider.PENDING, true); + // Update metadata for the item. + // This would normally shift status to UPDATE_REGISTERED, UPDATE_BEFORE_REGISTERING or UPDATE_RESERVED. + // But if the DOI is just pending, it should return without changing anything. + provider.updateMetadata(context, item, doi1); + // Get the DOI from the service + DOI doi = doiService.findDOIByDSpaceObject(context, item); + // Ensure it is still PENDING + assertEquals("Status of updated DOI did not remain PENDING", + DOIIdentifierProvider.PENDING, doi.getStatus()); + context.restoreAuthSystemState(); + } + + + @Test + public void testMintDoiAfterOrphanedPendingDOI() + throws SQLException, AuthorizeException, IOException, IdentifierException, IllegalAccessException, + WorkflowException { + context.turnOffAuthorisationSystem(); + Item item1 = newItem(); + // Mint a new DOI with PENDING status + String doi1 = this.createDOI(item1, DOIIdentifierProvider.PENDING, true); + // remove the item + itemService.delete(context, item1); + // Get the DOI from the service + DOI doi = doiService.findDOIByDSpaceObject(context, item1); + // ensure DOI has no state + assertNull("Orphaned DOI was not set deleted", doi); + // create a new item and a new DOI + Item item2 = newItem(); + String doi2 = null; + try { + // get a DOI (skipping any filters) + doi2 = provider.mint(context, item2); + } catch (IdentifierException e) { + e.printStackTrace(System.err); + fail("Got an IdentifierException: " + e.getMessage()); + } + + assertNotNull("Minted DOI is null?!", doi2); + assertFalse("Minted DOI is empty!", doi2.isEmpty()); + assertNotEquals("Minted DOI equals previously orphaned DOI.", doi1, doi2); + + try { + doiService.formatIdentifier(doi2); + } catch (DOIIdentifierException e) { + e.printStackTrace(System.err); + fail("Minted an unrecognizable DOI: " + e.getMessage()); + } + + context.restoreAuthSystemState(); + } + + @Test + public void testUpdateMetadataSkippedForMinted() + throws SQLException, AuthorizeException, IOException, IdentifierException, IllegalAccessException, + WorkflowException { + context.turnOffAuthorisationSystem(); + Item item = newItem(); + // Mint a new DOI with MINTED status + String doi1 = this.createDOI(item, DOIIdentifierProvider.MINTED, true); + // Update metadata for the item. + // This would normally shift status to UPDATE_REGISTERED, UPDATE_BEFORE_REGISTERING or UPDATE_RESERVED. + // But if the DOI is just minted, it should return without changing anything. + provider.updateMetadata(context, item, doi1); + // Get the DOI from the service + DOI doi = doiService.findDOIByDSpaceObject(context, item); + // Ensure it is still MINTED + assertEquals("Status of updated DOI did not remain PENDING", + DOIIdentifierProvider.MINTED, doi.getStatus()); + context.restoreAuthSystemState(); + } + + @Test + public void testLoadOrCreateDOIReturnsMintedStatus() + throws SQLException, AuthorizeException, IOException, IdentifierException, IllegalAccessException, + WorkflowException { + Item item = newItem(); + // Mint a DOI without an explicit reserve or register context + String mintedDoi = provider.mint(context, item, DSpaceServicesFactory.getInstance() + .getServiceManager().getServiceByName("always_true_filter", TrueFilter.class)); + DOI doi = doiService.findByDoi(context, mintedDoi.substring(DOI.SCHEME.length())); + // This should be minted + assertEquals("DOI is not of 'minted' status", DOIIdentifierProvider.MINTED, doi.getStatus()); + provider.updateMetadata(context, item, mintedDoi); + DOI secondFind = doiService.findByDoi(context, mintedDoi.substring(DOI.SCHEME.length())); + // After an update, this should still be minted + assertEquals("DOI is not of 'minted' status", + DOIIdentifierProvider.MINTED, secondFind.getStatus()); + + } // test the following methods using the MockDOIConnector. // updateMetadataOnline diff --git a/dspace-api/src/test/java/org/dspace/identifier/VersionedHandleIdentifierProviderTest.java b/dspace-api/src/test/java/org/dspace/identifier/VersionedHandleIdentifierProviderTest.java new file mode 100644 index 0000000000..1bc6bf1408 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/identifier/VersionedHandleIdentifierProviderTest.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.identifier; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +import org.dspace.AbstractIntegrationTestWithDatabase; +import org.dspace.authorize.AuthorizeException; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.builder.VersionBuilder; +import org.dspace.content.Collection; +import org.dspace.content.Item; +import org.dspace.kernel.ServiceManager; +import org.dspace.services.factory.DSpaceServicesFactory; +import org.junit.Before; +import org.junit.Test; + +public class VersionedHandleIdentifierProviderTest extends AbstractIntegrationTestWithDatabase { + private ServiceManager serviceManager; + private IdentifierServiceImpl identifierService; + + private String firstHandle; + + private Collection collection; + private Item itemV1; + private Item itemV2; + private Item itemV3; + + @Before + @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<>()); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection") + .build(); + } + + 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)); + } + + private void createVersions() throws SQLException, AuthorizeException { + itemV1 = ItemBuilder.createItem(context, collection) + .withTitle("First version") + .build(); + firstHandle = itemV1.getHandle(); + itemV2 = VersionBuilder.createVersion(context, itemV1, "Second version").build().getItem(); + itemV3 = VersionBuilder.createVersion(context, itemV1, "Third version").build().getItem(); + } + + @Test + public void testDefaultVersionedHandleProvider() throws Exception { + registerProvider(VersionedHandleIdentifierProvider.class); + createVersions(); + + // Confirm the original item only has its original handle + assertEquals(firstHandle, itemV1.getHandle()); + assertEquals(1, itemV1.getHandles().size()); + // Confirm the second item has the correct version handle + assertEquals(firstHandle + ".2", itemV2.getHandle()); + assertEquals(1, itemV2.getHandles().size()); + // Confirm the last item has the correct version handle + assertEquals(firstHandle + ".3", itemV3.getHandle()); + assertEquals(1, itemV3.getHandles().size()); + } + + @Test + public void testCanonicalVersionedHandleProvider() throws Exception { + registerProvider(VersionedHandleIdentifierProviderWithCanonicalHandles.class); + createVersions(); + + // Confirm the original item only has a version handle + assertEquals(firstHandle + ".1", itemV1.getHandle()); + assertEquals(1, itemV1.getHandles().size()); + // Confirm the second item has the correct version handle + assertEquals(firstHandle + ".2", itemV2.getHandle()); + assertEquals(1, itemV2.getHandles().size()); + // Confirm the last item has both the correct version handle and the original handle + assertEquals(firstHandle, itemV3.getHandle()); + assertEquals(2, itemV3.getHandles().size()); + containsHandle(itemV3, firstHandle + ".3"); + } + + private void containsHandle(Item item, String handle) { + assertTrue(item.getHandles().stream().anyMatch(h -> handle.equals(h.getHandle()))); + } +} diff --git a/dspace-api/src/test/java/org/dspace/iiif/MockIIIFApiQueryServiceImpl.java b/dspace-api/src/test/java/org/dspace/iiif/MockIIIFApiQueryServiceImpl.java new file mode 100644 index 0000000000..a240e76f97 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/iiif/MockIIIFApiQueryServiceImpl.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.iiif; + +import org.dspace.content.Bitstream; + +/** + * Mock for the IIIFApiQueryService. + * @author Michael Spalti (mspalti at willamette.edu) + */ +public class MockIIIFApiQueryServiceImpl extends IIIFApiQueryServiceImpl { + public int[] getImageDimensions(Bitstream bitstream) { + return new int[]{64, 64}; + } +} diff --git a/dspace-api/src/test/java/org/dspace/iiif/canvasdimension/CanvasDimensionsIT.java b/dspace-api/src/test/java/org/dspace/iiif/canvasdimension/CanvasDimensionsIT.java index 038654af43..7dba38c987 100644 --- a/dspace-api/src/test/java/org/dspace/iiif/canvasdimension/CanvasDimensionsIT.java +++ b/dspace-api/src/test/java/org/dspace/iiif/canvasdimension/CanvasDimensionsIT.java @@ -353,6 +353,40 @@ public class CanvasDimensionsIT extends AbstractIntegrationTestWithDatabase { } + + @Test + public void processItemWithJp2File() throws Exception { + context.turnOffAuthorisationSystem(); + // Create a new Item + iiifItem = ItemBuilder.createItem(context, col1) + .withTitle("Test Item") + .withIssueDate("2017-10-17") + .enableIIIF() + .build(); + + // Add jp2 image to verify image server call for dimensions + InputStream input = this.getClass().getResourceAsStream("cat.jp2"); + bitstream = BitstreamBuilder + .createBitstream(context, iiifItem, input) + .withName("Bitstream2.jp2") + .withMimeType("image/jp2") + .build(); + + context.restoreAuthSystemState(); + + String id = iiifItem.getID().toString(); + + execCanvasScript(id); + + assertTrue(bitstream.getMetadata().stream() + .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_HEIGHT)) + .anyMatch(m -> m.getValue().contentEquals("64"))); + assertTrue(bitstream.getMetadata().stream() + .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_WIDTH)) + .anyMatch(m -> m.getValue().contentEquals("64"))); + + } + @Test public void processParentCommunityWithMaximum() throws Exception { context.turnOffAuthorisationSystem(); diff --git a/dspace-api/src/test/java/org/dspace/matcher/SubscribeMatcher.java b/dspace-api/src/test/java/org/dspace/matcher/SubscribeMatcher.java new file mode 100644 index 0000000000..4671e65d38 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/matcher/SubscribeMatcher.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.matcher; + +import java.util.List; +import java.util.stream.Collectors; + +import org.dspace.content.DSpaceObject; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.Subscription; +import org.dspace.eperson.SubscriptionParameter; +import org.hamcrest.BaseMatcher; +import org.hamcrest.Description; + +public class SubscribeMatcher extends BaseMatcher { + + private final DSpaceObject dso; + private final EPerson eperson; + private final List parameters; + private final String type; + + private SubscribeMatcher(DSpaceObject dso, EPerson eperson, String type, List parameters) { + this.dso = dso; + this.eperson = eperson; + this.parameters = parameters; + this.type = type; + } + + public static SubscribeMatcher matches(DSpaceObject dso, EPerson ePerson, String type, + List parameters) { + return new SubscribeMatcher(dso, ePerson, type, parameters); + } + + @Override + public boolean matches(Object subscription) { + Subscription s = (Subscription) subscription; + return s.getEPerson().equals(eperson) + && s.getDSpaceObject().equals(dso) + && s.getSubscriptionType().equals(type) + && checkParameters(s.getSubscriptionParameterList()); + } + + private Boolean checkParameters(List parameters) { + if (parameters.size() != this.parameters.size()) { + return false; + } + // FIXME: for check purpose we rely on name and value. Evaluate to extend or refactor this part + for (int i = 0; i < parameters.size(); i++) { + SubscriptionParameter parameter = parameters.get(i); + SubscriptionParameter match = this.parameters.get(i); + boolean differentName = !parameter.getName().equals((match.getName())); + if (differentName) { + return false; + } + boolean differentValue = !parameter.getValue().equals((match.getValue())); + if (differentValue) { + return false; + } + } + return true; + } + + @Override + public void describeTo(Description description) { + String subscription = String.format("Type: %s, eperson: %s, dso: %s, params: %s", + type, eperson.getID(), dso.getID(), parameters.stream() + .map(p -> "{ name: " + p.getName() + + ", value: " + p.getValue() + + "}") + .collect(Collectors.joining(", "))); + description.appendText("Subscription matching: " + subscription); + } +} diff --git a/dspace-api/src/test/java/org/dspace/orcid/OrcidQueueConsumerIT.java b/dspace-api/src/test/java/org/dspace/orcid/OrcidQueueConsumerIT.java index e187e9a49e..f2e528d78c 100644 --- a/dspace-api/src/test/java/org/dspace/orcid/OrcidQueueConsumerIT.java +++ b/dspace-api/src/test/java/org/dspace/orcid/OrcidQueueConsumerIT.java @@ -30,7 +30,6 @@ import java.util.Date; import java.util.List; import org.dspace.AbstractIntegrationTestWithDatabase; -import org.dspace.authorize.AuthorizeException; import org.dspace.builder.CollectionBuilder; import org.dspace.builder.CommunityBuilder; import org.dspace.builder.EntityTypeBuilder; @@ -70,7 +69,9 @@ public class OrcidQueueConsumerIT extends AbstractIntegrationTestWithDatabase { private Collection profileCollection; @Before - public void setup() { + @Override + public void setUp() throws Exception { + super.setUp(); context.turnOffAuthorisationSystem(); @@ -84,12 +85,15 @@ public class OrcidQueueConsumerIT extends AbstractIntegrationTestWithDatabase { } @After - public void after() throws SQLException, AuthorizeException { + @Override + public void destroy() throws Exception { List records = orcidQueueService.findAll(context); for (OrcidQueue record : records) { orcidQueueService.delete(context, record); } context.setDispatcher(null); + + super.destroy(); } @Test @@ -139,6 +143,8 @@ public class OrcidQueueConsumerIT extends AbstractIntegrationTestWithDatabase { @Test public void testOrcidQueueRecordCreationForProfile() throws Exception { + // Set a fake handle prefix for this test which we will use to assign handles below + configurationService.setProperty("handle.prefix", "fake-handle"); context.turnOffAuthorisationSystem(); Item profile = ItemBuilder.createItem(context, profileCollection) @@ -146,7 +152,7 @@ public class OrcidQueueConsumerIT extends AbstractIntegrationTestWithDatabase { .withOrcidIdentifier("0000-1111-2222-3333") .withOrcidAccessToken("ab4d18a0-8d9a-40f1-b601-a417255c8d20", eperson) .withSubject("test") - .withHandle("123456789/200") + .withHandle("fake-handle/190") .withOrcidSynchronizationProfilePreference(BIOGRAPHICAL) .withOrcidSynchronizationProfilePreference(IDENTIFIERS) .build(); @@ -159,8 +165,8 @@ public class OrcidQueueConsumerIT extends AbstractIntegrationTestWithDatabase { assertThat(queueRecords, hasItem(matches(profile, profile, "KEYWORDS", null, "dc.subject::test", "test", INSERT))); assertThat(queueRecords, hasItem(matches(profile, "RESEARCHER_URLS", null, - "dc.identifier.uri::http://localhost:4000/handle/123456789/200", - "http://localhost:4000/handle/123456789/200", INSERT))); + "dc.identifier.uri::http://localhost:4000/handle/fake-handle/190", + "http://localhost:4000/handle/fake-handle/190", INSERT))); addMetadata(profile, "person", "name", "variant", "User Test", null); context.commit(); @@ -170,8 +176,8 @@ public class OrcidQueueConsumerIT extends AbstractIntegrationTestWithDatabase { assertThat(queueRecords, hasItem( matches(profile, profile, "KEYWORDS", null, "dc.subject::test", "test", INSERT))); assertThat(queueRecords, hasItem(matches(profile, "RESEARCHER_URLS", null, - "dc.identifier.uri::http://localhost:4000/handle/123456789/200", - "http://localhost:4000/handle/123456789/200", INSERT))); + "dc.identifier.uri::http://localhost:4000/handle/fake-handle/190", + "http://localhost:4000/handle/fake-handle/190", INSERT))); assertThat(queueRecords, hasItem(matches(profile, profile, "OTHER_NAMES", null, "person.name.variant::User Test", "User Test", INSERT))); } @@ -640,7 +646,8 @@ public class OrcidQueueConsumerIT extends AbstractIntegrationTestWithDatabase { @Test public void testOrcidQueueRecalculationOnProfilePreferenceUpdate() throws Exception { - + // Set a fake handle prefix for this test which we will use to assign handles below + configurationService.setProperty("handle.prefix", "fake-handle"); context.turnOffAuthorisationSystem(); Item profile = ItemBuilder.createItem(context, profileCollection) @@ -648,7 +655,7 @@ public class OrcidQueueConsumerIT extends AbstractIntegrationTestWithDatabase { .withOrcidIdentifier("0000-0000-0012-2345") .withOrcidAccessToken("ab4d18a0-8d9a-40f1-b601-a417255c8d20", eperson) .withSubject("Math") - .withHandle("123456789/200") + .withHandle("fake-handle/200") .withOrcidSynchronizationProfilePreference(BIOGRAPHICAL) .build(); @@ -669,8 +676,8 @@ public class OrcidQueueConsumerIT extends AbstractIntegrationTestWithDatabase { assertThat(records, hasItem(matches(profile, "KEYWORDS", null, "dc.subject::Math", "Math", INSERT))); assertThat(records, hasItem(matches(profile, "EXTERNAL_IDS", null, "person.identifier.rid::ID", "ID", INSERT))); assertThat(records, hasItem(matches(profile, "RESEARCHER_URLS", null, - "dc.identifier.uri::http://localhost:4000/handle/123456789/200", - "http://localhost:4000/handle/123456789/200", INSERT))); + "dc.identifier.uri::http://localhost:4000/handle/fake-handle/200", + "http://localhost:4000/handle/fake-handle/200", INSERT))); removeMetadata(profile, "dspace", "orcid", "sync-profile"); diff --git a/dspace-api/src/test/java/org/dspace/orcid/script/OrcidBulkPushIT.java b/dspace-api/src/test/java/org/dspace/orcid/script/OrcidBulkPushIT.java index db66f6c7aa..e6ca2a3d9e 100644 --- a/dspace-api/src/test/java/org/dspace/orcid/script/OrcidBulkPushIT.java +++ b/dspace-api/src/test/java/org/dspace/orcid/script/OrcidBulkPushIT.java @@ -215,6 +215,62 @@ public class OrcidBulkPushIT extends AbstractIntegrationTestWithDatabase { } + @Test + public void testWithVeryLongTitleQueueRecords() throws Exception { + Item firstProfileItem = createProfileItemItem("0000-1111-2222-3333", eperson, BATCH); + Item firstEntity = createPublication("Publication with a very very very very very very very very very " + + "very very very very very very very very very very very very very very very very very very very very " + + "very very very very very very very very very very very very very very very very very even " + + "extremely long title"); + + when(orcidClientMock.push(any(), eq("0000-1111-2222-3333"), any())) + .thenReturn(createdResponse("12345")); + + when(orcidClientMock.update(any(), eq("0000-1111-2222-3333"), any(), eq("98765"))) + .thenReturn(updatedResponse("98765")); + + when(orcidClientMock.deleteByPutCode( + any(), + eq("0000-1111-2222-3333"), + eq("22222"), + eq("/work")) + ).thenReturn(deletedResponse()); + + createOrcidQueue(context, firstProfileItem, firstEntity); + createOrcidQueue(context, firstProfileItem, "Description", "Publication", "22222"); + + context.commit(); + + TestDSpaceRunnableHandler handler = runBulkSynchronization(false); + + String firstProfileItemId = firstProfileItem.getID().toString(); + + assertThat(handler.getInfoMessages(), hasSize(5)); + assertThat(handler.getInfoMessages(), containsInAnyOrder( + "Found 2 queue records to synchronize with ORCID", + "Addition of Publication for profile with ID: " + firstProfileItemId, + "History record created with status 201. The operation was completed successfully", + "Deletion of Publication for profile with ID: " + firstProfileItemId + " by put code 22222", + "History record created with status 204. The operation was completed successfully")); + + assertThat(handler.getErrorMessages(), empty()); + assertThat(handler.getWarningMessages(), empty()); + + verify(orcidClientMock).push(any(), eq("0000-1111-2222-3333"), any()); + verify(orcidClientMock).deleteByPutCode( + any(), + eq("0000-1111-2222-3333"), + eq("22222"), + eq("/work")); + + verifyNoMoreInteractions(orcidClientMock); + + List historyRecords = orcidHistoryService.findAll(context); + assertThat(historyRecords, hasSize(2)); + assertThat(historyRecords, hasItem(matches(history(firstProfileItem, firstEntity, 201, INSERT)))); + assertThat(historyRecords, hasItem(matches(history(firstProfileItem, 204, DELETE)))); + } + @Test public void testWithOneValidationError() throws Exception { diff --git a/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceTest.java b/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceTest.java index e972aaa02b..920fb9316c 100644 --- a/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceTest.java +++ b/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceTest.java @@ -10,7 +10,9 @@ package org.dspace.storage.bitstore; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.isEmptyOrNullString; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.startsWith; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -63,6 +65,9 @@ public class S3BitStoreServiceTest extends AbstractUnitTest { @Mock private Bitstream bitstream; + @Mock + private Bitstream externalBitstream; + @Before public void setUp() throws Exception { this.s3BitStoreService = new S3BitStoreService(s3Service, tm); @@ -234,6 +239,24 @@ public class S3BitStoreServiceTest extends AbstractUnitTest { } + @Test + public void handleRegisteredIdentifierPrefixInS3() { + String trueBitStreamId = "012345"; + String registeredBitstreamId = s3BitStoreService.REGISTERED_FLAG + trueBitStreamId; + // Should be detected as registered bitstream + assertTrue(this.s3BitStoreService.isRegisteredBitstream(registeredBitstreamId)); + } + + @Test + public void stripRegisteredBitstreamPrefixWhenCalculatingPath() { + // Set paths and IDs + String s3Path = "UNIQUE_S3_PATH/test/bitstream.pdf"; + String registeredBitstreamId = s3BitStoreService.REGISTERED_FLAG + s3Path; + // Paths should be equal, since the getRelativePath method should strip the registered -R prefix + String relativeRegisteredPath = this.s3BitStoreService.getRelativePath(registeredBitstreamId); + assertEquals(s3Path, relativeRegisteredPath); + } + @Test public void givenBitStreamIdentifierLongerThanPossibleWhenIntermediatePathIsComputedThenIsSplittedAndTruncated() { String path = "01234567890123456789"; diff --git a/dspace-api/src/test/java/org/dspace/supervision/SupervisionOrderServiceIT.java b/dspace-api/src/test/java/org/dspace/supervision/SupervisionOrderServiceIT.java new file mode 100644 index 0000000000..6040782348 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/supervision/SupervisionOrderServiceIT.java @@ -0,0 +1,385 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.supervision; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasSize; + +import org.dspace.AbstractIntegrationTestWithDatabase; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.EPersonBuilder; +import org.dspace.builder.GroupBuilder; +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.eperson.Group; +import org.dspace.supervision.factory.SupervisionOrderServiceFactory; +import org.dspace.supervision.service.SupervisionOrderService; +import org.junit.Test; + +/** + * Unit tests for the {@link SupervisionOrderService} + * + * @author Mohamed Eskander (mohamed.eskander at 4science dot it) + */ +public class SupervisionOrderServiceIT extends AbstractIntegrationTestWithDatabase { + + protected SupervisionOrderService supervisionOrderService = + SupervisionOrderServiceFactory.getInstance().getSupervisionOrderService(); + + @Test + public void createSupervisionOrderTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = + CommunityBuilder.createCommunity(context) + .withName("parent community") + .build(); + + Collection collection = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("collection") + .withEntityType("Publication") + .build(); + + WorkspaceItem workspaceItem = + WorkspaceItemBuilder.createWorkspaceItem(context, collection) + .withTitle("workspace item") + .withIssueDate("2023-01-24") + .grantLicense() + .build(); + + Item item = workspaceItem.getItem(); + + EPerson userA = + EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withEmail("userA@example.org") + .build(); + + EPerson userB = + EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withEmail("userB@example.org") + .build(); + + Group groupA = GroupBuilder.createGroup(context) + .withName("group A") + .addMember(userA) + .build(); + + Group groupB = GroupBuilder.createGroup(context) + .withName("group B") + .addMember(userB) + .build(); + + SupervisionOrder supervisionOrderOne = + supervisionOrderService.create(context, item, groupA); + + SupervisionOrder supervisionOrderTwo = + supervisionOrderService.create(context, item, groupB); + + context.restoreAuthSystemState(); + + assertThat(supervisionOrderOne, notNullValue()); + assertThat(supervisionOrderOne.getItem().getID(), is(item.getID())); + assertThat(supervisionOrderOne.getGroup().getID(), is(groupA.getID())); + + assertThat(supervisionOrderTwo, notNullValue()); + assertThat(supervisionOrderTwo.getItem().getID(), is(item.getID())); + assertThat(supervisionOrderTwo.getGroup().getID(), is(groupB.getID())); + + } + + @Test + public void findSupervisionOrderTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = + CommunityBuilder.createCommunity(context) + .withName("parent community") + .build(); + + Collection collection = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("collection") + .withEntityType("Publication") + .build(); + + WorkspaceItem workspaceItem = + WorkspaceItemBuilder.createWorkspaceItem(context, collection) + .withTitle("workspace item") + .withIssueDate("2023-01-24") + .grantLicense() + .build(); + + EPerson userA = + EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withEmail("userA@example.org") + .build(); + + Group groupA = GroupBuilder.createGroup(context) + .withName("group A") + .addMember(userA) + .build(); + + SupervisionOrder supervisionOrderOne = + supervisionOrderService.create(context, workspaceItem.getItem(), groupA); + + context.restoreAuthSystemState(); + + SupervisionOrder supervisionOrder = + supervisionOrderService.find(context, supervisionOrderOne.getID()); + + assertThat(supervisionOrder, notNullValue()); + assertThat(supervisionOrder.getID(), is(supervisionOrderOne.getID())); + + assertThat(supervisionOrder.getGroup().getID(), + is(supervisionOrderOne.getGroup().getID())); + + assertThat(supervisionOrder.getItem().getID(), + is(supervisionOrderOne.getItem().getID())); + + } + + @Test + public void findAllSupervisionOrdersTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = + CommunityBuilder.createCommunity(context) + .withName("parent community") + .build(); + + Collection collection = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("collection") + .withEntityType("Publication") + .build(); + + WorkspaceItem workspaceItem = + WorkspaceItemBuilder.createWorkspaceItem(context, collection) + .withTitle("workspace item") + .withIssueDate("2023-01-24") + .grantLicense() + .build(); + + WorkspaceItem workspaceItemTwo = + WorkspaceItemBuilder.createWorkspaceItem(context, collection) + .withTitle("workspace item two") + .withIssueDate("2023-01-25") + .grantLicense() + .build(); + + EPerson userA = + EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withEmail("userA@example.org") + .build(); + + EPerson userB = + EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withEmail("userB@example.org") + .build(); + + Group groupA = GroupBuilder.createGroup(context) + .withName("group A") + .addMember(userA) + .build(); + + Group groupB = GroupBuilder.createGroup(context) + .withName("group B") + .addMember(userB) + .build(); + + supervisionOrderService.create(context, workspaceItem.getItem(), groupA); + supervisionOrderService.create(context, workspaceItem.getItem(), groupB); + supervisionOrderService.create(context, workspaceItemTwo.getItem(), groupA); + + context.restoreAuthSystemState(); + + assertThat(supervisionOrderService.findAll(context), hasSize(3)); + } + + @Test + public void findSupervisionOrderByItemTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = + CommunityBuilder.createCommunity(context) + .withName("parent community") + .build(); + + Collection collection = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("collection") + .withEntityType("Publication") + .build(); + + WorkspaceItem workspaceItem = + WorkspaceItemBuilder.createWorkspaceItem(context, collection) + .withTitle("workspace item") + .withIssueDate("2023-01-24") + .grantLicense() + .build(); + + WorkspaceItem workspaceItemTwo = + WorkspaceItemBuilder.createWorkspaceItem(context, collection) + .withTitle("workspace item two") + .withIssueDate("2023-01-25") + .grantLicense() + .build(); + + EPerson userA = + EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withEmail("userA@example.org") + .build(); + + Group groupA = GroupBuilder.createGroup(context) + .withName("group A") + .addMember(userA) + .build(); + + Group groupB = GroupBuilder.createGroup(context) + .withName("group B") + .addMember(eperson) + .build(); + + supervisionOrderService.create(context, workspaceItem.getItem(), groupA); + supervisionOrderService.create(context, workspaceItem.getItem(), groupB); + supervisionOrderService.create(context, workspaceItemTwo.getItem(), groupA); + + context.restoreAuthSystemState(); + + assertThat(supervisionOrderService.findByItem(context, workspaceItem.getItem()), hasSize(2)); + assertThat(supervisionOrderService.findByItem(context, workspaceItemTwo.getItem()), hasSize(1)); + + } + + @Test + public void findSupervisionOrderByItemAndGroupTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = + CommunityBuilder.createCommunity(context) + .withName("parent community") + .build(); + + Collection collection = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("collection") + .withEntityType("Publication") + .build(); + + WorkspaceItem workspaceItem = + WorkspaceItemBuilder.createWorkspaceItem(context, collection) + .withTitle("workspace item") + .withIssueDate("2023-01-24") + .grantLicense() + .build(); + + Item item = workspaceItem.getItem(); + + EPerson userA = + EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withEmail("userA@example.org") + .build(); + + Group groupA = GroupBuilder.createGroup(context) + .withName("group A") + .addMember(userA) + .build(); + + Group groupB = GroupBuilder.createGroup(context) + .withName("group B") + .addMember(eperson) + .build(); + + supervisionOrderService.create(context, item, groupA); + + context.restoreAuthSystemState(); + + SupervisionOrder supervisionOrderA = + supervisionOrderService.findByItemAndGroup(context, item, groupA); + + assertThat(supervisionOrderA, notNullValue()); + assertThat(supervisionOrderA.getItem().getID(), is(item.getID())); + assertThat(supervisionOrderA.getGroup().getID(), is(groupA.getID())); + + // no supervision order on item and groupB + assertThat(supervisionOrderService.findByItemAndGroup(context, item, groupB), nullValue()); + + } + + @Test + public void isSupervisorTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = + CommunityBuilder.createCommunity(context) + .withName("parent community") + .build(); + + Collection collection = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("collection") + .withEntityType("Publication") + .build(); + + WorkspaceItem workspaceItem = + WorkspaceItemBuilder.createWorkspaceItem(context, collection) + .withTitle("workspace item") + .withIssueDate("2023-01-24") + .grantLicense() + .build(); + + EPerson userA = + EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withEmail("userA@example.org") + .build(); + + EPerson userB = + EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withEmail("userB@example.org") + .build(); + + Group groupA = GroupBuilder.createGroup(context) + .withName("group A") + .addMember(userA) + .build(); + + GroupBuilder.createGroup(context) + .withName("group B") + .addMember(userB) + .build(); + + supervisionOrderService.create(context, workspaceItem.getItem(), groupA); + + context.restoreAuthSystemState(); + + assertThat(supervisionOrderService.isSupervisor( + context, userA, workspaceItem.getItem()), is(true)); + + // userB is not a supervisor on workspace Item + assertThat(supervisionOrderService.isSupervisor( + context, userB, workspaceItem.getItem()), is(false)); + } + +} 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 69c4dc16f4..865abaca21 100644 --- a/dspace-api/src/test/java/org/dspace/xmlworkflow/XmlWorkflowServiceIT.java +++ b/dspace-api/src/test/java/org/dspace/xmlworkflow/XmlWorkflowServiceIT.java @@ -9,11 +9,13 @@ package org.dspace.xmlworkflow; import static org.junit.Assert.assertTrue; +import java.io.IOException; import java.sql.SQLException; import java.util.List; import javax.servlet.http.HttpServletRequest; import org.dspace.AbstractIntegrationTestWithDatabase; +import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.ResourcePolicy; import org.dspace.authorize.factory.AuthorizeServiceFactory; import org.dspace.authorize.service.AuthorizeService; @@ -21,17 +23,24 @@ import org.dspace.builder.ClaimedTaskBuilder; import org.dspace.builder.CollectionBuilder; import org.dspace.builder.CommunityBuilder; import org.dspace.builder.EPersonBuilder; +import org.dspace.builder.GroupBuilder; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.Item; import org.dspace.core.Constants; import org.dspace.discovery.IndexingService; import org.dspace.eperson.EPerson; +import org.dspace.eperson.Group; +import org.dspace.eperson.factory.EPersonServiceFactory; +import org.dspace.eperson.service.GroupService; +import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.xmlworkflow.factory.XmlWorkflowServiceFactory; import org.dspace.xmlworkflow.service.XmlWorkflowService; import org.dspace.xmlworkflow.state.Workflow; +import org.dspace.xmlworkflow.state.actions.processingaction.SelectReviewerAction; import org.dspace.xmlworkflow.storedcomponents.ClaimedTask; +import org.junit.After; import org.junit.Test; import org.springframework.mock.web.MockHttpServletRequest; @@ -47,6 +56,22 @@ public class XmlWorkflowServiceIT extends AbstractIntegrationTestWithDatabase { .getServiceByName(IndexingService.class.getName(), IndexingService.class); protected AuthorizeService authorizeService = AuthorizeServiceFactory.getInstance().getAuthorizeService(); + protected GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); + protected ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); + + /** + * Cleans up the created workflow role groups after each test + * @throws SQLException + * @throws AuthorizeException + * @throws IOException + */ + @After + public void cleanup() throws SQLException, AuthorizeException, IOException { + Group reviewManagers = groupService.findByName(context, "ReviewManagers"); + if (reviewManagers != null) { + groupService.delete(context, reviewManagers); + } + } /** * Test to verify that if a user submits an item into the workflow, then it gets rejected that the submitter gets @@ -85,6 +110,93 @@ public class XmlWorkflowServiceIT extends AbstractIntegrationTestWithDatabase { assertTrue(this.containsRPForUser(taskToReject.getWorkflowItem().getItem(), submitter, Constants.WRITE)); } + /** + * Test to verify that if a user submits an item into the workflow, a reviewmanager can select a single reviewer + * eperson + */ + @Test + public void workflowUserSingleSelectedReviewer_ItemShouldBeEditable() throws Exception { + context.turnOffAuthorisationSystem(); + EPerson submitter = EPersonBuilder.createEPerson(context).withEmail("submitter@example.org").build(); + context.setCurrentUser(submitter); + EPerson reviewManager = + EPersonBuilder.createEPerson(context).withEmail("reviewmanager-test@example.org").build(); + Community community = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Collection colWithWorkflow = CollectionBuilder.createCollection(context, community, "123456789/workflow-test-1") + .withName("Collection WITH workflow") + .withWorkflowGroup("reviewmanagers", reviewManager) + .build(); + Workflow workflow = XmlWorkflowServiceFactory.getInstance().getWorkflowFactory().getWorkflow(colWithWorkflow); + ClaimedTask task = ClaimedTaskBuilder.createClaimedTask(context, colWithWorkflow, reviewManager) + .withTitle("Test workflow item to reject").build(); + // Set reviewer group property and add reviewer to group + SelectReviewerAction.resetGroup(); + configurationService.setProperty("action.selectrevieweraction.group", "Reviewers"); + Group reviewerGroup = GroupBuilder.createGroup(context).withName("Reviewers").build(); + EPerson reviewer = EPersonBuilder.createEPerson(context).withEmail("reviewer@example.org").build(); + groupService.addMember(context, reviewerGroup, reviewer); + context.restoreAuthSystemState(); + + // Review Manager should have access to workflow item + assertTrue(this.containsRPForUser(task.getWorkflowItem().getItem(), reviewManager, Constants.WRITE)); + + // select 1 reviewer + MockHttpServletRequest httpSelectReviewerRequest = new MockHttpServletRequest(); + httpSelectReviewerRequest.setParameter("submit_select_reviewer", "true"); + httpSelectReviewerRequest.setParameter("eperson", reviewer.getID().toString()); + executeWorkflowAction(httpSelectReviewerRequest, workflow, task); + + // Reviewer should have access to workflow item + assertTrue(this.containsRPForUser(task.getWorkflowItem().getItem(), reviewer, Constants.WRITE)); + } + + /** + * Test to verify that if a user submits an item into the workflow, a reviewmanager can select a multiple reviewer + * epersons + */ + @Test + public void workflowUserMultipleSelectedReviewer_ItemShouldBeEditable() throws Exception { + context.turnOffAuthorisationSystem(); + EPerson submitter = EPersonBuilder.createEPerson(context).withEmail("submitter@example.org").build(); + context.setCurrentUser(submitter); + EPerson reviewManager = + EPersonBuilder.createEPerson(context).withEmail("reviewmanager-test@example.org").build(); + Community community = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Collection colWithWorkflow = CollectionBuilder.createCollection(context, community, "123456789/workflow-test-1") + .withName("Collection WITH workflow") + .withWorkflowGroup("reviewmanagers", reviewManager) + .build(); + Workflow workflow = XmlWorkflowServiceFactory.getInstance().getWorkflowFactory().getWorkflow(colWithWorkflow); + ClaimedTask task = ClaimedTaskBuilder.createClaimedTask(context, colWithWorkflow, reviewManager) + .withTitle("Test workflow item to reject").build(); + // Set reviewer group property and add reviewer to group + SelectReviewerAction.resetGroup(); + configurationService.setProperty("action.selectrevieweraction.group", "Reviewers"); + Group reviewerGroup = GroupBuilder.createGroup(context).withName("Reviewers").build(); + EPerson reviewer1 = EPersonBuilder.createEPerson(context).withEmail("reviewer1@example.org").build(); + EPerson reviewer2 = EPersonBuilder.createEPerson(context).withEmail("reviewer2@example.org").build(); + groupService.addMember(context, reviewerGroup, reviewer1); + groupService.addMember(context, reviewerGroup, reviewer2); + context.restoreAuthSystemState(); + + // Review Manager should have access to workflow item + assertTrue(this.containsRPForUser(task.getWorkflowItem().getItem(), reviewManager, Constants.WRITE)); + + // Select multiple reviewers + MockHttpServletRequest httpSelectMultipleReviewers = new MockHttpServletRequest(); + httpSelectMultipleReviewers.setParameter("submit_select_reviewer", "true"); + httpSelectMultipleReviewers.setParameter("eperson", reviewer1.getID().toString(), reviewer2.getID().toString()); + executeWorkflowAction(httpSelectMultipleReviewers, workflow, task); + + // Reviewers should have access to workflow item + assertTrue(this.containsRPForUser(task.getWorkflowItem().getItem(), reviewer1, Constants.WRITE)); + assertTrue(this.containsRPForUser(task.getWorkflowItem().getItem(), reviewer2, Constants.WRITE)); + } + private boolean containsRPForUser(Item item, EPerson user, int action) throws SQLException { List rps = authorizeService.getPolicies(context, item); for (ResourcePolicy rp : rps) { diff --git a/dspace-api/src/test/resources/org/dspace/iiif/canvasdimension/cat.jp2 b/dspace-api/src/test/resources/org/dspace/iiif/canvasdimension/cat.jp2 new file mode 100644 index 0000000000..a6649c0886 Binary files /dev/null and b/dspace-api/src/test/resources/org/dspace/iiif/canvasdimension/cat.jp2 differ diff --git a/dspace-iiif/pom.xml b/dspace-iiif/pom.xml index f3257ad769..7e26e22fa2 100644 --- a/dspace-iiif/pom.xml +++ b/dspace-iiif/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.4 + 7.6-SNAPSHOT .. diff --git a/dspace-iiif/src/main/java/org/dspace/app/iiif/service/CanvasService.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/CanvasService.java index 189e4d6f62..dcfb707d62 100644 --- a/dspace-iiif/src/main/java/org/dspace/app/iiif/service/CanvasService.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/CanvasService.java @@ -78,29 +78,45 @@ public class CanvasService extends AbstractResourceService { } /** - * Checks for bitstream iiif.image.width metadata in the first - * bitstream in first IIIF bundle. If bitstream metadata is not - * found, use the IIIF image service to update the default canvas - * dimensions for this request. Called once for each manifest. + * Checks for "iiif.image.width" metadata in IIIF bundles. When bitstream + * metadata is not found for the first image in the bundle this method updates the + * default canvas dimensions for the request based on the actual image dimensions, + * using the IIIF image service. Called once for each manifest. * @param bundles IIIF bundles for this item */ - protected void guessCanvasDimensions(List bundles) { - Bitstream firstBistream = bundles.get(0).getBitstreams().get(0); - if (!utils.hasWidthMetadata(firstBistream)) { - int[] imageDims = utils.getImageDimensions(firstBistream); - if (imageDims != null && imageDims.length == 2) { - // update the fallback dimensions - defaultCanvasWidthFallback = imageDims[0]; - defaultCanvasHeightFallback = imageDims[1]; + protected void guessCanvasDimensions(Context context, List bundles) { + // prevent redundant updates. + boolean dimensionUpdated = false; + + for (Bundle bundle : bundles) { + if (!dimensionUpdated) { + for (Bitstream bitstream : bundle.getBitstreams()) { + if (utils.isIIIFBitstream(context, bitstream)) { + // check for width dimension + if (!utils.hasWidthMetadata(bitstream)) { + // get the dimensions of the image. + int[] imageDims = utils.getImageDimensions(bitstream); + if (imageDims != null && imageDims.length == 2) { + // update the fallback dimensions + defaultCanvasWidthFallback = imageDims[0]; + defaultCanvasHeightFallback = imageDims[1]; + } + setDefaultCanvasDimensions(); + // stop processing the bundles + dimensionUpdated = true; + } + // check only the first image + break; + } + } } - setDefaultCanvasDimensions(); } } /** - * Used to set the height and width dimensions for all images when iiif.image.default-width and - * iiif.image.default-height are set to -1 in DSpace configuration. - * The values are updated only if the bitstream does not have its own iiif.image.width metadata. + * Sets the height and width dimensions for all images when "iiif.image.default-width" + * and "iiif.image.default-height" are set to -1 in DSpace configuration. The values + * are updated only when the bitstream does not have its own image dimension metadata. * @param bitstream */ private void setCanvasDimensions(Bitstream bitstream) { diff --git a/dspace-iiif/src/main/java/org/dspace/app/iiif/service/ManifestService.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/ManifestService.java index c12365a47d..09526deeb6 100644 --- a/dspace-iiif/src/main/java/org/dspace/app/iiif/service/ManifestService.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/ManifestService.java @@ -156,7 +156,7 @@ public class ManifestService extends AbstractResourceService { List bundles = utils.getIIIFBundles(item); // Set the default canvas dimensions. if (guessCanvasDimension) { - canvasService.guessCanvasDimensions(bundles); + canvasService.guessCanvasDimensions(context, bundles); } for (Bundle bnd : bundles) { String bundleToCPrefix = null; 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 0ef759daf8..782a5a9852 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 @@ -137,7 +137,7 @@ public class IIIFUtils { * @param b the DSpace bitstream to check * @return true if the bitstream can be used as IIIF resource */ - private boolean isIIIFBitstream(Context context, Bitstream b) { + public boolean isIIIFBitstream(Context context, Bitstream b) { return checkImageMimeType(getBitstreamMimeType(b, context)) && b.getMetadata().stream() .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_ENABLED)) .noneMatch(m -> m.getValue().equalsIgnoreCase("false") || m.getValue().equalsIgnoreCase("no")); @@ -228,7 +228,7 @@ public class IIIFUtils { * @param mimetype * @return true if an image */ - public boolean checkImageMimeType(String mimetype) { + private boolean checkImageMimeType(String mimetype) { if (mimetype != null && mimetype.contains("image/")) { return true; } diff --git a/dspace-oai/pom.xml b/dspace-oai/pom.xml index de872b867c..8692482d78 100644 --- a/dspace-oai/pom.xml +++ b/dspace-oai/pom.xml @@ -8,7 +8,7 @@ dspace-parent org.dspace - 7.4 + 7.6-SNAPSHOT .. @@ -35,24 +35,6 @@ - - - oracle-support - - - db.name - oracle - - - - - com.oracle - ojdbc6 - - - - - commons-cli diff --git a/dspace-rdf/pom.xml b/dspace-rdf/pom.xml index dcba95e682..95354621aa 100644 --- a/dspace-rdf/pom.xml +++ b/dspace-rdf/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - 7.4 + 7.6-SNAPSHOT .. diff --git a/dspace-rest/pom.xml b/dspace-rest/pom.xml index 8040ce5a1d..7fdf21ef4c 100644 --- a/dspace-rest/pom.xml +++ b/dspace-rest/pom.xml @@ -3,7 +3,7 @@ org.dspace dspace-rest war - 7.4 + 7.6-SNAPSHOT DSpace (Deprecated) REST Webapp DSpace RESTful Web Services API. NOTE: this REST API is DEPRECATED. Please consider using the REST API in the dspace-server-webapp instead! @@ -12,7 +12,7 @@ org.dspace dspace-parent - 7.4 + 7.6-SNAPSHOT .. diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index 63145abfb9..99aa88bebf 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.4 + 7.6-SNAPSHOT .. @@ -429,11 +429,6 @@ org.apache.commons commons-collections4 - - org.apache.commons - commons-text - 1.9 - commons-validator commons-validator @@ -527,13 +522,6 @@ solr-core ${solr.client.version} test - - - - org.apache.commons - commons-text - - org.apache.lucene @@ -556,6 +544,10 @@ 2.0.7 test + + javax.annotation + javax.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 313fe2de60..e8b8eb8e70 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 @@ -34,7 +34,6 @@ import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.security.RestAuthenticationService; 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.dspace.service.ClientInfoService; import org.slf4j.Logger; @@ -210,33 +209,7 @@ public class AuthenticationRestController implements InitializingBean { } /** - * This method will generate a short lived token to be used for bitstream downloads among other things. - * - * For security reasons, this endpoint only responds to a explicitly defined list of ips. - * - * curl -v -X GET https://{dspace-server.url}/api/authn/shortlivedtokens -H "Authorization: Bearer eyJhbG...COdbo" - * - * Example: - *
-     * {@code
-     * curl -v -X GET https://{dspace-server.url}/api/authn/shortlivedtokens -H "Authorization: Bearer eyJhbG...COdbo"
-     * }
-     * 
- * @param request The StandardMultipartHttpServletRequest - * @return The created short lived token - */ - @PreAuthorize("hasAuthority('AUTHENTICATED')") - @RequestMapping(value = "/shortlivedtokens", method = RequestMethod.GET) - public AuthenticationTokenResource shortLivedTokenViaGet(HttpServletRequest request) throws AuthorizeException { - if (!clientInfoService.isRequestFromTrustedProxy(request.getRemoteAddr())) { - throw new AuthorizeException("Requests to this endpoint should be made from a trusted IP address."); - } - - return shortLivedTokenResponse(request); - } - - /** - * See {@link #shortLivedToken} and {@link #shortLivedTokenViaGet} + * See {@link #shortLivedToken} */ private AuthenticationTokenResource shortLivedTokenResponse(HttpServletRequest request) { Projection projection = utils.obtainProjection(); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/IdentifierRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/IdentifierRestController.java deleted file mode 100644 index 5f7a473b91..0000000000 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/IdentifierRestController.java +++ /dev/null @@ -1,108 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.app.rest; - -import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo; - -import java.io.IOException; -import java.net.URI; -import java.sql.SQLException; -import java.util.Arrays; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.atteo.evo.inflector.English; -import org.dspace.app.rest.converter.ConverterService; -import org.dspace.app.rest.model.DSpaceObjectRest; -import org.dspace.app.rest.utils.ContextUtil; -import org.dspace.app.rest.utils.Utils; -import org.dspace.content.DSpaceObject; -import org.dspace.core.Context; -import org.dspace.identifier.IdentifierNotFoundException; -import org.dspace.identifier.IdentifierNotResolvableException; -import org.dspace.identifier.factory.IdentifierServiceFactory; -import org.dspace.identifier.service.IdentifierService; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.hateoas.Link; -import org.springframework.hateoas.TemplateVariable; -import org.springframework.hateoas.TemplateVariable.VariableType; -import org.springframework.hateoas.TemplateVariables; -import org.springframework.hateoas.UriTemplate; -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; - -@RestController -@RequestMapping("/api/" + IdentifierRestController.CATEGORY) -public class IdentifierRestController implements InitializingBean { - public static final String CATEGORY = "pid"; - - public static final String ACTION = "find"; - - public static final String PARAM = "id"; - - private static final Logger log = LogManager.getLogger(); - - @Autowired - private ConverterService converter; - - @Autowired - private Utils utils; - - @Autowired - private DiscoverableEndpointsService discoverableEndpointsService; - - @Override - public void afterPropertiesSet() throws Exception { - discoverableEndpointsService - .register(this, - Arrays.asList( - Link.of( - UriTemplate.of("/api/" + CATEGORY + "/" + ACTION, - new TemplateVariables( - new TemplateVariable(PARAM, VariableType.REQUEST_PARAM))), - CATEGORY))); - } - - @RequestMapping(method = RequestMethod.GET, value = ACTION, params = PARAM) - @SuppressWarnings("unchecked") - public void getDSObyIdentifier(HttpServletRequest request, - HttpServletResponse response, - @RequestParam(PARAM) String id) - throws IOException, SQLException { - - DSpaceObject dso = null; - Context context = ContextUtil.obtainContext(request); - IdentifierService identifierService = IdentifierServiceFactory - .getInstance().getIdentifierService(); - try { - dso = identifierService.resolve(context, id); - if (dso != null) { - DSpaceObjectRest dsor = converter.toRest(dso, utils.obtainProjection()); - URI link = linkTo(dsor.getController(), dsor.getCategory(), - English.plural(dsor.getType())) - .slash(dsor.getId()).toUri(); - response.setStatus(HttpServletResponse.SC_FOUND); - response.sendRedirect(link.toString()); - } else { - response.setStatus(HttpServletResponse.SC_NOT_FOUND); - } - } catch (IdentifierNotFoundException e) { - response.setStatus(HttpServletResponse.SC_NOT_FOUND); - } catch (IdentifierNotResolvableException e) { - response.setStatus(HttpServletResponse.SC_NOT_IMPLEMENTED); - } finally { - context.abort(); - } - } - -} 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 b06360ee1d..b5a0c957f2 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 @@ -39,6 +39,7 @@ import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.PathVariable; 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; /** @@ -69,6 +70,8 @@ public class ItemOwningCollectionUpdateRestController { * moving the item to the new collection. * * @param uuid The UUID of the item that will be moved + * @param inheritCollectionPolicies Boolean flag whether to inherit the target collection policies when + * moving the item * @param response The response object * @param request The request object * @return The wrapped resource containing the new owning collection or null when the item was not moved @@ -79,7 +82,10 @@ public class ItemOwningCollectionUpdateRestController { @RequestMapping(method = RequestMethod.PUT, consumes = {"text/uri-list"}) @PreAuthorize("hasPermission(#uuid, 'ITEM','WRITE')") @PostAuthorize("returnObject != null") - public CollectionRest move(@PathVariable UUID uuid, HttpServletResponse response, + public CollectionRest move(@PathVariable UUID uuid, + @RequestParam(name = "inheritPolicies", defaultValue = "false") + Boolean inheritCollectionPolicies, + HttpServletResponse response, HttpServletRequest request) throws SQLException, IOException, AuthorizeException { Context context = ContextUtil.obtainContext(request); @@ -91,7 +97,8 @@ public class ItemOwningCollectionUpdateRestController { "or the data cannot be resolved to a collection."); } - Collection targetCollection = performItemMove(context, uuid, (Collection) dsoList.get(0)); + Collection targetCollection = performItemMove(context, uuid, (Collection) dsoList.get(0), + inheritCollectionPolicies); if (targetCollection == null) { return null; @@ -107,17 +114,19 @@ public class ItemOwningCollectionUpdateRestController { * @param item The item to be moved * @param currentCollection The current owning collection of the item * @param targetCollection The target collection of the item + * @param inheritPolicies Boolean flag whether to inherit the target collection policies when moving the item * @return The target collection * @throws SQLException If something goes wrong * @throws IOException If something goes wrong * @throws AuthorizeException If the user is not authorized to perform the move action */ private Collection moveItem(final Context context, final Item item, final Collection currentCollection, - final Collection targetCollection) + final Collection targetCollection, + final boolean inheritPolicies) throws SQLException, IOException, AuthorizeException { - itemService.move(context, item, currentCollection, targetCollection); - //Necessary because Controller does not pass through general RestResourceController, and as such does not do its - // commit in DSpaceRestRepository.createAndReturn() or similar + itemService.move(context, item, currentCollection, targetCollection, inheritPolicies); + // Necessary because Controller does not pass through general RestResourceController, and as such does not do + // its commit in DSpaceRestRepository.createAndReturn() or similar context.commit(); return context.reloadEntity(targetCollection); @@ -129,12 +138,14 @@ public class ItemOwningCollectionUpdateRestController { * @param context The context Object * @param itemUuid The uuid of the item to be moved * @param targetCollection The target collection + * @param inheritPolicies Whether to inherit the target collection policies when moving the item * @return The new owning collection of the item when authorized or null when not authorized * @throws SQLException If something goes wrong * @throws IOException If something goes wrong * @throws AuthorizeException If the user is not authorized to perform the move action */ - private Collection performItemMove(final Context context, final UUID itemUuid, final Collection targetCollection) + private Collection performItemMove(final Context context, final UUID itemUuid, final Collection targetCollection, + boolean inheritPolicies) throws SQLException, IOException, AuthorizeException { Item item = itemService.find(context, itemUuid); @@ -153,7 +164,7 @@ public class ItemOwningCollectionUpdateRestController { if (authorizeService.authorizeActionBoolean(context, currentCollection, Constants.ADMIN)) { - return moveItem(context, item, currentCollection, targetCollection); + return moveItem(context, item, currentCollection, targetCollection, inheritPolicies); } return null; 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 6a760b7d08..79ca381753 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 @@ -45,6 +45,8 @@ import org.dspace.discovery.SearchUtils; import org.dspace.discovery.configuration.DiscoveryConfiguration; import org.dspace.discovery.configuration.DiscoveryConfigurationService; import org.dspace.discovery.configuration.DiscoverySearchFilter; +import org.dspace.discovery.configuration.DiscoverySortConfiguration; +import org.dspace.discovery.configuration.DiscoverySortFieldConfiguration; import org.dspace.discovery.indexobject.IndexableItem; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; @@ -141,16 +143,36 @@ public class OpenSearchController { queryArgs.setStart(start); queryArgs.setMaxResults(count); queryArgs.setDSpaceObjectFilter(IndexableItem.TYPE); + if (sort != null) { - //this is the default sort so we want to switch this to date accessioned - if (sortDirection != null && sortDirection.equals("DESC")) { - queryArgs.setSortField(sort + "_sort", SORT_ORDER.desc); - } else { - queryArgs.setSortField(sort + "_sort", SORT_ORDER.asc); + DiscoveryConfiguration discoveryConfiguration = + searchConfigurationService.getDiscoveryConfiguration(""); + if (discoveryConfiguration != null) { + DiscoverySortConfiguration searchSortConfiguration = discoveryConfiguration + .getSearchSortConfiguration(); + if (searchSortConfiguration != null) { + DiscoverySortFieldConfiguration sortFieldConfiguration = searchSortConfiguration + .getSortFieldConfiguration(sort); + if (sortFieldConfiguration != null) { + String sortField = searchService + .toSortFieldIndex(sortFieldConfiguration.getMetadataField(), + sortFieldConfiguration.getType()); + + if (sortDirection != null && sortDirection.equals("DESC")) { + queryArgs.setSortField(sortField, SORT_ORDER.desc); + } else { + queryArgs.setSortField(sortField, SORT_ORDER.asc); + } + } else { + throw new IllegalArgumentException(sort + " is not a valid sort field"); + } + } } } else { + // this is the default sort so we want to switch this to date accessioned queryArgs.setSortField("dc.date.accessioned_dt", SORT_ORDER.desc); } + if (dsoObject != null) { container = scopeResolver.resolveScope(context, dsoObject); DiscoveryConfiguration discoveryConfiguration = searchConfigurationService diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/AuthorizationFeature.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/AuthorizationFeature.java index f2f1ae31f5..353e2b9957 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/AuthorizationFeature.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/AuthorizationFeature.java @@ -13,6 +13,7 @@ import org.dspace.app.rest.model.BaseObjectRest; import org.dspace.app.rest.model.RestAddressableModel; import org.dspace.app.rest.model.SiteRest; import org.dspace.core.Context; +import org.dspace.discovery.SearchServiceException; import org.springframework.core.annotation.AnnotationUtils; /** @@ -33,7 +34,7 @@ public interface AuthorizationFeature { * wide feature * @return true if the user associated with the context has access to the feature for the specified object */ - boolean isAuthorized(Context context, BaseObjectRest object) throws SQLException; + boolean isAuthorized(Context context, BaseObjectRest object) throws SQLException, SearchServiceException; /** * Return the name of the feature @@ -69,4 +70,4 @@ public interface AuthorizationFeature { * @return the supported object type, required to be not null */ String[] getSupportedTypes(); -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/AuthorizationFeatureService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/AuthorizationFeatureService.java index 2b04bb983c..941c614fcb 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/AuthorizationFeatureService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/AuthorizationFeatureService.java @@ -13,6 +13,7 @@ import java.util.List; import org.dspace.app.rest.model.BaseObjectRest; import org.dspace.app.rest.model.SiteRest; import org.dspace.core.Context; +import org.dspace.discovery.SearchServiceException; /** * This service provides access to the Authorization Features and check if the feature is allowed or not in a specific @@ -34,7 +35,8 @@ public interface AuthorizationFeatureService { * feature pass the {@link SiteRest} object * @return true if the user associated with the context has access to the feature */ - boolean isAuthorized(Context context, AuthorizationFeature feature, BaseObjectRest object) throws SQLException; + boolean isAuthorized(Context context, AuthorizationFeature feature, BaseObjectRest object) + throws SQLException, SearchServiceException; /** * Get all the authorization features defined in the system @@ -60,4 +62,4 @@ public interface AuthorizationFeatureService { * @return */ List findByResourceType(String categoryDotModel); -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/AuthorizationFeatureServiceImpl.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/AuthorizationFeatureServiceImpl.java index 01385d4435..766204364c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/AuthorizationFeatureServiceImpl.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/AuthorizationFeatureServiceImpl.java @@ -18,6 +18,7 @@ import org.dspace.app.rest.authorization.AuthorizationFeatureService; import org.dspace.app.rest.model.BaseObjectRest; import org.dspace.app.rest.utils.Utils; import org.dspace.core.Context; +import org.dspace.discovery.SearchServiceException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -38,7 +39,7 @@ public class AuthorizationFeatureServiceImpl implements AuthorizationFeatureServ @Override public boolean isAuthorized(Context context, AuthorizationFeature feature, BaseObjectRest object) - throws SQLException { + throws SQLException, SearchServiceException { if (object == null) { // the authorization interface require that the object is not null return false; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanRegisterDOIFeature.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanRegisterDOIFeature.java new file mode 100644 index 0000000000..7b1493d56d --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanRegisterDOIFeature.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.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.authorization.AuthorizeServiceRestUtil; +import org.dspace.app.rest.model.BaseObjectRest; +import org.dspace.app.rest.model.ItemRest; +import org.dspace.app.rest.security.DSpaceRestPermission; +import org.dspace.core.Context; +import org.dspace.services.ConfigurationService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Can the current user register a DOI for this item? + * + * @author Kim Shepherd + */ +@Component +@AuthorizationFeatureDocumentation(name = CanRegisterDOIFeature.NAME, + description = "It can be used to verify if the user can register a DOI for this item") +public class CanRegisterDOIFeature implements AuthorizationFeature { + + @Autowired + private AuthorizeServiceRestUtil authorizeServiceRestUtil; + @Autowired + private ConfigurationService configurationService; + + public static final String NAME = "canRegisterDOI"; + + @Override + @SuppressWarnings("rawtypes") + public boolean isAuthorized(Context context, BaseObjectRest object) throws SQLException { + // Check configuration to see if this REST operation is allowed + if (!configurationService.getBooleanProperty("identifiers.item-status.register-doi", false)) { + return false; + } + if (object instanceof ItemRest) { + return authorizeServiceRestUtil.authorizeActionBoolean(context, object, DSpaceRestPermission.ADMIN); + } + return false; + } + + @Override + public String[] getSupportedTypes() { + return new String[]{ + ItemRest.CATEGORY + "." + ItemRest.NAME + }; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanSubscribeFeature.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanSubscribeFeature.java new file mode 100644 index 0000000000..2e0e27b057 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanSubscribeFeature.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.authorization.impl; + +import static org.dspace.core.Constants.READ; + +import java.sql.SQLException; +import java.util.Objects; + +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.CollectionRest; +import org.dspace.app.rest.model.CommunityRest; +import org.dspace.app.rest.utils.Utils; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.content.DSpaceObject; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Checks if the given user can subscribe to a DSpace object + * + * @author Alba Aliu (alba.aliu at atis.al) + */ +@Component +@AuthorizationFeatureDocumentation(name = CanSubscribeFeature.NAME, + description = "Used to verify if the given user can subscribe to a DSpace object") +public class CanSubscribeFeature implements AuthorizationFeature { + + public static final String NAME = "canSubscribeDso"; + + @Autowired + private Utils utils; + @Autowired + private AuthorizeService authorizeService; + + @Override + @SuppressWarnings("rawtypes") + public boolean isAuthorized(Context context, BaseObjectRest object) throws SQLException { + if (Objects.isNull(context.getCurrentUser())) { + return false; + } + DSpaceObject dSpaceObject = (DSpaceObject) utils.getDSpaceAPIObjectFromRest(context, object); + return authorizeService.authorizeActionBoolean(context, context.getCurrentUser(), dSpaceObject, READ, true); + } + + @Override + public String[] getSupportedTypes() { + return new String[]{ + CommunityRest.CATEGORY + "." + CommunityRest.NAME, + CollectionRest.CATEGORY + "." + CollectionRest.NAME + }; + } + +} \ No newline at end of file diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/EditItemFeature.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/EditItemFeature.java new file mode 100644 index 0000000000..5c605daaf4 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/EditItemFeature.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.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.ItemRest; +import org.dspace.app.rest.model.SiteRest; +import org.dspace.app.rest.utils.Utils; +import org.dspace.authorize.service.AuthorizeService; +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.SearchServiceException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +@AuthorizationFeatureDocumentation(name = EditItemFeature.NAME, + description = "It can be used to verify if a user has rights to edit any item.") +public class EditItemFeature implements AuthorizationFeature { + public static final String NAME = "canEditItem"; + @Autowired + AuthorizeService authService; + @Autowired + ItemService itemService; + + @Autowired + Utils utils; + + @Override + public boolean isAuthorized(Context context, BaseObjectRest object) throws SQLException, SearchServiceException { + if (object instanceof SiteRest) { + return itemService.countItemsWithEdit(context) > 0; + } else if (object instanceof ItemRest) { + Item item = (Item) utils.getDSpaceAPIObjectFromRest(context, object); + return authService.authorizeActionBoolean(context, item, Constants.WRITE); + } + return false; + } + + @Override + public String[] getSupportedTypes() { + return new String[] { + ItemRest.CATEGORY + "." + ItemRest.NAME, + SiteRest.CATEGORY + "." + SiteRest.NAME + }; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/SubmitFeature.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/SubmitFeature.java new file mode 100644 index 0000000000..3793928fb0 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/SubmitFeature.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.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.CollectionRest; +import org.dspace.app.rest.model.SiteRest; +import org.dspace.app.rest.utils.Utils; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.content.Collection; +import org.dspace.content.service.CollectionService; +import org.dspace.core.Constants; +import org.dspace.core.Context; +import org.dspace.discovery.SearchServiceException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +@AuthorizationFeatureDocumentation(name = SubmitFeature.NAME, + description = "It can be used to verify if a user has rights to submit anything.") +public class SubmitFeature implements AuthorizationFeature { + public static final String NAME = "canSubmit"; + + @Autowired + AuthorizeService authService; + + @Autowired + CollectionService collectionService; + + @Autowired + Utils utils; + + @Override + public boolean isAuthorized(Context context, BaseObjectRest object) throws SQLException, SearchServiceException { + if (object instanceof SiteRest) { + // Check whether the user has permission to add to any collection + return collectionService.countCollectionsWithSubmit("", context, null) > 0; + } else if (object instanceof CollectionRest) { + // Check whether the user has permission to add to the given collection + Collection collection = (Collection) utils.getDSpaceAPIObjectFromRest(context, object); + return authService.authorizeActionBoolean(context, collection, Constants.ADD); + } + return false; + } + + @Override + public String[] getSupportedTypes() { + return new String[] { + CollectionRest.CATEGORY + "." + CollectionRest.NAME, + SiteRest.CATEGORY + "." + SiteRest.NAME + }; + } +} 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 0f7b47239e..e837904951 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 @@ -202,17 +202,18 @@ public class ConverterService { * @throws ClassCastException if the converter's return type is not compatible with the inferred return type. */ public Page toRestPage(List modelObjects, Pageable pageable, Projection projection) { + if (pageable == null) { + pageable = utils.getPageable(pageable); + } + List pageableObjects = utils.getPageObjectList(modelObjects, pageable); List transformedList = new LinkedList<>(); - for (M modelObject : modelObjects) { + for (M modelObject : pageableObjects) { R transformedObject = toRest(modelObject, projection); if (transformedObject != null) { transformedList.add(transformedObject); } } - if (pageable == null) { - pageable = utils.getPageable(pageable); - } - return utils.getPage(transformedList, pageable); + return new PageImpl(transformedList, pageable, modelObjects.size()); } /** diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ScoreReviewActionAdvancedInfoConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ScoreReviewActionAdvancedInfoConverter.java new file mode 100644 index 0000000000..44800f6e50 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ScoreReviewActionAdvancedInfoConverter.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.rest.converter; + +import org.dspace.app.rest.model.ScoreReviewActionAdvancedInfoRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.xmlworkflow.state.actions.processingaction.ScoreReviewActionAdvancedInfo; + +/** + * This converter is responsible for transforming the model representation of a ScoreReviewActionAdvancedInfo to + * the REST representation of a ScoreReviewActionAdvancedInfo + */ +public class ScoreReviewActionAdvancedInfoConverter + implements DSpaceConverter { + + @Override + public ScoreReviewActionAdvancedInfoRest convert(ScoreReviewActionAdvancedInfo modelObject, + Projection projection) { + ScoreReviewActionAdvancedInfoRest restModel = new ScoreReviewActionAdvancedInfoRest(); + restModel.setDescriptionRequired(modelObject.isDescriptionRequired()); + restModel.setMaxValue(modelObject.getMaxValue()); + restModel.setType(modelObject.getType()); + restModel.setId(modelObject.getId()); + return restModel; + } + + @Override + public Class getModelClass() { + return ScoreReviewActionAdvancedInfo.class; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SelectReviewerActionAdvancedInfoConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SelectReviewerActionAdvancedInfoConverter.java new file mode 100644 index 0000000000..3dd8f0b3b7 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SelectReviewerActionAdvancedInfoConverter.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.rest.model.SelectReviewerActionAdvancedInfoRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.xmlworkflow.state.actions.processingaction.SelectReviewerActionAdvancedInfo; + +/** + * This converter is responsible for transforming the model representation of a SelectReviewerActionAdvancedInfo to + * the REST representation of a SelectReviewerActionAdvancedInfo + */ +public class SelectReviewerActionAdvancedInfoConverter + implements DSpaceConverter { + + @Override + public SelectReviewerActionAdvancedInfoRest convert(SelectReviewerActionAdvancedInfo modelObject, + Projection projection) { + SelectReviewerActionAdvancedInfoRest restModel = new SelectReviewerActionAdvancedInfoRest(); + restModel.setGroup(modelObject.getGroup()); + restModel.setType(modelObject.getType()); + restModel.setId(modelObject.getId()); + return restModel; + } + + @Override + public Class getModelClass() { + return SelectReviewerActionAdvancedInfo.class; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubscriptionConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubscriptionConverter.java new file mode 100644 index 0000000000..cd491ecb17 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubscriptionConverter.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.rest.converter; + +import java.util.ArrayList; +import java.util.List; + +import org.dspace.app.rest.model.SubscriptionParameterRest; +import org.dspace.app.rest.model.SubscriptionRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.app.rest.utils.Utils; +import org.dspace.eperson.Subscription; +import org.dspace.eperson.SubscriptionParameter; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * This is the converter from Entity Subscription to the REST data model + * + * @author Alba Aliu at atis.al + * + */ +@Component +public class SubscriptionConverter implements DSpaceConverter { + + @Autowired + protected Utils utils; + + @Override + public SubscriptionRest convert(Subscription subscription, Projection projection) { + SubscriptionRest rest = new SubscriptionRest(); + rest.setProjection(projection); + rest.setId(subscription.getID()); + List subscriptionParameterRestList = new ArrayList<>(); + for (SubscriptionParameter subscriptionParameter : subscription.getSubscriptionParameterList()) { + SubscriptionParameterRest subscriptionParameterRest = new SubscriptionParameterRest(); + subscriptionParameterRest.setName(subscriptionParameter.getName()); + subscriptionParameterRest.setValue(subscriptionParameter.getValue()); + subscriptionParameterRestList.add(subscriptionParameterRest); + } + rest.setSubscriptionParameterList(subscriptionParameterRestList); + rest.setSubscriptionType(subscription.getSubscriptionType()); + return rest; + } + + /* (non-Javadoc) + * @see org.dspace.app.rest.converter.DSpaceConverter#getModelClass() + */ + @Override + public Class getModelClass() { + return Subscription.class; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SupervisionOrderConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SupervisionOrderConverter.java new file mode 100644 index 0000000000..e9ffb22446 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SupervisionOrderConverter.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.converter; + +import java.util.Objects; + +import org.dspace.app.rest.model.SupervisionOrderRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.content.Item; +import org.dspace.eperson.Group; +import org.dspace.supervision.SupervisionOrder; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +/** + * This class is responsible to convert SupervisionOrder to its rest model + * + * @author Mohamed Eskander (mohamed.eskander at 4science dot it) + */ +@Component +public class SupervisionOrderConverter + implements DSpaceConverter { + + @Lazy + @Autowired + private ConverterService converter; + + @Override + public SupervisionOrderRest convert(SupervisionOrder modelObject, Projection projection) { + + SupervisionOrderRest rest = new SupervisionOrderRest(); + Item item = modelObject.getItem(); + Group group = modelObject.getGroup(); + + rest.setId(modelObject.getID()); + + if (Objects.nonNull(item)) { + rest.setItem(converter.toRest(item, projection)); + } + + if (Objects.nonNull(group)) { + rest.setGroup(converter.toRest(group, projection)); + } + + rest.setProjection(projection); + + return rest; + } + + @Override + public Class getModelClass() { + return SupervisionOrder.class; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SystemWideAlertConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SystemWideAlertConverter.java new file mode 100644 index 0000000000..419f2cf1d1 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SystemWideAlertConverter.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.app.rest.converter; + +import org.dspace.alerts.SystemWideAlert; +import org.dspace.app.rest.model.SystemWideAlertRest; +import org.dspace.app.rest.projection.Projection; +import org.springframework.stereotype.Component; + +/** + * This converter will convert an object of {@Link SystemWideAlert} to an object of {@link SystemWideAlertRest} + */ +@Component +public class SystemWideAlertConverter implements DSpaceConverter { + + + @Override + public SystemWideAlertRest convert(SystemWideAlert systemWideAlert, Projection projection) { + SystemWideAlertRest systemWideAlertRest = new SystemWideAlertRest(); + systemWideAlertRest.setProjection(projection); + systemWideAlertRest.setId(systemWideAlert.getID()); + systemWideAlertRest.setAlertId(systemWideAlert.getID()); + systemWideAlertRest.setMessage(systemWideAlert.getMessage()); + systemWideAlertRest.setAllowSessions(systemWideAlert.getAllowSessions().getValue()); + systemWideAlertRest.setCountdownTo(systemWideAlert.getCountdownTo()); + systemWideAlertRest.setActive(systemWideAlert.isActive()); + return systemWideAlertRest; + } + + @Override + public Class getModelClass() { + return SystemWideAlert.class; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/WorkflowActionConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/WorkflowActionConverter.java index ee6479433e..f905bbf1b3 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/WorkflowActionConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/WorkflowActionConverter.java @@ -26,6 +26,10 @@ public class WorkflowActionConverter implements DSpaceConverter + */ +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"; + private String value; + private String identifierType; + private String identifierStatus; + + // Empty constructor + public IdentifierRest() { + } + + /** + * Constructor that takes a value, type and status for an identifier + * @param value the identifier value eg. https://doi.org/123/234 + * @param identifierType identifier type eg. doi + * @param identifierStatus identifier status eg. TO_BE_REGISTERED + */ + public IdentifierRest(String value, String identifierType, String identifierStatus) { + this.value = value; + this.identifierType = identifierType; + this.identifierStatus = identifierStatus; + } + + /** + * Return name for getType() - this is the section name + * and not the type of identifier, see: identifierType string + * @return + */ + @Override + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + public String getType() { + return NAME; + } + + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + + /** + * Get the identifier value eg full DOI URL + * @return identifier value eg. https://doi.org/123/234 + */ + public String getValue() { + return value; + } + + /** + * Set the identifier value + * @param value identifier value, eg. https://doi.org/123/234 + */ + public void setValue(String value) { + this.value = value; + } + + /** + * Get type of identifier eg 'doi' or 'handle' + * @return type string + */ + public String getIdentifierType() { + return identifierType; + } + + /** + * Set type of identifier + * @param identifierType type string eg 'doi' + */ + public void setIdentifierType(String identifierType) { + this.identifierType = identifierType; + } + + /** + * Get status of identifier, if relevant + * @return identifierStatus eg. null or TO_BE_REGISTERED + */ + public String getIdentifierStatus() { + return identifierStatus; + } + + /** + * Set status of identifier, if relevant + * @param identifierStatus eg. null or TO_BE_REGISTERED + */ + public void setIdentifierStatus(String identifierStatus) { + this.identifierStatus = identifierStatus; + } + + @Override + public String getCategory() { + return "pid"; + } + + @Override + public String getId() { + return getValue(); + } + + @Override + public Class getController() { + return RestResourceController.class; + } +} 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 new file mode 100644 index 0000000000..169e40979c --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/IdentifiersRest.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 java.util.ArrayList; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Implementation of IdentifierRest REST resource, representing a list of all identifiers + * for use with the REST API + * + * @author Kim Shepherd + */ +public class IdentifiersRest extends BaseObjectRest { + + // Set names used in component wiring + public static final String NAME = "identifiers"; + private List identifiers; + + // Empty constructor + public IdentifiersRest() { + identifiers = new ArrayList<>(); + } + + // Return name for getType() + // Note this is the section name, NOT the identifier type + @Override + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + public String getType() { + return NAME; + } + + public List getIdentifiers() { + return identifiers; + } + + public void setIdentifiers(List identifiers) { + this.identifiers = identifiers; + } + + @Override + public String getCategory() { + return null; + } + + @Override + public Class getController() { + return null; + } +} 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 63004b68d2..1254ef8f93 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 @@ -25,6 +25,10 @@ import com.fasterxml.jackson.annotation.JsonProperty; name = ItemRest.BUNDLES, method = "getBundles" ), + @LinkRest( + name = ItemRest.IDENTIFIERS, + method = "getIdentifiers" + ), @LinkRest( name = ItemRest.MAPPED_COLLECTIONS, method = "getMappedCollections" @@ -57,6 +61,7 @@ public class ItemRest extends DSpaceObjectRest { public static final String ACCESS_STATUS = "accessStatus"; public static final String BUNDLES = "bundles"; + public static final String IDENTIFIERS = "identifiers"; public static final String MAPPED_COLLECTIONS = "mappedCollections"; public static final String OWNING_COLLECTION = "owningCollection"; public static final String RELATIONSHIPS = "relationships"; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ScoreReviewActionAdvancedInfoRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ScoreReviewActionAdvancedInfoRest.java new file mode 100644 index 0000000000..14644be151 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ScoreReviewActionAdvancedInfoRest.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.model; + +/** + * The ScoreReviewActionAdvancedInfo REST Resource, + * see {@link org.dspace.xmlworkflow.state.actions.processingaction.ScoreReviewActionAdvancedInfo} + */ +public class ScoreReviewActionAdvancedInfoRest extends AdvancedInfoRest { + + private boolean descriptionRequired; + private int maxValue; + + public boolean isDescriptionRequired() { + return descriptionRequired; + } + + public void setDescriptionRequired(boolean descriptionRequired) { + this.descriptionRequired = descriptionRequired; + } + + public int getMaxValue() { + return maxValue; + } + + public void setMaxValue(int maxValue) { + this.maxValue = maxValue; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SelectReviewerActionAdvancedInfoRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SelectReviewerActionAdvancedInfoRest.java new file mode 100644 index 0000000000..86b2003b07 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SelectReviewerActionAdvancedInfoRest.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; + +/** + * The SelectReviewerActionAdvancedInfoRest REST Resource, + * see {@link org.dspace.xmlworkflow.state.actions.processingaction.SelectReviewerActionAdvancedInfo} + */ +public class SelectReviewerActionAdvancedInfoRest extends AdvancedInfoRest { + + private String groupId; + + public String getGroup() { + return groupId; + } + + public void setGroup(String groupId) { + this.groupId = groupId; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubscriptionParameterRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubscriptionParameterRest.java new file mode 100644 index 0000000000..ab815f0d3b --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubscriptionParameterRest.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.app.rest.model; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import org.dspace.eperson.Subscription; + +/** + * @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com) + */ +public class SubscriptionParameterRest { + + @JsonIgnore + private Integer id; + private String name; + private String value; + + public SubscriptionParameterRest() {} + + public SubscriptionParameterRest(Integer id, String name, String value, Subscription subscription) { + this.id = id; + this.name = name; + this.value = value; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + +} 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 new file mode 100644 index 0000000000..78a81c38b1 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubscriptionRest.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.ArrayList; +import java.util.List; + +import org.dspace.app.rest.RestResourceController; + +@LinksRest(links = { + @LinkRest(name = SubscriptionRest.DSPACE_OBJECT, method = "getDSpaceObject"), + @LinkRest(name = SubscriptionRest.EPERSON, method = "getEPerson") +}) +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 DSPACE_OBJECT = "resource"; + public static final String EPERSON = "eperson"; + + private Integer id; + private String subscriptionType; + private List subscriptionParameterList = new ArrayList<>(); + + @Override + public String getCategory() { + return CATEGORY; + } + + @Override + public Class getController() { + return RestResourceController.class; + } + + @Override + public String getType() { + return NAME; + } + + public void setSubscriptionType(String type) { + this.subscriptionType = type; + } + + public List getSubscriptionParameterList() { + return subscriptionParameterList; + } + + public void setSubscriptionParameterList(List subscriptionParameterList) { + this.subscriptionParameterList = subscriptionParameterList; + } + + public String getSubscriptionType() { + return this.subscriptionType; + } + + @Override + public Integer getId() { + return id; + } + + @Override + public void setId(Integer id) { + this.id = id; + } + +} 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 new file mode 100644 index 0000000000..e114fdeb39 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SupervisionOrderRest.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.model; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import org.dspace.app.rest.RestResourceController; +import org.dspace.supervision.SupervisionOrder; + +/** + * The REST Resource of {@link SupervisionOrder}. + * + * @author Mohamed Eskander (mohamed.eskander at 4science.it) + */ +public class SupervisionOrderRest extends BaseObjectRest { + + public static final String NAME = "supervisionorder"; + public static final String CATEGORY = RestAddressableModel.CORE; + + private Integer id; + + @JsonIgnore + private ItemRest item; + + @JsonIgnore + private GroupRest group; + + @Override + public Integer getId() { + return id; + } + + @Override + public void setId(Integer id) { + this.id = id; + } + + public ItemRest getItem() { + return item; + } + + public void setItem(ItemRest item) { + this.item = item; + } + + public GroupRest getGroup() { + return group; + } + + public void setGroup(GroupRest group) { + this.group = group; + } + + @Override + public String getCategory() { + return CATEGORY; + } + + @Override + public Class getController() { + return RestResourceController.class; + } + + @Override + public String getType() { + return 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 new file mode 100644 index 0000000000..995ec8e934 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SystemWideAlertRest.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.Date; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.dspace.alerts.SystemWideAlert; +import org.dspace.app.rest.RestResourceController; + +/** + * This class serves as a REST representation for the {@link SystemWideAlert} class + */ +public class SystemWideAlertRest extends BaseObjectRest { + public static final String NAME = "systemwidealert"; + public static final String CATEGORY = RestAddressableModel.SYSTEM; + + public String getCategory() { + return CATEGORY; + } + + public Class getController() { + return RestResourceController.class; + } + + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + public String getType() { + return NAME; + } + + private Integer alertId; + private String message; + private String allowSessions; + private Date countdownTo; + private boolean active; + + public Integer getAlertId() { + return alertId; + } + + public void setAlertId(final Integer alertID) { + this.alertId = alertID; + } + + public String getMessage() { + return message; + } + + public void setMessage(final String message) { + this.message = message; + } + + public String getAllowSessions() { + return allowSessions; + } + + public void setAllowSessions(final String allowSessions) { + this.allowSessions = allowSessions; + } + + public Date getCountdownTo() { + return countdownTo; + } + + public void setCountdownTo(final Date countdownTo) { + this.countdownTo = countdownTo; + } + + public boolean isActive() { + return active; + } + + public void setActive(final boolean active) { + this.active = active; + } + + @JsonIgnore + @Override + public Integer getId() { + return id; + } +} 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 e998df6bc2..07a2c36cff 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 @@ -9,7 +9,10 @@ package org.dspace.app.rest.model; import java.util.List; +import com.fasterxml.jackson.annotation.JsonInclude; +import org.apache.commons.collections4.CollectionUtils; import org.dspace.app.rest.RestResourceController; +import org.dspace.xmlworkflow.state.actions.ActionAdvancedInfo; /** * The rest resource used for workflow actions @@ -23,6 +26,8 @@ public class WorkflowActionRest extends BaseObjectRest { public static final String NAME_PLURAL = "workflowactions"; private List options; + private List advancedOptions; + private List advancedInfo; @Override public String getCategory() { @@ -39,21 +44,33 @@ public class WorkflowActionRest extends BaseObjectRest { return NAME; } - /** - * Generic getter for the options - * - * @return the options value of this WorkflowActionRest - */ public List getOptions() { return options; } - /** - * Generic setter for the options - * - * @param options The options to be set on this WorkflowActionRest - */ public void setOptions(List options) { this.options = options; } + + @JsonInclude(JsonInclude.Include.NON_NULL) + public List getAdvancedOptions() { + return advancedOptions; + } + + public void setAdvancedOptions(List advancedOptions) { + this.advancedOptions = advancedOptions; + } + + public boolean getAdvanced() { + return CollectionUtils.isNotEmpty(getAdvancedOptions()); + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + public List getAdvancedInfo() { + return advancedInfo; + } + + public void setAdvancedInfo(List advancedInfo) { + this.advancedInfo = advancedInfo; + } } 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 69fefd1a9b..57a5ab5c7f 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 @@ -14,10 +14,18 @@ import org.dspace.app.rest.RestResourceController; * * @author Andrea Bollini (andrea.bollini at 4science.it) */ +@LinksRest(links = { + @LinkRest( + name = WorkspaceItemRest.SUPERVISION_ORDERS, + method = "getSupervisionOrders" + ) +}) public class WorkspaceItemRest extends AInprogressSubmissionRest { public static final String NAME = "workspaceitem"; public static final String CATEGORY = RestAddressableModel.SUBMISSION; + public static final String SUPERVISION_ORDERS = "supervisionOrders"; + @Override public String getCategory() { return CATEGORY; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/IdentifierResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/IdentifierResource.java new file mode 100644 index 0000000000..e25f04f6c9 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/IdentifierResource.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.IdentifierRest; +import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; +import org.dspace.app.rest.utils.Utils; + +/** + * + * Simple HAL wrapper for IdentifierRest model + * + * @author Kim Shepherd + */ +@RelNameDSpaceResource(IdentifierRest.NAME) +public class IdentifierResource extends DSpaceResource { + public IdentifierResource(IdentifierRest model, Utils utils) { + super(model, utils); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/IdentifiersResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/IdentifiersResource.java new file mode 100644 index 0000000000..2c6453d8cb --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/IdentifiersResource.java @@ -0,0 +1,24 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the 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.IdentifiersRest; +import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; +import org.dspace.app.rest.utils.Utils; + +/** + * Boilerplate hateos resource for IdentifiersRest + * + * @author Kim Shepherd + */ +@RelNameDSpaceResource(IdentifiersRest.NAME) +public class IdentifiersResource extends HALResource { + public IdentifiersResource(IdentifiersRest data, Utils utils) { + super(data); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SubscriptionResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SubscriptionResource.java new file mode 100644 index 0000000000..2e7ea1618a --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SubscriptionResource.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.SubscriptionRest; +import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; +import org.dspace.app.rest.utils.Utils; + +/** + * The Resource representation of a Subscription object + */ +@RelNameDSpaceResource(SubscriptionRest.NAME) +public class SubscriptionResource extends DSpaceResource { + public SubscriptionResource(SubscriptionRest data, Utils utils) { + super(data, utils); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SupervisionOrderResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SupervisionOrderResource.java new file mode 100644 index 0000000000..06439f29ef --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SupervisionOrderResource.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.SupervisionOrderRest; +import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; +import org.dspace.app.rest.utils.Utils; + +/** + * SupervisionOrder 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 dot it) + */ +@RelNameDSpaceResource(SupervisionOrderRest.NAME) +public class SupervisionOrderResource extends DSpaceResource { + public SupervisionOrderResource(SupervisionOrderRest data, Utils utils) { + super(data, utils); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SystemWideAlertResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SystemWideAlertResource.java new file mode 100644 index 0000000000..9089103d2b --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SystemWideAlertResource.java @@ -0,0 +1,23 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the 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.alerts.SystemWideAlert; +import org.dspace.app.rest.model.SystemWideAlertRest; +import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; +import org.dspace.app.rest.utils.Utils; + +/** + * The Resource representation of a {@link SystemWideAlert} object + */ +@RelNameDSpaceResource(SystemWideAlertRest.NAME) +public class SystemWideAlertResource extends DSpaceResource { + public SystemWideAlertResource(SystemWideAlertRest content, Utils utils) { + super(content, utils); + } +} 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 new file mode 100644 index 0000000000..01e0eabdd3 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/step/DataIdentifiers.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.rest.model.step; + +import java.util.ArrayList; +import java.util.List; + +import org.dspace.app.rest.model.IdentifierRest; +/** + * Java bean with basic DOI / Handle / other identifier data for + * display in submission step + * + * @author Kim Shepherd (kim@shepherd.nz) + */ +public class DataIdentifiers implements SectionData { + // Map of identifier types and values + List identifiers; + // Types to display, a hint for te UI + List displayTypes; + + public DataIdentifiers() { + identifiers = new ArrayList<>(); + displayTypes = new ArrayList<>(); + } + + public List getIdentifiers() { + return identifiers; + } + + public void setIdentifiers(List identifiers) { + this.identifiers = identifiers; + } + + public void addIdentifier(String type, String value, String status) { + IdentifierRest identifier = new IdentifierRest(); + identifier.setValue(value); + identifier.setIdentifierType(type); + identifier.setIdentifierStatus(status); + this.identifiers.add(identifier); + } + + public List getDisplayTypes() { + return displayTypes; + } + + public void setDisplayTypes(List displayTypes) { + this.displayTypes = displayTypes; + } + + public String toString() { + StringBuilder sb = new StringBuilder(); + for (IdentifierRest identifier : identifiers) { + sb.append(identifier.getType()).append(": ").append(identifier.getValue()).append("\n"); + } + return sb.toString(); + } + +} 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 97e6866073..1379528669 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 @@ -31,6 +31,7 @@ import org.dspace.app.rest.model.BaseObjectRest; import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.service.AuthorizeService; import org.dspace.core.Context; +import org.dspace.discovery.SearchServiceException; import org.dspace.eperson.EPerson; import org.dspace.eperson.service.EPersonService; import org.slf4j.Logger; @@ -124,7 +125,7 @@ public class AuthorizationRestRepository extends DSpaceRestRepository findByObject(@Parameter(value = "uri", required = true) String uri, @Parameter(value = "eperson") UUID epersonUuid, @Parameter(value = "feature") String featureName, - Pageable pageable) throws AuthorizeException, SQLException { + Pageable pageable) throws AuthorizeException, SQLException, SearchServiceException { Context context = obtainContext(); @@ -234,7 +235,7 @@ public class AuthorizationRestRepository extends DSpaceRestRepository(); @@ -269,7 +270,7 @@ public class AuthorizationRestRepository extends DSpaceRestRepository findByObjectAndFeature( Context context, EPerson user, BaseObjectRest obj, String featureName - ) throws SQLException { + ) throws SQLException, SearchServiceException { AuthorizationFeature feature = authorizationFeatureService.find(featureName); 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 01277ff29b..8ffefb619b 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 @@ -7,12 +7,16 @@ */ package org.dspace.app.rest.repository; +import java.sql.SQLException; import java.util.Arrays; import java.util.List; +import org.dspace.app.rest.Parameter; +import org.dspace.app.rest.SearchRestMethod; import org.dspace.app.rest.model.BrowseIndexRest; import org.dspace.browse.BrowseException; import org.dspace.browse.BrowseIndex; +import org.dspace.browse.CrossLinks; import org.dspace.core.Context; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -53,6 +57,37 @@ public class BrowseIndexRestRepository extends DSpaceRestRepository getDomainClass() { return BrowseIndexRest.class; 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 c77dcf18dc..3c728d8c31 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 @@ -8,6 +8,7 @@ package org.dspace.app.rest.repository; import java.sql.SQLException; +import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.UUID; @@ -31,6 +32,7 @@ import org.dspace.discovery.indexobject.factory.IndexObjectFactoryFactory; 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.Order; import org.springframework.data.rest.webmvc.ResourceNotFoundException; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Component; @@ -72,6 +74,14 @@ public class CommunityCollectionLinkRepository extends AbstractDSpaceRestReposit discoverQuery.setStart(Math.toIntExact(pageable.getOffset())); discoverQuery.setMaxResults(pageable.getPageSize()); discoverQuery.setSortField("dc.title_sort", DiscoverQuery.SORT_ORDER.asc); + Iterator orderIterator = pageable.getSort().iterator(); + if (orderIterator.hasNext()) { + Order order = orderIterator.next(); + discoverQuery.setSortField( + order.getProperty() + "_sort", + order.getDirection().isAscending() ? DiscoverQuery.SORT_ORDER.asc : DiscoverQuery.SORT_ORDER.desc + ); + } DiscoverResult resp = searchService.search(context, scopeObject, discoverQuery); long tot = resp.getTotalSearchResults(); for (IndexableObject solrCol : resp.getIndexableObjects()) { 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 c211810d11..135d964f3f 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 @@ -8,6 +8,7 @@ package org.dspace.app.rest.repository; import java.sql.SQLException; +import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.UUID; @@ -29,6 +30,7 @@ import org.dspace.discovery.indexobject.factory.IndexObjectFactoryFactory; 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.Order; import org.springframework.data.rest.webmvc.ResourceNotFoundException; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Component; @@ -68,6 +70,14 @@ public class CommunitySubcommunityLinkRepository extends AbstractDSpaceRestRepos discoverQuery.setStart(Math.toIntExact(pageable.getOffset())); discoverQuery.setMaxResults(pageable.getPageSize()); discoverQuery.setSortField("dc.title_sort", DiscoverQuery.SORT_ORDER.asc); + Iterator orderIterator = pageable.getSort().iterator(); + if (orderIterator.hasNext()) { + Order order = orderIterator.next(); + discoverQuery.setSortField( + order.getProperty() + "_sort", + order.getDirection().isAscending() ? DiscoverQuery.SORT_ORDER.asc : DiscoverQuery.SORT_ORDER.desc + ); + } DiscoverResult resp = searchService.search(context, scopeObject, discoverQuery); long tot = resp.getTotalSearchResults(); for (IndexableObject solrCommunities : resp.getIndexableObjects()) { 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 01f127eca5..a93f5e55dc 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 @@ -195,7 +195,11 @@ public abstract class DSpaceRestRepository implements InitializingBean { + @Autowired + private DiscoverableEndpointsService discoverableEndpointsService; + @Autowired + private UriListHandlerService uriListHandlerService; + @Autowired + private DOIService doiService; + @Autowired + private HandleService handleService; + @Autowired + private ItemService itemService; + + // Set category and name for routing + public static final String CATEGORY = "pid"; + public static final String NAME = IdentifierRest.NAME; + + /** + * Register /api/pid/find?id=... as a discoverable endpoint service + * + * @throws Exception + */ + @Override + public void afterPropertiesSet() throws Exception { + discoverableEndpointsService + .register(this, + Arrays.asList(Link.of(UriTemplate.of("/api/pid/find", + new TemplateVariables( + new TemplateVariable("id", + TemplateVariable.VariableType.REQUEST_PARAM))), + CATEGORY))); + } + + /** + * Find all identifiers. Not implemented. + * @param context + * the dspace context + * @param pageable + * object embedding the requested pagination info + * @return + */ + @PreAuthorize("permitAll()") + @Override + public Page findAll(Context context, Pageable pageable) { + throw new RepositoryMethodNotImplementedException(IdentifierRest.NAME, "findAll"); + } + + /** + * Find the identifier object for a given identifier string (eg. doi). + * Not implemented -- Tomcat interprets %2F as path separators which means + * parameters are a safer way to handle these operations + * + * @param context + * the dspace context + * @param identifier + * the rest object id + * @return + */ + @PreAuthorize("permitAll()") + @Override + public IdentifierRest findOne(Context context, String identifier) { + throw new RepositoryMethodNotImplementedException(IdentifierRest.NAME, "findOne"); + } + + /** + * Find identifiers associated with a given item + * @param uuid + * @param pageable + * @return + */ + @SearchRestMethod(name = "findByItem") + @PreAuthorize("permitAll()") + public Page findByItem(@Parameter(value = "uuid", required = true) + String uuid, Pageable pageable) { + Context context = obtainContext(); + List results = new ArrayList<>(); + try { + DSpaceObject dso = itemService.find(context, UUID.fromString(uuid)); + String handle = dso.getHandle(); + DOI doi = doiService.findDOIByDSpaceObject(context, dso); + if (doi != null) { + String doiUrl = doiService.DOIToExternalForm(doi.getDoi()); + results.add(new IdentifierRest(doiUrl, "doi", DOIIdentifierProvider.statusText[doi.getStatus()])); + } + if (handle != null) { + String handleUrl = handleService.getCanonicalForm(handle); + results.add(new IdentifierRest(handleUrl, "handle", null)); + } + } catch (SQLException | IdentifierException e) { + throw new LinkNotFoundException(IdentifierRestRepository.CATEGORY, IdentifierRest.NAME, uuid); + } + // Return list of identifiers for this DSpaceObject + return new PageImpl<>(results, pageable, results.size()); + } + + /** + * Create (mint / queue for registration) a new persistent identifier of a given type (eg DOI) for an item + * Currently, the only supported identifier type for this operation is "doi" + * + * @param context + * the dspace context + * @param list + * A uri-list with the item URI for which to create an identifier + * @return 201 Created with object JSON on success + * @throws AuthorizeException + * @throws SQLException + * @throws RepositoryMethodNotImplementedException + */ + @Override + protected IdentifierRest createAndReturn(Context context, List list) + throws AuthorizeException, SQLException, RepositoryMethodNotImplementedException { + HttpServletRequest request = getRequestService().getCurrentRequest().getHttpServletRequest(); + // Extract 'type' from request + String type = request.getParameter("type"); + if (!"doi".equals(type)) { + throw new NotSupportedException("Only identifiers of type 'doi' are supported"); + } + IdentifierRest identifierRest = new IdentifierRest(); + try { + Item item = uriListHandlerService.handle(context, request, list, Item.class); + if (item == null) { + throw new UnprocessableEntityException( + "No DSpace Item found, the uri-list does not contain a valid resource"); + } + // Does this item have a DOI already? If the DOI doesn't exist or has a null, MINTED or PENDING status + // then we proceed with a typical create operation and return 201 success with the object + DOI doi = doiService.findDOIByDSpaceObject(context, item); + if (doi == null || null == doi.getStatus() || DOIIdentifierProvider.MINTED.equals(doi.getStatus()) + || DOIIdentifierProvider.PENDING.equals(doi.getStatus())) { + // Proceed with creation + // Register things + identifierRest = registerDOI(context, item); + } else { + // Return bad request exception, as per other createAndReturn implementations (eg EPerson) + throw new DSpaceBadRequestException("The DOI is already registered or queued to be registered"); + } + } catch (AuthorizeException e) { + throw new RESTAuthorizationException(e); + } + return identifierRest; + } + + /** + * Perform DOI registration, skipping any other filters used. + * + * @param context + * @param item + * @return + * @throws SQLException + * @throws AuthorizeException + */ + private IdentifierRest registerDOI(Context context, Item item) + throws SQLException, AuthorizeException { + String identifier = null; + IdentifierRest identifierRest = new IdentifierRest(); + identifierRest.setIdentifierType("doi"); + try { + DOIIdentifierProvider doiIdentifierProvider = DSpaceServicesFactory.getInstance().getServiceManager() + .getServiceByName("org.dspace.identifier.DOIIdentifierProvider", DOIIdentifierProvider.class); + if (doiIdentifierProvider != null) { + String doiValue = doiIdentifierProvider.register(context, item, new TrueFilter()); + identifierRest.setValue(doiValue); + // Get new status + DOI doi = doiService.findByDoi(context, doiValue); + if (doi != null) { + identifierRest.setIdentifierStatus(DOIIdentifierProvider.statusText[doi.getStatus()]); + } + } else { + throw new IllegalStateException("No DOI provider is configured"); + } + } catch (IdentifierException e) { + throw new IllegalStateException("Failed to register identifier: " + identifier); + } + // We didn't exactly change the item, but we did queue an identifier which is closely associated with it, + // so we should update the last modified date here + itemService.updateLastModified(context, item); + context.complete(); + return identifierRest; + } + + + /** + * Redirect to a DSO page, given an identifier + * + * @param request HTTP request + * @param response HTTP response + * @param id The persistent identifier (eg. handle, DOI) to search for + * @throws IOException + * @throws SQLException + */ + @RequestMapping(method = RequestMethod.GET, value = "find", params = "id") + @SuppressWarnings("unchecked") + public void getDSObyIdentifier(HttpServletRequest request, + HttpServletResponse response, + @RequestParam("id") String id) + throws IOException, SQLException { + + DSpaceObject dso; + Context context = ContextUtil.obtainContext(request); + IdentifierService identifierService = IdentifierServiceFactory + .getInstance().getIdentifierService(); + try { + // Resolve identifier to a DSpace object + dso = identifierService.resolve(context, id); + 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())) + .slash(dsor.getId()).toUri(); + response.setStatus(HttpServletResponse.SC_FOUND); + response.sendRedirect(link.toString()); + } else { + // No object could be found + response.setStatus(HttpServletResponse.SC_NOT_FOUND); + } + } catch (IdentifierNotFoundException e) { + response.setStatus(HttpServletResponse.SC_NOT_FOUND); + } catch (IdentifierNotResolvableException e) { + response.setStatus(HttpServletResponse.SC_NOT_IMPLEMENTED); + } finally { + context.abort(); + } + } + + @Override + public Class getDomainClass() { + return IdentifierRest.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 new file mode 100644 index 0000000000..0714b7329b --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemIdentifierLinkRepository.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.repository; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; + +import org.dspace.app.rest.model.IdentifierRest; +import org.dspace.app.rest.model.IdentifiersRest; +import org.dspace.app.rest.model.ItemRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.content.Item; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.dspace.handle.factory.HandleServiceFactory; +import org.dspace.handle.service.HandleService; +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; +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 the identifier of an Item + */ +@Component(ItemRest.CATEGORY + "." + ItemRest.NAME + "." + ItemRest.IDENTIFIERS) +public class ItemIdentifierLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { + @Autowired + ItemService itemService; + + @Autowired + IdentifierService identifierService; + + @Autowired + DOIService doiService; + @Autowired + HandleService handleService; + + @PreAuthorize("hasPermission(#itemId, 'ITEM', 'READ')") + public IdentifiersRest getIdentifiers(@Nullable HttpServletRequest request, + UUID itemId, + @Nullable Pageable optionalPageable, + Projection projection) throws SQLException { + Context context = ContextUtil.obtainCurrentRequestContext(); + Item item = itemService.find(context, itemId); + if (item == null) { + throw new ResourceNotFoundException("Could not find item with id " + itemId); + } + IdentifiersRest identifiersRest = new IdentifiersRest(); + List identifierRestList = new ArrayList<>(); + DOI doi = doiService.findDOIByDSpaceObject(context, item); + String handle = HandleServiceFactory.getInstance().getHandleService().findHandle(context, item); + try { + if (doi != null) { + String doiUrl = doiService.DOIToExternalForm(doi.getDoi()); + identifierRestList.add(new IdentifierRest( + doiUrl, "doi", DOIIdentifierProvider.statusText[doi.getStatus()])); + } + if (handle != null) { + identifierRestList.add(new IdentifierRest(handleService.getCanonicalForm(handle), "handle", null)); + } + } catch (IdentifierException e) { + throw new IllegalStateException("Failed to register identifier: " + e.getMessage()); + } + identifiersRest.setIdentifiers(identifierRestList); + return identifiersRest; + } +} 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 e6a794392a..be28170d8a 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 @@ -26,6 +26,7 @@ import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.RegistrationRest; import org.dspace.app.util.AuthorizeUtil; +import org.dspace.authenticate.service.AuthenticationService; import org.dspace.authorize.AuthorizeException; import org.dspace.core.Context; import org.dspace.eperson.EPerson; @@ -52,12 +53,19 @@ public class RegistrationRestRepository extends DSpaceRestRepository authorizers; - try { - authorizers = requestItemAuthorExtractor.getRequestItemAuthor(context, ri.getItem()); - } catch (SQLException ex) { - LOG.warn("Failed to find an authorizer: {}", ex.getMessage()); - authorizers = Collections.EMPTY_LIST; - } - - boolean authorized = false; - String requester = context.getCurrentUser().getEmail(); - for (RequestItemAuthor authorizer : authorizers) { - authorized |= authorizer.getEmail().equals(requester); - } - if (!authorized) { - throw new AuthorizeException("Not authorized to approve this request"); - } - // Do not permit updates after a decision has been given. Date decisionDate = ri.getDecision_date(); if (null != decisionDate) { @@ -275,7 +255,7 @@ public class RequestItemRepository try { RequestItemEmailNotifier.sendResponse(context, ri, subject, message); } catch (IOException ex) { - LOG.warn("Response not sent: {}", ex.getMessage()); + LOG.warn("Response not sent: {}", ex::getMessage); throw new RuntimeException("Response not sent", ex); } @@ -284,7 +264,7 @@ public class RequestItemRepository try { RequestItemEmailNotifier.requestOpenAccess(context, ri); } catch (IOException ex) { - LOG.warn("Open access request not sent: {}", ex.getMessage()); + LOG.warn("Open access request not sent: {}", ex::getMessage); throw new RuntimeException("Open access request not sent", ex); } } 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 13d5a560dc..d974a6d78a 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 @@ -80,7 +80,8 @@ public class ScriptRestRepository extends DSpaceRestRepository findAll(Context context, Pageable pageable) { - List scriptConfigurations = scriptService.getScriptConfigurations(context); + List scriptConfigurations = + scriptService.getScriptConfigurations(context); return converter.toRestPage(scriptConfigurations, pageable, utils.obtainProjection()); } 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 0dab42f9bd..356b3f22bf 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 @@ -8,11 +8,14 @@ package org.dspace.app.rest.repository; import java.util.List; +import java.util.Locale; +import org.apache.commons.lang3.StringUtils; import org.dspace.app.rest.model.SubmissionCCLicenseRest; import org.dspace.core.Context; import org.dspace.license.CCLicense; import org.dspace.license.service.CreativeCommonsService; +import org.dspace.services.ConfigurationService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -29,10 +32,23 @@ public class SubmissionCCLicenseRestRepository extends DSpaceRestRepository findAll(final Context context, final Pageable pageable) { - List allCCLicenses = creativeCommonsService.findAllCCLicenses(); + String defaultCCLocale = configurationService.getProperty("cc.license.locale"); + + Locale currentLocale = context.getCurrentLocale(); + List allCCLicenses; + // when no default CC locale is defined, current locale is used + if (currentLocale != null && StringUtils.isBlank(defaultCCLocale)) { + allCCLicenses = creativeCommonsService.findAllCCLicenses(currentLocale.toString()); + } else { + allCCLicenses = creativeCommonsService.findAllCCLicenses(); + } return converter.toRestPage(allCCLicenses, pageable, utils.obtainProjection()); } 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 new file mode 100644 index 0000000000..95c4714e9c --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionDSpaceObjectLinkRepository.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.repository; + +import java.sql.SQLException; +import java.util.Objects; +import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; + +import org.dspace.app.rest.model.DSpaceObjectRest; +import org.dspace.app.rest.model.SubscriptionRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.eperson.Subscription; +import org.dspace.eperson.service.SubscribeService; +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 "DSpaceObject" of subscription + */ +@Component(SubscriptionRest.CATEGORY + "." + SubscriptionRest.NAME + "." + SubscriptionRest.DSPACE_OBJECT) +public class SubscriptionDSpaceObjectLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { + + @Autowired + private SubscribeService subscribeService; + + @PreAuthorize("hasPermission(#subscriptionId, 'subscription', 'READ')") + public DSpaceObjectRest getDSpaceObject(@Nullable HttpServletRequest request, Integer subscriptionId, + @Nullable Pageable optionalPageable, Projection projection) { + try { + Subscription subscription = subscribeService.findById(obtainContext(), subscriptionId); + if (Objects.isNull(subscription)) { + throw new ResourceNotFoundException("No such subscription: " + subscriptionId); + } + return converter.toRest(subscription.getDSpaceObject(), projection); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + +} \ 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 new file mode 100644 index 0000000000..dcf612e52d --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionEPersonLinkRepository.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.repository; + +import java.sql.SQLException; +import java.util.Objects; +import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; + +import org.dspace.app.rest.model.EPersonRest; +import org.dspace.app.rest.model.SubscriptionRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.eperson.Subscription; +import org.dspace.eperson.service.SubscribeService; +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 "eperson" of subscription + */ +@Component(SubscriptionRest.CATEGORY + "." + SubscriptionRest.NAME + "." + SubscriptionRest.EPERSON) +public class SubscriptionEPersonLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { + + @Autowired + private SubscribeService subscribeService; + + @PreAuthorize("hasPermission(#subscriptionId, 'subscription', 'READ')") + public EPersonRest getEPerson(@Nullable HttpServletRequest request, Integer subscriptionId, + @Nullable Pageable optionalPageable, Projection projection) { + try { + Subscription subscription = subscribeService.findById(obtainContext(), subscriptionId); + if (Objects.isNull(subscription)) { + throw new ResourceNotFoundException("No such subscription: " + subscriptionId); + } + return converter.toRest(subscription.getEPerson(), projection); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + +} \ 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 new file mode 100644 index 0000000000..ce1bcff11f --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java @@ -0,0 +1,284 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the 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 org.dspace.app.rest.model.SubscriptionRest.CATEGORY; +import static org.dspace.app.rest.model.SubscriptionRest.NAME; +import static org.dspace.core.Constants.COLLECTION; +import static org.dspace.core.Constants.COMMUNITY; +import static org.dspace.core.Constants.READ; + +import java.io.IOException; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Arrays; +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 org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.dspace.app.rest.DiscoverableEndpointsService; +import org.dspace.app.rest.Parameter; +import org.dspace.app.rest.SearchRestMethod; +import org.dspace.app.rest.converter.ConverterService; +import org.dspace.app.rest.exception.DSpaceBadRequestException; +import org.dspace.app.rest.exception.UnprocessableEntityException; +import org.dspace.app.rest.model.SubscriptionParameterRest; +import org.dspace.app.rest.model.SubscriptionRest; +import org.dspace.app.rest.utils.DSpaceObjectUtils; +import org.dspace.authorize.AuthorizeException; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.content.DSpaceObject; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.FrequencyType; +import org.dspace.eperson.Subscription; +import org.dspace.eperson.SubscriptionParameter; +import org.dspace.eperson.service.EPersonService; +import org.dspace.eperson.service.SubscribeService; +import org.dspace.subscriptions.SubscriptionEmailNotificationService; +import org.springframework.beans.factory.InitializingBean; +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.hateoas.Link; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + +/** + * This is the repository responsible to manage SubscriptionRest object + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.com) + */ +@Component(SubscriptionRest.CATEGORY + "." + SubscriptionRest.NAME) +public class SubscriptionRestRepository extends DSpaceRestRepository + implements InitializingBean { + + @Autowired + private ConverterService converter; + @Autowired + private EPersonService ePersonService; + @Autowired + private AuthorizeService authorizeService; + @Autowired + private SubscribeService subscribeService; + @Autowired + private DSpaceObjectUtils dspaceObjectUtil; + @Autowired + private DiscoverableEndpointsService discoverableEndpointsService; + @Autowired + private SubscriptionEmailNotificationService subscriptionEmailNotificationService; + + @Override + @PreAuthorize("hasPermission(#id, 'subscription', 'READ')") + public SubscriptionRest findOne(Context context, Integer id) { + Subscription subscription = null; + try { + subscription = subscribeService.findById(context, id); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + return Objects.isNull(subscription) ? null : converter.toRest(subscription, utils.obtainProjection()); + } + + @Override + @PreAuthorize("hasAuthority('ADMIN')") + public Page findAll(Context context, Pageable pageable) { + try { + List subscriptionList = subscribeService.findAll(context, null, + Math.toIntExact(pageable.getPageSize()), + Math.toIntExact(pageable.getOffset())); + Long total = subscribeService.countAll(context); + return converter.toRestPage(subscriptionList, pageable, total, utils.obtainProjection()); + } catch (Exception e) { + throw new RuntimeException(e.getMessage(), e); + } + } + + @SearchRestMethod(name = "findByEPerson") + @PreAuthorize("hasPermission(#epersonId, 'EPERSON', 'READ')") + public Page findSubscriptionsByEPerson(@Parameter(value = "uuid", required = true) UUID epersonId, + Pageable pageable) throws Exception { + Long total = null; + List subscriptions = null; + try { + Context context = obtainContext(); + EPerson ePerson = ePersonService.find(context, epersonId); + subscriptions = subscribeService.findSubscriptionsByEPerson(context, ePerson, + Math.toIntExact(pageable.getPageSize()), + Math.toIntExact(pageable.getOffset())); + total = subscribeService.countSubscriptionsByEPerson(context, ePerson); + } catch (SQLException e) { + throw new SQLException(e.getMessage(), e); + } + return converter.toRestPage(subscriptions, pageable, total, utils.obtainProjection()); + } + + @SearchRestMethod(name = "findByEPersonAndDso") + @PreAuthorize("hasPermission(#epersonId, 'EPERSON', 'READ')") + public Page findByEPersonAndDso(@Parameter(value = "eperson_id", required = true) UUID epersonId, + @Parameter(value = "resource",required = true) UUID dsoId, + Pageable pageable) throws Exception { + Long total = null; + List subscriptions = null; + try { + Context context = obtainContext(); + DSpaceObject dSpaceObject = dspaceObjectUtil.findDSpaceObject(context, dsoId); + EPerson ePerson = ePersonService.find(context, epersonId); + subscriptions = subscribeService.findSubscriptionsByEPersonAndDso(context, ePerson, dSpaceObject, + Math.toIntExact(pageable.getPageSize()), + Math.toIntExact(pageable.getOffset())); + total = subscribeService.countByEPersonAndDSO(context, ePerson, dSpaceObject); + } catch (SQLException e) { + throw new SQLException(e.getMessage(), e); + } + return converter.toRestPage(subscriptions, pageable, total, utils.obtainProjection()); + } + + @Override + @PreAuthorize("isAuthenticated()") + protected SubscriptionRest createAndReturn(Context context) throws SQLException, AuthorizeException { + HttpServletRequest req = getRequestService().getCurrentRequest().getHttpServletRequest(); + String epersonId = req.getParameter("eperson_id"); + String dsoId = req.getParameter("resource"); + + if (StringUtils.isBlank(dsoId) || StringUtils.isBlank(epersonId)) { + throw new UnprocessableEntityException("Both eperson than DSpaceObject uuids must be provieded!"); + } + + try { + DSpaceObject dSpaceObject = dspaceObjectUtil.findDSpaceObject(context, UUID.fromString(dsoId)); + EPerson ePerson = ePersonService.findByIdOrLegacyId(context, epersonId); + if (Objects.isNull(ePerson) || Objects.isNull(dSpaceObject)) { + throw new DSpaceBadRequestException("Id of person or dspace object must represents reals ids"); + } + + // user must have read permissions to dSpaceObject object + if (!authorizeService.authorizeActionBoolean(context, ePerson, dSpaceObject, READ, true)) { + throw new AuthorizeException("The user has not READ rights on this DSO"); + } + + // if user is Admin don't make this control, + // otherwise make this control because normal user can only subscribe with their own ID of user. + if (!authorizeService.isAdmin(context)) { + if (!ePerson.equals(context.getCurrentUser())) { + throw new AuthorizeException("Only administrator can subscribe for other persons"); + } + } + + if (dSpaceObject.getType() == COMMUNITY || dSpaceObject.getType() == COLLECTION) { + Subscription subscription = null; + ServletInputStream input = req.getInputStream(); + SubscriptionRest subscriptionRest = new ObjectMapper().readValue(input, SubscriptionRest.class); + List subscriptionParameterList = subscriptionRest + .getSubscriptionParameterList(); + if (CollectionUtils.isNotEmpty(subscriptionParameterList)) { + List subscriptionParameters = new ArrayList<>(); + validateParameters(subscriptionRest, subscriptionParameterList, subscriptionParameters); + subscription = subscribeService.subscribe(context, ePerson, dSpaceObject, subscriptionParameters, + subscriptionRest.getSubscriptionType()); + } + context.commit(); + return converter.toRest(subscription, utils.obtainProjection()); + } else { + throw new DSpaceBadRequestException( + "Currently subscription is supported only for Community and Collection"); + } + } catch (SQLException sqlException) { + throw new SQLException(sqlException.getMessage(), sqlException); + } catch (IOException ioException) { + throw new UnprocessableEntityException("error parsing the body"); + } + } + + private void validateParameters(SubscriptionRest subscriptionRest, + List subscriptionParameterList, + List subscriptionParameters) { + for (SubscriptionParameterRest subscriptionParameterRest : subscriptionParameterList) { + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + var name = subscriptionParameterRest.getName(); + var value = subscriptionParameterRest.getValue(); + if (!StringUtils.equals("frequency", name) || !FrequencyType.isSupportedFrequencyType(value)) { + throw new UnprocessableEntityException("Provided SubscriptionParameter name:" + name + + " or value: " + value + " is not supported!"); + } + subscriptionParameter.setName(name); + subscriptionParameter.setValue(value); + subscriptionParameters.add(subscriptionParameter); + } + + var type = subscriptionRest.getSubscriptionType(); + if (!subscriptionEmailNotificationService.getSupportedSubscriptionTypes().contains(type)) { + throw new UnprocessableEntityException("Provided subscriptionType:" + type + " is not supported!" + + " Must be one of: " + subscriptionEmailNotificationService.getSupportedSubscriptionTypes()); + } + } + + @Override + @PreAuthorize("hasPermission(#id, 'subscription', 'WRITE')") + protected SubscriptionRest put(Context context, HttpServletRequest request, String apiCategory, String model, + Integer id, JsonNode jsonNode) throws SQLException { + + SubscriptionRest subscriptionRest; + try { + subscriptionRest = new ObjectMapper().readValue(jsonNode.toString(), SubscriptionRest.class); + } catch (IOException e) { + throw new UnprocessableEntityException("Error parsing subscription json: " + e.getMessage(), e); + } + + Subscription subscription = subscribeService.findById(context, id); + if (Objects.isNull(subscription)) { + throw new ResourceNotFoundException(apiCategory + "." + model + " with id: " + id + " not found"); + } + + if (id.equals(subscription.getID())) { + List subscriptionParameters = new ArrayList<>(); + List subscriptionParameterList = subscriptionRest.getSubscriptionParameterList(); + validateParameters(subscriptionRest, subscriptionParameterList, subscriptionParameters); + subscription = subscribeService.updateSubscription(context, id, subscriptionRest.getSubscriptionType(), + subscriptionParameters); + context.commit(); + return converter.toRest(subscription, utils.obtainProjection()); + } else { + throw new IllegalArgumentException("The id in the Json and the id in the url do not match: " + id + ", " + + subscription.getID()); + } + } + + @Override + @PreAuthorize("hasPermission(#id, 'subscription', 'DELETE')") + protected void delete(Context context, Integer id) { + try { + Subscription subscription = subscribeService.findById(context, id); + if (Objects.isNull(subscription)) { + throw new ResourceNotFoundException(CATEGORY + "." + NAME + " with id: " + id + " not found"); + } + subscribeService.deleteSubscription(context, subscription); + } catch (SQLException e) { + throw new RuntimeException("Unable to delete Subscription with id = " + id, e); + } + } + + @Override + public Class getDomainClass() { + return SubscriptionRest.class; + } + + @Override + public void afterPropertiesSet() throws Exception { + discoverableEndpointsService.register(this, Arrays.asList(Link.of("/api/" + SubscriptionRest.CATEGORY + + "/" + SubscriptionRest.NAME_PLURAL + "/search", SubscriptionRest.NAME_PLURAL + "-search"))); + } + +} \ No newline at end of file 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 new file mode 100644 index 0000000000..fb2f589dbc --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SupervisionOrderRestRepository.java @@ -0,0 +1,241 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the 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 org.dspace.authorize.ResourcePolicy.TYPE_SUBMISSION; + +import java.sql.SQLException; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.UUID; +import java.util.stream.Collectors; +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.Logger; +import org.dspace.app.exception.ResourceAlreadyExistsException; +import org.dspace.app.rest.Parameter; +import org.dspace.app.rest.SearchRestMethod; +import org.dspace.app.rest.converter.ConverterService; +import org.dspace.app.rest.exception.MissingParameterException; +import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException; +import org.dspace.app.rest.exception.UnprocessableEntityException; +import org.dspace.app.rest.model.SupervisionOrderRest; +import org.dspace.authorize.AuthorizeException; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.content.Bitstream; +import org.dspace.content.Bundle; +import org.dspace.content.Item; +import org.dspace.content.service.ItemService; +import org.dspace.core.Constants; +import org.dspace.core.Context; +import org.dspace.eperson.Group; +import org.dspace.eperson.service.GroupService; +import org.dspace.supervision.SupervisionOrder; +import org.dspace.supervision.enumeration.SupervisionOrderType; +import org.dspace.supervision.service.SupervisionOrderService; +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 SupervisionOrderRest object + * + * @author Mohamed Eskander (mohamed.eskander at 4science dot it) + */ +@Component(SupervisionOrderRest.CATEGORY + "." + SupervisionOrderRest.NAME) +public class SupervisionOrderRestRepository extends DSpaceRestRepository { + + private static final Logger log = + org.apache.logging.log4j.LogManager.getLogger(SupervisionOrderRestRepository.class); + + @Autowired + private SupervisionOrderService supervisionOrderService; + + @Autowired + private ConverterService converterService; + + @Autowired + private ItemService itemService; + + @Autowired + private GroupService groupService; + + @Autowired + private AuthorizeService authorizeService; + + @PreAuthorize("hasAuthority('ADMIN')") + @Override + public SupervisionOrderRest findOne(Context context, Integer id) { + try { + SupervisionOrder supervisionOrder = supervisionOrderService.find(context, id); + if (Objects.isNull(supervisionOrder)) { + throw new ResourceNotFoundException("Couldn't find supervision order for id: " + id); + } + return converterService.toRest(supervisionOrder, utils.obtainProjection()); + } catch (SQLException e) { + log.error("Something went wrong with getting supervision order with id:" + id, e); + throw new RuntimeException(e.getMessage(), e); + } + } + + @PreAuthorize("hasAuthority('ADMIN')") + @Override + public Page findAll(Context context, Pageable pageable) { + try { + List supervisionOrders = supervisionOrderService.findAll(context); + return converterService.toRestPage(supervisionOrders, pageable, utils.obtainProjection()); + } catch (SQLException e) { + log.error("Something went wrong with getting supervision orders", e); + throw new RuntimeException(e.getMessage(), e); + } + } + + @PreAuthorize("hasAuthority('ADMIN')") + @Override + public SupervisionOrderRest createAndReturn(Context context) throws AuthorizeException, SQLException { + HttpServletRequest req = getRequestService().getCurrentRequest().getHttpServletRequest(); + SupervisionOrder supervisionOrder; + String itemId = req.getParameter("uuid"); + String groupId = req.getParameter("group"); + String type = req.getParameter("type"); + + validateParameters(itemId, groupId, type); + + Item item = itemService.find(context, UUID.fromString(itemId)); + if (item == null) { + throw new UnprocessableEntityException("Item with uuid: " + itemId + " not found"); + } + + if (item.isArchived() || item.isWithdrawn()) { + throw new UnprocessableEntityException("An archived Item with uuid: " + itemId + " can't be supervised"); + } + + Group group = groupService.find(context, UUID.fromString(groupId)); + if (group == null) { + throw new UnprocessableEntityException("Group with uuid: " + groupId + " not found"); + } + + supervisionOrder = supervisionOrderService.findByItemAndGroup(context, item, group); + if (Objects.nonNull(supervisionOrder)) { + throw new ResourceAlreadyExistsException( + "A supervision order already exists with itemId <" + itemId + "> and groupId <" + groupId + ">"); + } + supervisionOrder = supervisionOrderService.create(context, item, group); + addGroupPoliciesToItem(context, item, group, type); + return converterService.toRest(supervisionOrder, utils.obtainProjection()); + } + + @PreAuthorize("hasAuthority('ADMIN')") + @Override + protected void delete(Context context, Integer id) + throws AuthorizeException, RepositoryMethodNotImplementedException { + + try { + SupervisionOrder supervisionOrder = supervisionOrderService.find(context, id); + if (Objects.isNull(supervisionOrder)) { + throw new ResourceNotFoundException( + SupervisionOrderRest.CATEGORY + "." + SupervisionOrderRest.NAME + + " with id: " + id + " not found" + ); + } + removeGroupPoliciesToItem(context, supervisionOrder.getItem(), supervisionOrder.getGroup()); + supervisionOrderService.delete(context, supervisionOrder); + + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + + @PreAuthorize("hasAuthority('ADMIN')") + @SearchRestMethod(name = "byItem") + public Page findByItem(@Parameter(value = "uuid", required = true) String itemId, + Pageable pageable) { + try { + Context context = obtainContext(); + Item item = itemService.find(context, UUID.fromString(itemId)); + if (Objects.isNull(item)) { + throw new ResourceNotFoundException("no item is found for the uuid < " + itemId + " >"); + } + return converterService.toRestPage(supervisionOrderService.findByItem(context, item), + pageable, utils.obtainProjection()); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + + @Override + public Class getDomainClass() { + return SupervisionOrderRest.class; + } + + private void validateParameters(String itemId, String groupId, String type) { + if (Objects.isNull(itemId)) { + throw new MissingParameterException("Missing item (uuid) parameter"); + } + + if (Objects.isNull(groupId)) { + throw new MissingParameterException("Missing group (uuid) parameter"); + } + + if (Objects.isNull(type)) { + throw new MissingParameterException("Missing type parameter"); + } else if (SupervisionOrderType.invalid(type)) { + throw new IllegalArgumentException("wrong type value, Type must be (" + + Arrays.stream(SupervisionOrderType.values()) + .map(Enum::name) + .collect(Collectors.joining(" or ")) + ")"); + } + + } + + private void addGroupPoliciesToItem(Context context, Item item, Group group, String type) + throws SQLException, AuthorizeException { + + if (StringUtils.isNotEmpty(type)) { + if (type.equals("EDITOR")) { + addGroupPolicyToItem(context, item, Constants.READ, group, TYPE_SUBMISSION); + addGroupPolicyToItem(context, item, Constants.WRITE, group, TYPE_SUBMISSION); + addGroupPolicyToItem(context, item, Constants.ADD, group, TYPE_SUBMISSION); + } else if (type.equals("OBSERVER")) { + addGroupPolicyToItem(context, item, Constants.READ, group, TYPE_SUBMISSION); + } + } + } + + private void addGroupPolicyToItem(Context context, Item item, int action, Group group, String policyType) + throws AuthorizeException, SQLException { + authorizeService.addPolicy(context, item, action, group, policyType); + List bundles = item.getBundles(); + for (Bundle bundle : bundles) { + authorizeService.addPolicy(context, bundle, action, group, policyType); + List bits = bundle.getBitstreams(); + for (Bitstream bitstream : bits) { + authorizeService.addPolicy(context, bitstream, action, group, policyType); + } + } + } + + private void removeGroupPoliciesToItem(Context context, Item item, Group group) + throws AuthorizeException, SQLException { + authorizeService.removeGroupPolicies(context, item, group); + List bundles = item.getBundles(); + for (Bundle bundle : bundles) { + authorizeService.removeGroupPolicies(context, bundle, group); + List bits = bundle.getBitstreams(); + for (Bitstream bitstream : bits) { + authorizeService.removeGroupPolicies(context, bitstream, group); + } + } + } + +} 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 new file mode 100644 index 0000000000..73544145b2 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SystemWideAlertRestRepository.java @@ -0,0 +1,208 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the 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 org.apache.commons.lang3.StringUtils.isBlank; + +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 org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.dspace.alerts.AllowSessionsEnum; +import org.dspace.alerts.SystemWideAlert; +import org.dspace.alerts.service.SystemWideAlertService; +import org.dspace.app.rest.SearchRestMethod; +import org.dspace.app.rest.exception.DSpaceBadRequestException; +import org.dspace.app.rest.exception.UnprocessableEntityException; +import org.dspace.app.rest.model.SystemWideAlertRest; +import org.dspace.authorize.AuthorizeException; +import org.dspace.authorize.service.AuthorizeService; +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; + +/** + * The repository for the SystemWideAlert workload + */ +@Component(SystemWideAlertRest.CATEGORY + "." + SystemWideAlertRest.NAME) +public class SystemWideAlertRestRepository extends DSpaceRestRepository { + + private static final Logger log = LogManager.getLogger(); + + @Autowired + private SystemWideAlertService systemWideAlertService; + + @Autowired + private AuthorizeService authorizeService; + + @Override + @PreAuthorize("hasAuthority('ADMIN')") + protected SystemWideAlertRest createAndReturn(Context context) throws SQLException, AuthorizeException { + SystemWideAlert systemWideAlert = createSystemWideAlert(context); + return converter.toRest(systemWideAlert, utils.obtainProjection()); + } + + + /** + * This method will retrieve the system-wide alert for the provided ID + * However, only admins will be able to retrieve the inactive alerts. Non-admin users will only be able to retrieve + * active alerts. This is necessary also to be able to return the results through the search endpoint, since the + * PreAuthorization will be checked when converting the results to a list. Therefore, closing this endpoint fully + * off will + * prevent results from being displayed in the search endpoint + * + * @param context the dspace context + * @param id the rest object id + * @return retrieve the system-wide alert for the provided ID + */ + @Override + @PreAuthorize("permitAll()") + public SystemWideAlertRest findOne(Context context, Integer id) { + try { + SystemWideAlert systemWideAlert = systemWideAlertService.find(context, id); + if (systemWideAlert == null) { + throw new ResourceNotFoundException( + "systemWideAlert with id " + systemWideAlert.getID() + " was not found"); + } + if (!systemWideAlert.isActive() && !authorizeService.isAdmin(context)) { + throw new AuthorizeException("Non admin users are not allowed to retrieve inactive alerts"); + } + return converter.toRest(systemWideAlert, utils.obtainProjection()); + } catch (Exception e) { + log.error(e.getMessage(), e); + } + return null; + } + + @Override + @PreAuthorize("hasAuthority('ADMIN')") + public Page findAll(Context context, Pageable pageable) { + try { + List systemWideAlerts = systemWideAlertService.findAll(context, pageable.getPageSize(), + Math.toIntExact( + pageable.getOffset())); + return converter.toRestPage(systemWideAlerts, pageable, utils.obtainProjection()); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + + @Override + @PreAuthorize("hasAuthority('ADMIN')") + protected SystemWideAlertRest put(Context context, HttpServletRequest request, String apiCategory, String model, + Integer id, JsonNode jsonNode) throws SQLException, AuthorizeException { + + SystemWideAlertRest systemWideAlertRest; + try { + systemWideAlertRest = new ObjectMapper().readValue(jsonNode.toString(), SystemWideAlertRest.class); + } catch (JsonProcessingException e) { + throw new UnprocessableEntityException("Cannot parse JSON in request body", e); + } + + if (systemWideAlertRest == null || isBlank(systemWideAlertRest.getMessage())) { + throw new UnprocessableEntityException("system alert message cannot be blank"); + } + + SystemWideAlert systemWideAlert = systemWideAlertService.find(context, id); + if (systemWideAlert == null) { + throw new ResourceNotFoundException("system wide alert with id: " + id + " not found"); + } + + systemWideAlert.setMessage(systemWideAlertRest.getMessage()); + systemWideAlert.setAllowSessions(AllowSessionsEnum.fromString(systemWideAlertRest.getAllowSessions())); + systemWideAlert.setCountdownTo(systemWideAlertRest.getCountdownTo()); + systemWideAlert.setActive(systemWideAlertRest.isActive()); + + systemWideAlertService.update(context, systemWideAlert); + context.commit(); + + return converter.toRest(systemWideAlert, utils.obtainProjection()); + } + + + /** + * Helper method to create a system-wide alert and deny creation when one already exists + * + * @param context The database context + * @return the created system-wide alert + * @throws SQLException + */ + private SystemWideAlert createSystemWideAlert(Context context) + throws SQLException, AuthorizeException { + List all = systemWideAlertService.findAll(context); + if (!all.isEmpty()) { + throw new DSpaceBadRequestException("A system wide alert already exists, no new value can be created. " + + "Try updating the existing one."); + } + + + HttpServletRequest req = getRequestService().getCurrentRequest().getHttpServletRequest(); + ObjectMapper mapper = new ObjectMapper(); + SystemWideAlertRest systemWideAlertRest; + try { + ServletInputStream input = req.getInputStream(); + systemWideAlertRest = mapper.readValue(input, SystemWideAlertRest.class); + } catch (IOException e1) { + throw new UnprocessableEntityException("Error parsing request body.", e1); + } + + SystemWideAlert systemWideAlert; + + try { + systemWideAlert = systemWideAlertService.create(context, systemWideAlertRest.getMessage(), + AllowSessionsEnum.fromString( + systemWideAlertRest.getAllowSessions()), + systemWideAlertRest.getCountdownTo(), + systemWideAlertRest.isActive()); + systemWideAlertService.update(context, systemWideAlert); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + return systemWideAlert; + } + + /** + * Search method to retrieve all active system-wide alerts + * + * @param pageable The page object + * @return all active system-wide alerts for the provided page + */ + @PreAuthorize("permitAll()") + @SearchRestMethod(name = "active") + public Page findAllActive(Pageable pageable) { + Context context = obtainContext(); + try { + List systemWideAlerts = + systemWideAlertService.findAllActive(context, + pageable.getPageSize(), + Math.toIntExact( + pageable.getOffset())); + return converter.toRestPage(systemWideAlerts, pageable, utils.obtainProjection()); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + + } + + + @Override + public Class getDomainClass() { + return SystemWideAlertRest.class; + } +} 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 3f16217d76..585fc1de9f 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 @@ -111,7 +111,7 @@ public class VersionRestRepository extends DSpaceRestRepository getChildren(@Nullable HttpServletRequest request, String name, @Nullable Pageable optionalPageable, Projection projection) { 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 379928d9cc..d7a1ff85c3 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 @@ -40,7 +40,7 @@ public class VocabularyEntryDetailsParentLinkRepository extends AbstractDSpaceRe @Autowired private AuthorityUtils authorityUtils; - @PreAuthorize("hasAuthority('AUTHENTICATED')") + @PreAuthorize("permitAll()") public VocabularyEntryDetailsRest getParent(@Nullable HttpServletRequest request, String name, @Nullable Pageable optionalPageable, Projection projection) { Context context = obtainContext(); 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 bc7c60d62b..3e4d600b12 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 @@ -66,7 +66,7 @@ public class VocabularyEntryDetailsRestRepository extends DSpaceRestRepository findAllTop(@Parameter(value = "vocabulary", required = true) String vocabularyId, Pageable pageable) { Context context = obtainContext(); 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 9d75ef87c3..a24bef42c2 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 @@ -51,7 +51,7 @@ public class VocabularyEntryLinkRepository extends AbstractDSpaceRestRepository @Autowired private AuthorityUtils authorityUtils; - @PreAuthorize("hasAuthority('AUTHENTICATED')") + @PreAuthorize("permitAll()") public Page filter(@Nullable HttpServletRequest request, String name, @Nullable Pageable optionalPageable, Projection projection) { Context context = obtainContext(); 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 dcdf71186b..fcc37d1316 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 @@ -53,7 +53,7 @@ public class VocabularyRestRepository extends DSpaceRestRepository getSupervisionOrders(@Nullable HttpServletRequest request, + Integer id, + @Nullable Pageable optionalPageable, + Projection projection) { + try { + Context context = obtainContext(); + WorkspaceItem workspaceItem = workspaceItemService.find(context, id); + if (workspaceItem == null) { + throw new ResourceNotFoundException("No such workspace item: " + id); + } + return converter.toRestPage( + supervisionOrderService.findByItem(context, workspaceItem.getItem()), + optionalPageable, projection); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } +} 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 6b106d1b77..dd464edfcd 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 @@ -53,7 +53,7 @@ public interface RestAuthenticationService { * Checks the current request for a valid authentication token. If found, extracts that token and obtains the * currently logged in EPerson. * @param request current request - * @param request current response + * @param response current response * @param context current DSpace Context * @return EPerson of the logged in user (if auth token found), or null if no auth token is found */ 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 new file mode 100644 index 0000000000..c93d966e73 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/SubscriptionRestPermissionEvaluatorPlugin.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.security; + +import static org.dspace.app.rest.model.SubscriptionRest.NAME; +import static org.dspace.app.rest.security.DSpaceRestPermission.DELETE; +import static org.dspace.app.rest.security.DSpaceRestPermission.READ; +import static org.dspace.app.rest.security.DSpaceRestPermission.WRITE; + +import java.io.Serializable; +import java.sql.SQLException; +import java.util.Objects; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.Subscription; +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); + + @Autowired + private RequestService requestService; + @Autowired + private SubscribeService subscribeService; + @Autowired + private AuthorizeService authorizeService; + + @Override + public boolean hasDSpacePermission(Authentication authentication, Serializable targetId, String targetType, + DSpaceRestPermission permission) { + + DSpaceRestPermission restPermission = DSpaceRestPermission.convert(permission); + + if (!READ.equals(restPermission) && !WRITE.equals(restPermission) && !DELETE.equals(restPermission) + || !StringUtils.equalsIgnoreCase(targetType, NAME)) { + return false; + } + + Request request = requestService.getCurrentRequest(); + Context context = ContextUtil.obtainContext(request.getHttpServletRequest()); + + try { + EPerson currentUser = context.getCurrentUser(); + // anonymous user + if (Objects.isNull(currentUser)) { + return false; + } + // Admin user + if (authorizeService.isAdmin(context, currentUser)) { + return true; + } + + 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); + } + return false; + } + +} 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 e225df067d..626290fdc3 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 @@ -20,6 +20,7 @@ import org.dspace.eperson.EPerson; import org.dspace.eperson.service.EPersonService; import org.dspace.services.RequestService; import org.dspace.services.model.Request; +import org.dspace.supervision.service.SupervisionOrderService; import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; import org.dspace.xmlworkflow.storedcomponents.service.ClaimedTaskService; import org.dspace.xmlworkflow.storedcomponents.service.PoolTaskService; @@ -56,6 +57,9 @@ public class WorkflowRestPermissionEvaluatorPlugin extends RestObjectPermissionE @Autowired private EPersonService ePersonService; + @Autowired + private SupervisionOrderService supervisionOrderService; + @Override public boolean hasDSpacePermission(Authentication authentication, Serializable targetId, String targetType, DSpaceRestPermission permission) { @@ -89,6 +93,11 @@ public class WorkflowRestPermissionEvaluatorPlugin extends RestObjectPermissionE if (claimedTaskService.findByWorkflowIdAndEPerson(context, workflowItem, ePerson) != null) { return true; } + + if (supervisionOrderService.isSupervisor(context, ePerson, workflowItem.getItem())) { + return true; + } + } catch (SQLException | AuthorizeException | IOException e) { log.error(e.getMessage(), e); } 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 9a8de3675c..c0efbd60f2 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 @@ -13,12 +13,14 @@ import java.sql.SQLException; import org.apache.commons.lang3.StringUtils; import org.dspace.app.rest.model.WorkspaceItemRest; import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.WorkspaceItem; import org.dspace.content.service.WorkspaceItemService; import org.dspace.core.Context; import org.dspace.eperson.EPerson; 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; @@ -41,6 +43,12 @@ public class WorkspaceItemRestPermissionEvaluatorPlugin extends RestObjectPermis @Autowired WorkspaceItemService wis; + @Autowired + private SupervisionOrderService supervisionOrderService; + + @Autowired + private AuthorizeService authorizeService; + @Override public boolean hasDSpacePermission(Authentication authentication, Serializable targetId, String targetType, DSpaceRestPermission permission) { @@ -82,6 +90,15 @@ public class WorkspaceItemRestPermissionEvaluatorPlugin extends RestObjectPermis return true; } } + + if (witem.getItem() != null) { + if (supervisionOrderService.isSupervisor(context, ePerson, witem.getItem())) { + return authorizeService.authorizeActionBoolean(context, ePerson, witem.getItem(), + restPermission.getDspaceApiActionId(), + true); + } + } + } catch (SQLException e) { log.error(e.getMessage(), e); } 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 8eb03acde0..99af309cdb 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 @@ -38,6 +38,7 @@ public interface DataProcessingStep extends RestProcessingStep { public static final String CCLICENSE_STEP_OPERATION_ENTRY = "cclicense/uri"; 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 UPLOAD_STEP_METADATA_PATH = "metadata"; 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 new file mode 100644 index 0000000000..e63d38ab2e --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/ShowIdentifiersStep.java @@ -0,0 +1,170 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the 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 java.sql.SQLException; +import java.util.Arrays; +import java.util.List; +import javax.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.model.patch.Operation; +import org.dspace.app.rest.model.step.DataIdentifiers; +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.factory.HandleServiceFactory; +import org.dspace.handle.service.HandleService; +import org.dspace.identifier.DOI; +import org.dspace.identifier.DOIIdentifierProvider; +import org.dspace.identifier.Handle; +import org.dspace.identifier.IdentifierException; +import org.dspace.identifier.factory.IdentifierServiceFactory; +import org.dspace.identifier.service.IdentifierService; +import org.dspace.services.factory.DSpaceServicesFactory; +import org.dspace.services.model.Request; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Submission processing step to return identifier data for use in the 'identifiers' submission section component + * in dspace-angular. For effective use, the "identifiers.submission.register" configuration property + * in identifiers.cfg should be enabled so that the WorkspaceItemService will register identifiers for the new item + * at the time of creation, and the DOI consumer will allow workspace and workflow items to have their DOIs minted + * or deleted as per item filter results. + * + * This method can be extended to allow (if authorised) an operation to be sent which will + * override an item filter and force reservation of an identifier. + * + * @author Kim Shepherd + */ +public class ShowIdentifiersStep extends AbstractProcessingStep { + + private static final Logger log = LogManager.getLogger(ShowIdentifiersStep.class); + + @Autowired(required = true) + protected HandleService handleService; + @Autowired(required = true) + protected ContentServiceFactory contentServiceFactory; + + /** + * Override DataProcessing.getData, return data identifiers from getIdentifierData() + * + * @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 DataIdentifiers getData(SubmissionService submissionService, InProgressSubmission obj, + SubmissionStepConfig config) throws Exception { + // If configured, WorkspaceItemService item creation will also call IdentifierService.register() + // for the new item, and the DOI consumer (if also configured) will mint or delete DOI entries as appropriate + // while the item is saved in the submission / workflow process + + // This step simply looks for existing identifier data and returns it in section data for rendering + return getIdentifierData(obj); + } + + /** + * Get data about existing identifiers for this in-progress submission item - this method doesn't require + * submissionService or step config, so can be more easily called from doPatchProcessing as well + * + * @param obj The workspace or workflow item + * @return A simple DataIdentifiers bean containing doi, handle and list of other identifiers + */ + private DataIdentifiers getIdentifierData(InProgressSubmission obj) { + Context context = getContext(); + DataIdentifiers result = new DataIdentifiers(); + // Load identifier service + IdentifierService identifierService = + IdentifierServiceFactory.getInstance().getIdentifierService(); + // Attempt to look up handle and DOI identifiers for this item + String[] defaultTypes = {"handle", "doi"}; + List displayTypes = Arrays.asList(configurationService.getArrayProperty( + "identifiers.submission.display", + defaultTypes)); + result.setDisplayTypes(displayTypes); + String handle = identifierService.lookup(context, obj.getItem(), Handle.class); + DOI doi = null; + String doiString = null; + try { + doi = IdentifierServiceFactory.getInstance().getDOIService().findDOIByDSpaceObject(context, obj.getItem()); + if (doi != null && !DOIIdentifierProvider.MINTED.equals(doi.getStatus()) + && !DOIIdentifierProvider.DELETED.equals(doi.getStatus())) { + doiString = doi.getDoi(); + } + } catch (SQLException e) { + log.error(e.getMessage()); + } + + // Other identifiers can be looked up / resolved through identifier service or + // its own specific service here + + // If we got a DOI, format it to its external form + if (StringUtils.isNotEmpty(doiString)) { + try { + doiString = IdentifierServiceFactory.getInstance().getDOIService().DOIToExternalForm(doiString); + } catch (IdentifierException e) { + log.error("Error formatting DOI: " + doi); + } + } + // If we got a handle, format it to its canonical form + if (StringUtils.isNotEmpty(handle)) { + handle = HandleServiceFactory.getInstance().getHandleService().getCanonicalForm(handle); + } + + // Populate bean with data and return, if the identifier type is configured for exposure + result.addIdentifier("doi", doiString, + doi != null ? DOIIdentifierProvider.statusText[doi.getStatus()] : null); + result.addIdentifier("handle", handle, null); + return result; + } + + /** + * 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/utils/HttpHeadersInitializer.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/HttpHeadersInitializer.java index ffe2acc2e0..a69da4c5e8 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 @@ -163,6 +163,8 @@ public class HttpHeadersInitializer { } + httpHeaders.put(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, + Collections.singletonList(HttpHeaders.ACCEPT_RANGES)); httpHeaders.put(CONTENT_DISPOSITION, Collections.singletonList(String.format(CONTENT_DISPOSITION_FORMAT, disposition, encodeText(fileName)))); 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 237550326f..ed6e26ed0f 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 @@ -64,6 +64,7 @@ import org.dspace.app.rest.model.PropertyRest; import org.dspace.app.rest.model.ResourcePolicyRest; import org.dspace.app.rest.model.RestAddressableModel; import org.dspace.app.rest.model.RestModel; +import org.dspace.app.rest.model.SupervisionOrderRest; import org.dspace.app.rest.model.VersionHistoryRest; import org.dspace.app.rest.model.VocabularyRest; import org.dspace.app.rest.model.hateoas.EmbeddedPage; @@ -150,20 +151,50 @@ public class Utils { public Page getPage(List fullContents, @Nullable Pageable optionalPageable) { Pageable pageable = getPageable(optionalPageable); int total = fullContents.size(); - List pageContent = null; if (pageable.getOffset() > total) { throw new PaginationException(total); } else { - if (pageable.getOffset() + pageable.getPageSize() > total) { - pageContent = fullContents.subList(Math.toIntExact(pageable.getOffset()), total); - } else { - pageContent = fullContents.subList(Math.toIntExact(pageable.getOffset()), - Math.toIntExact(pageable.getOffset()) + pageable.getPageSize()); - } + List pageContent = getListSlice(fullContents, pageable); return new PageImpl<>(pageContent, pageable, total); } } + /** + * Returns list of objects for the current page. + * @param fullList the complete list of objects + * @param optionalPageable + * @return list of page objects + * @param + */ + public List getPageObjectList(List fullList, @Nullable Pageable optionalPageable) { + Pageable pageable = getPageable(optionalPageable); + int total = fullList.size(); + if (pageable.getOffset() > total) { + throw new PaginationException(total); + } else { + return getListSlice(fullList, pageable); + } + } + + /** + * Returns the list elements required for the page + * @param fullList the complete list of objects + * @param pageable + * @return list of page objects + * @param + */ + private List getListSlice(List fullList, Pageable pageable) { + int total = fullList.size(); + List pageContent = null; + if (pageable.getOffset() + pageable.getPageSize() > total) { + pageContent = fullList.subList(Math.toIntExact(pageable.getOffset()), total); + } else { + pageContent = fullList.subList(Math.toIntExact(pageable.getOffset()), + Math.toIntExact(pageable.getOffset()) + pageable.getPageSize()); + } + return pageContent; + } + /** * Convenience method to get a default pageable instance if needed. * @@ -296,6 +327,9 @@ public class Utils { if (StringUtils.equals(modelPlural, "orcidhistories")) { return OrcidHistoryRest.NAME; } + if (StringUtils.equals(modelPlural, "supervisionorders")) { + return SupervisionOrderRest.NAME; + } return modelPlural.replaceAll("s$", ""); } diff --git a/dspace-server-webapp/src/test/data/dspaceFolder/config/item-submission.xml b/dspace-server-webapp/src/test/data/dspaceFolder/config/item-submission.xml index cc23dbc09e..eca9acf79f 100644 --- a/dspace-server-webapp/src/test/data/dspaceFolder/config/item-submission.xml +++ b/dspace-server-webapp/src/test/data/dspaceFolder/config/item-submission.xml @@ -162,6 +162,13 @@ submission-form + + + submit.progressbar.identifiers + org.dspace.app.rest.submit.step.ShowIdentifiersStep + identifiers + + Sample @@ -194,6 +201,8 @@ + + diff --git a/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/identifier-service.xml b/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/identifier-service.xml new file mode 100644 index 0000000000..447d0a59dd --- /dev/null +++ b/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/identifier-service.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/iiif-processing.xml b/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/iiif-processing.xml new file mode 100644 index 0000000000..462fc23a0d --- /dev/null +++ b/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/iiif-processing.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/opensearch/OpenSearchControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/opensearch/OpenSearchControllerIT.java index 9974d7e725..1ddea619d2 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/opensearch/OpenSearchControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/opensearch/OpenSearchControllerIT.java @@ -184,6 +184,32 @@ public class OpenSearchControllerIT extends AbstractControllerIntegrationTest { ; } + @Test + public void validSortTest() throws Exception { + //When we call the root endpoint + getClient().perform(get("/opensearch/search") + .param("query", "") + .param("sort", "dc.date.issued")) + //The status has to be 200 OK + .andExpect(status().isOk()) + //We expect the content type to be "application/atom+xml;charset=UTF-8" + .andExpect(content().contentType("application/atom+xml;charset=UTF-8")) + .andExpect(xpath("feed/totalResults").string("0")) + ; + } + + @Test + public void invalidSortTest() throws Exception { + //When we call the root endpoint + getClient().perform(get("/opensearch/search") + .param("query", "") + .param("sort", "dc.invalid.field")) + //We get an exception for such a sort field + //The status has to be 400 ERROR + .andExpect(status().is(400)) + ; + } + @Test public void serviceDocumentTest() throws Exception { //When we call the root endpoint @@ -223,4 +249,24 @@ public class OpenSearchControllerIT extends AbstractControllerIntegrationTest { */ } + + @Test + public void emptyDescriptionTest() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection collection1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1") + .build(); + + getClient().perform(get("/opensearch/search") + .param("format", "rss") + .param("scope", collection1.getID().toString()) + .param("query", "*")) + .andExpect(status().isOk()) + .andExpect(xpath("rss/channel/description").string("No Description")); + } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthenticationRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthenticationRestControllerIT.java index 19f0d9d267..69e70dbb08 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthenticationRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthenticationRestControllerIT.java @@ -1394,64 +1394,6 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio .andExpect(status().isNoContent()); } - @Test - public void testShortLivedTokenUsingGet() throws Exception { - String token = getAuthToken(eperson.getEmail(), password); - - // Verify the main session salt doesn't change - String salt = eperson.getSessionSalt(); - - getClient(token).perform( - get("/api/authn/shortlivedtokens") - .with(ip(TRUSTED_IP)) - ) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.token", notNullValue())) - .andExpect(jsonPath("$.type", is("shortlivedtoken"))) - .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/authn/shortlivedtokens"))) - // Verify generating short-lived token doesn't change our CSRF token - // (so, neither the CSRF cookie nor header are sent back) - .andExpect(cookie().doesNotExist("DSPACE-XSRF-COOKIE")) - .andExpect(header().doesNotExist("DSPACE-XSRF-TOKEN")); - - assertEquals(salt, eperson.getSessionSalt()); - - // Logout, invalidating token - getClient(token).perform(post("/api/authn/logout")) - .andExpect(status().isNoContent()); - } - - @Test - public void testShortLivedTokenUsingGetFromUntrustedIpShould403() throws Exception { - String token = getAuthToken(eperson.getEmail(), password); - - getClient(token).perform( - get("/api/authn/shortlivedtokens") - .with(ip(UNTRUSTED_IP)) - ) - .andExpect(status().isForbidden()); - - // Logout, invalidating token - getClient(token).perform(post("/api/authn/logout")) - .andExpect(status().isNoContent()); - } - - @Test - public void testShortLivedTokenUsingGetFromUntrustedIpWithForwardHeaderShould403() throws Exception { - String token = getAuthToken(eperson.getEmail(), password); - - getClient(token).perform( - get("/api/authn/shortlivedtokens") - .with(ip(UNTRUSTED_IP)) - .header("X-Forwarded-For", TRUSTED_IP) // this should not affect the test result - ) - .andExpect(status().isForbidden()); - - // Logout, invalidating token - getClient(token).perform(post("/api/authn/logout")) - .andExpect(status().isNoContent()); - } - @Test public void testShortLivedTokenWithCSRFSentViaParam() throws Exception { String token = getAuthToken(eperson.getEmail(), password); @@ -1476,15 +1418,6 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio .andExpect(status().isUnauthorized()); } - @Test - public void testShortLivedTokenNotAuthenticatedUsingGet() throws Exception { - getClient().perform( - get("/api/authn/shortlivedtokens") - .with(ip(TRUSTED_IP)) - ) - .andExpect(status().isUnauthorized()); - } - @Test public void testShortLivedTokenToDownloadBitstream() throws Exception { Bitstream bitstream = createPrivateBitstream(); @@ -1598,22 +1531,6 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio .andExpect(status().isNoContent()); } - @Test - public void testGenerateShortLivedTokenWithShortLivedTokenUsingGet() throws Exception { - String token = getAuthToken(eperson.getEmail(), password); - String shortLivedToken = getShortLivedToken(token); - - getClient().perform( - get("/api/authn/shortlivedtokens?authentication-token=" + shortLivedToken) - .with(ip(TRUSTED_IP)) - ) - .andExpect(status().isForbidden()); - - // Logout, invalidating token - getClient(token).perform(post("/api/authn/logout")) - .andExpect(status().isNoContent()); - } - @Test public void testStatusOrcidAuthenticatedWithCookie() throws Exception { diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamFormatRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamFormatRestRepositoryIT.java index 1a6cc29ca7..fd12826930 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamFormatRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamFormatRestRepositoryIT.java @@ -56,7 +56,7 @@ public class BitstreamFormatRestRepositoryIT extends AbstractControllerIntegrati @Autowired private BitstreamFormatConverter bitstreamFormatConverter; - private final int DEFAULT_AMOUNT_FORMATS = 81; + private final int DEFAULT_AMOUNT_FORMATS = 85; @Test public void findAllPaginationTest() throws Exception { diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java index f9c1e469fc..d6947d7567 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java @@ -15,6 +15,7 @@ import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -30,6 +31,9 @@ import org.dspace.app.rest.matcher.BitstreamFormatMatcher; import org.dspace.app.rest.matcher.BitstreamMatcher; import org.dspace.app.rest.matcher.BundleMatcher; import org.dspace.app.rest.matcher.HalMatcher; +import org.dspace.app.rest.matcher.MetadataMatcher; +import org.dspace.app.rest.model.patch.Operation; +import org.dspace.app.rest.model.patch.ReplaceOperation; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.app.rest.test.MetadataPatchSuite; import org.dspace.authorize.service.ResourcePolicyService; @@ -45,6 +49,7 @@ import org.dspace.content.Bundle; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.Item; +import org.dspace.content.MetadataSchemaEnum; import org.dspace.content.service.BitstreamFormatService; import org.dspace.content.service.BitstreamService; import org.dspace.content.service.ItemService; @@ -1222,6 +1227,92 @@ public class BitstreamRestRepositoryIT extends AbstractControllerIntegrationTest + parentCommunity.getLogo().getID(), expectedStatus); } + @Test + public void patchReplaceMultipleDescriptionBitstream() throws Exception { + context.turnOffAuthorisationSystem(); + + List bitstreamDescriptions = List.of( + "FIRST", + "SECOND", + "THIRD" + ); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = + CommunityBuilder.createSubCommunity(context, parentCommunity).withName("Sub Community").build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + + Item publicItem1 = ItemBuilder.createItem(context, col1).withTitle("Test").build(); + + String bitstreamContent = "ThisIsSomeDummyText"; + Bitstream bitstream = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + bitstream = BitstreamBuilder. + createBitstream(context, publicItem1, is) + .withName("Bitstream") + .withMimeType("text/plain") + .build(); + } + + this.bitstreamService + .addMetadata( + context, bitstream, + MetadataSchemaEnum.DC.getName(), "description", null, + Item.ANY, bitstreamDescriptions + ); + + context.restoreAuthSystemState(); + String token = getAuthToken(admin.getEmail(), password); + + getClient(token) + .perform(get("/api/core/bitstreams/" + bitstream.getID())) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$.metadata", + Matchers.allOf( + MetadataMatcher.matchMetadata("dc.description", bitstreamDescriptions.get(0), 0), + MetadataMatcher.matchMetadata("dc.description", bitstreamDescriptions.get(1), 1), + MetadataMatcher.matchMetadata("dc.description", bitstreamDescriptions.get(2), 2) + ) + ) + ); + + List ops = List.of( + new ReplaceOperation("/metadata/dc.description/0", bitstreamDescriptions.get(2)), + new ReplaceOperation("/metadata/dc.description/1", bitstreamDescriptions.get(0)), + new ReplaceOperation("/metadata/dc.description/2", bitstreamDescriptions.get(1)) + ); + String requestBody = getPatchContent(ops); + getClient(token) + .perform(patch("/api/core/bitstreams/" + bitstream.getID()) + .content(requestBody) + .contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$.metadata", + Matchers.allOf( + MetadataMatcher.matchMetadata("dc.description", bitstreamDescriptions.get(2), 0), + MetadataMatcher.matchMetadata("dc.description", bitstreamDescriptions.get(0), 1), + MetadataMatcher.matchMetadata("dc.description", bitstreamDescriptions.get(1), 2) + ) + ) + ); + getClient(token) + .perform(get("/api/core/bitstreams/" + bitstream.getID())) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$.metadata", + Matchers.allOf( + MetadataMatcher.matchMetadata("dc.description", bitstreamDescriptions.get(2), 0), + MetadataMatcher.matchMetadata("dc.description", bitstreamDescriptions.get(0), 1), + MetadataMatcher.matchMetadata("dc.description", bitstreamDescriptions.get(1), 2) + ) + ) + ); + } + @Test public void testHiddenMetadataForAnonymousUser() throws Exception { diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowsesResourceControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowsesResourceControllerIT.java index baf459408d..24850cd11b 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowsesResourceControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowsesResourceControllerIT.java @@ -176,6 +176,24 @@ public class BrowsesResourceControllerIT extends AbstractControllerIntegrationTe .withSubject("AnotherTest").withSubject("TestingForMore") .withSubject("ExtraEntry") .build(); + Item withdrawnItem1 = ItemBuilder.createItem(context, col2) + .withTitle("Withdrawn item 1") + .withIssueDate("2016-02-13") + .withAuthor("Smith, Maria").withAuthor("Doe, Jane") + .withSubject("AnotherTest").withSubject("TestingForMore") + .withSubject("ExtraEntry").withSubject("WithdrawnEntry") + .withdrawn() + .build(); + Item privateItem1 = ItemBuilder.createItem(context, col2) + .withTitle("Private item 1") + .withIssueDate("2016-02-13") + .withAuthor("Smith, Maria").withAuthor("Doe, Jane") + .withSubject("AnotherTest").withSubject("TestingForMore") + .withSubject("ExtraEntry").withSubject("PrivateEntry") + .makeUnDiscoverable() + .build(); + + context.restoreAuthSystemState(); @@ -369,6 +387,23 @@ public class BrowsesResourceControllerIT extends AbstractControllerIntegrationTe .withSubject("AnotherTest") .build(); + Item withdrawnItem1 = ItemBuilder.createItem(context, col2) + .withTitle("Withdrawn item 1") + .withIssueDate("2016-02-13") + .withAuthor("Smith, Maria").withAuthor("Doe, Jane") + .withSubject("AnotherTest").withSubject("TestingForMore") + .withSubject("ExtraEntry").withSubject("WithdrawnEntry") + .withdrawn() + .build(); + Item privateItem1 = ItemBuilder.createItem(context, col2) + .withTitle("Private item 1") + .withIssueDate("2016-02-13") + .withAuthor("Smith, Maria").withAuthor("Doe, Jane") + .withSubject("AnotherTest").withSubject("TestingForMore") + .withSubject("ExtraEntry").withSubject("PrivateEntry") + .makeUnDiscoverable() + .build(); + context.restoreAuthSystemState(); //** WHEN ** @@ -407,6 +442,276 @@ public class BrowsesResourceControllerIT extends AbstractControllerIntegrationTe ItemMatcher.matchItemWithTitleAndDateIssued(publicItem1, "zPublic item more", "2017-10-17") ))); + //** WHEN ** + //An anonymous user browses the items that correspond with the PrivateEntry subject query + getClient().perform(get("/api/discover/browses/subject/items") + .param("filterValue", "PrivateEntry")) + //** THEN ** + //The status has to be 200 + .andExpect(status().isOk()) + //We expect the content type to be "application/hal+json;charset=UTF-8" + .andExpect(content().contentType(contentType)) + //We expect there to be no elements because the item is private + .andExpect(jsonPath("$.page.totalElements", is(0))) + .andExpect(jsonPath("$.page.size", is(20))); + + //** WHEN ** + //An anonymous user browses the items that correspond with the WithdrawnEntry subject query + getClient().perform(get("/api/discover/browses/subject/items") + .param("filterValue", "WithdrawnEntry")) + //** THEN ** + //The status has to be 200 + .andExpect(status().isOk()) + //We expect the content type to be "application/hal+json;charset=UTF-8" + .andExpect(content().contentType(contentType)) + //We expect there to be no elements because the item is withdrawn + .andExpect(jsonPath("$.page.totalElements", is(0))) + .andExpect(jsonPath("$.page.size", is(20))); + } + + @Test + public void findBrowseBySubjectItemsWithScope() throws Exception { + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community-collection structure with one parent community with sub-community and two collections. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + Collection col2 = CollectionBuilder.createCollection(context, child1).withName("Collection 2").build(); + + //2. Two public items with the same subject and another public item that contains that same subject, but also + // another one + // All of the items are readable by an Anonymous user + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("zPublic item more") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald").withAuthor("Doe, John") + .withSubject("ExtraEntry").withSubject("AnotherTest") + .build(); + + Item publicItem2 = ItemBuilder.createItem(context, col2) + .withTitle("Public item 2") + .withIssueDate("2016-02-13") + .withAuthor("Smith, Maria").withAuthor("Doe, Jane") + .withSubject("AnotherTest") + .build(); + + Item publicItem3 = ItemBuilder.createItem(context, col2) + .withTitle("Public item 3") + .withIssueDate("2016-02-14") + .withAuthor("Smith, Maria").withAuthor("Doe, Jane") + .withSubject("AnotherTest") + .build(); + + Item withdrawnItem1 = ItemBuilder.createItem(context, col2) + .withTitle("Withdrawn item 1") + .withIssueDate("2016-02-13") + .withAuthor("Smith, Maria").withAuthor("Doe, Jane") + .withSubject("AnotherTest").withSubject("TestingForMore") + .withSubject("ExtraEntry").withSubject("WithdrawnEntry") + .withdrawn() + .build(); + Item privateItem1 = ItemBuilder.createItem(context, col2) + .withTitle("Private item 1") + .withIssueDate("2016-02-13") + .withAuthor("Smith, Maria").withAuthor("Doe, Jane") + .withSubject("AnotherTest").withSubject("TestingForMore") + .withSubject("ExtraEntry").withSubject("PrivateEntry") + .makeUnDiscoverable() + .build(); + + context.restoreAuthSystemState(); + + //** WHEN ** + //An anonymous user browses the items that correspond with the ExtraEntry subject query + getClient().perform(get("/api/discover/browses/subject/items") + .param("scope", String.valueOf(col2.getID())) + .param("filterValue", "ExtraEntry")) + //** THEN ** + //The status has to be 200 + .andExpect(status().isOk()) + //We expect the content type to be "application/hal+json;charset=UTF-8" + .andExpect(content().contentType(contentType)) + //We expect there to be no elements in collection 2 + .andExpect(jsonPath("$.page.totalElements", is(0))) + .andExpect(jsonPath("$.page.size", is(20))); + + //** WHEN ** + //An anonymous user browses the items that correspond with the AnotherTest subject query + getClient().perform(get("/api/discover/browses/subject/items") + .param("scope", String.valueOf(col2.getID())) + .param("filterValue", "AnotherTest")) + //** THEN ** + //The status has to be 200 + .andExpect(status().isOk()) + //We expect the content type to be "application/hal+json;charset=UTF-8" + .andExpect(content().contentType(contentType)) + //We expect there to be only two elements, the ones that we've added with the requested subject + // in collection 2 + .andExpect(jsonPath("$.page.totalElements", is(2))) + .andExpect(jsonPath("$.page.size", is(20))) + //Verify that the title of the public and embargoed items are present and sorted descending + .andExpect(jsonPath("$._embedded.items", contains( + ItemMatcher.matchItemWithTitleAndDateIssued(publicItem2, "Public item 2", "2016-02-13"), + ItemMatcher.matchItemWithTitleAndDateIssued(publicItem3, "Public item 3", "2016-02-14") + ))); + + //** WHEN ** + //An anonymous user browses the items that correspond with the PrivateEntry subject query + getClient().perform(get("/api/discover/browses/subject/items") + .param("scope", String.valueOf(col2.getID())) + .param("filterValue", "PrivateEntry")) + //** THEN ** + //The status has to be 200 + .andExpect(status().isOk()) + //We expect the content type to be "application/hal+json;charset=UTF-8" + .andExpect(content().contentType(contentType)) + //We expect there to be no elements because the item is private + .andExpect(jsonPath("$.page.totalElements", is(0))) + .andExpect(jsonPath("$.page.size", is(20))); + + //** WHEN ** + //An anonymous user browses the items that correspond with the WithdrawnEntry subject query + getClient().perform(get("/api/discover/browses/subject/items") + .param("scope", String.valueOf(col2.getID())) + .param("filterValue", "WithdrawnEntry")) + //** THEN ** + //The status has to be 200 + .andExpect(status().isOk()) + //We expect the content type to be "application/hal+json;charset=UTF-8" + .andExpect(content().contentType(contentType)) + //We expect there to be no elements because the item is withdrawn + .andExpect(jsonPath("$.page.totalElements", is(0))) + .andExpect(jsonPath("$.page.size", is(20))); + } + + @Test + public void findBrowseBySubjectItemsWithScopeAsAdmin() throws Exception { + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community-collection structure with one parent community with sub-community and two collections. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + Collection col2 = CollectionBuilder.createCollection(context, child1).withName("Collection 2").build(); + + //2. Two public items with the same subject and another public item that contains that same subject, but also + // another one + // All of the items are readable by an Anonymous user + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("zPublic item more") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald").withAuthor("Doe, John") + .withSubject("ExtraEntry").withSubject("AnotherTest") + .build(); + + Item publicItem2 = ItemBuilder.createItem(context, col2) + .withTitle("Public item 2") + .withIssueDate("2016-02-13") + .withAuthor("Smith, Maria").withAuthor("Doe, Jane") + .withSubject("AnotherTest") + .build(); + + Item publicItem3 = ItemBuilder.createItem(context, col2) + .withTitle("Public item 3") + .withIssueDate("2016-02-14") + .withAuthor("Smith, Maria").withAuthor("Doe, Jane") + .withSubject("AnotherTest") + .build(); + + Item withdrawnItem1 = ItemBuilder.createItem(context, col2) + .withTitle("Withdrawn item 1") + .withIssueDate("2016-02-13") + .withAuthor("Smith, Maria").withAuthor("Doe, Jane") + .withSubject("AnotherTest").withSubject("TestingForMore") + .withSubject("ExtraEntry").withSubject("WithdrawnEntry") + .withdrawn() + .build(); + Item privateItem1 = ItemBuilder.createItem(context, col2) + .withTitle("Private item 1") + .withIssueDate("2016-02-13") + .withAuthor("Smith, Maria").withAuthor("Doe, Jane") + .withSubject("AnotherTest").withSubject("TestingForMore") + .withSubject("ExtraEntry").withSubject("PrivateEntry") + .makeUnDiscoverable() + .build(); + + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + + + //** WHEN ** + //An admin user browses the items that correspond with the ExtraEntry subject query + getClient(adminToken).perform(get("/api/discover/browses/subject/items") + .param("scope", String.valueOf(col2.getID())) + .param("filterValue", "ExtraEntry")) + //** THEN ** + //The status has to be 200 + .andExpect(status().isOk()) + //We expect the content type to be "application/hal+json;charset=UTF-8" + .andExpect(content().contentType(contentType)) + //We expect there to be no elements in collection 2 + .andExpect(jsonPath("$.page.totalElements", is(0))) + .andExpect(jsonPath("$.page.size", is(20))); + + //** WHEN ** + //An admin user browses the items that correspond with the AnotherTest subject query + getClient(adminToken).perform(get("/api/discover/browses/subject/items") + .param("scope", String.valueOf(col2.getID())) + .param("filterValue", "AnotherTest")) + //** THEN ** + //The status has to be 200 + .andExpect(status().isOk()) + //We expect the content type to be "application/hal+json;charset=UTF-8" + .andExpect(content().contentType(contentType)) + //We expect there to be only two elements, the ones that we've added with the requested subject + // in collection 2 + .andExpect(jsonPath("$.page.totalElements", is(2))) + .andExpect(jsonPath("$.page.size", is(20))) + //Verify that the title of the public and embargoed items are present and sorted descending + .andExpect(jsonPath("$._embedded.items", contains( + ItemMatcher.matchItemWithTitleAndDateIssued(publicItem2, "Public item 2", "2016-02-13"), + ItemMatcher.matchItemWithTitleAndDateIssued(publicItem3, "Public item 3", "2016-02-14") + ))); + + //** WHEN ** + //An admin user browses the items that correspond with the PrivateEntry subject query + getClient(adminToken).perform(get("/api/discover/browses/subject/items") + .param("scope", String.valueOf(col2.getID())) + .param("filterValue", "PrivateEntry")) + //** THEN ** + //The status has to be 200 + .andExpect(status().isOk()) + //We expect the content type to be "application/hal+json;charset=UTF-8" + .andExpect(content().contentType(contentType)) + //We expect there to be no elements because the item is private + .andExpect(jsonPath("$.page.totalElements", is(0))) + .andExpect(jsonPath("$.page.size", is(20))); + + //** WHEN ** + //An admin user browses the items that correspond with the WithdrawnEntry subject query + getClient(adminToken).perform(get("/api/discover/browses/subject/items") + .param("scope", String.valueOf(col2.getID())) + .param("filterValue", "WithdrawnEntry")) + //** THEN ** + //The status has to be 200 + .andExpect(status().isOk()) + //We expect the content type to be "application/hal+json;charset=UTF-8" + .andExpect(content().contentType(contentType)) + //We expect there to be no elements because the item is withdrawn + .andExpect(jsonPath("$.page.totalElements", is(0))) + .andExpect(jsonPath("$.page.size", is(20))); } @Test @@ -501,13 +806,181 @@ public class BrowsesResourceControllerIT extends AbstractControllerIntegrationTe .andExpect(jsonPath("$._embedded.items[*].metadata", Matchers.allOf( not(matchMetadata("dc.title", "This is a private item")), not(matchMetadata("dc.title", "Internal publication"))))); + + String adminToken = getAuthToken(admin.getEmail(), password); + + //** WHEN ** + //An anonymous user browses the items in the Browse by item endpoint + //sorted descending by tile + getClient(adminToken).perform(get("/api/discover/browses/title/items") + .param("sort", "title,desc")) + + //** THEN ** + //The status has to be 200 OK + .andExpect(status().isOk()) + //We expect the content type to be "application/hal+json;charset=UTF-8" + .andExpect(content().contentType(contentType)) + + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", is(4))) + .andExpect(jsonPath("$.page.totalPages", is(1))) + .andExpect(jsonPath("$.page.number", is(0))) + + .andExpect(jsonPath("$._embedded.items", + contains(ItemMatcher.matchItemWithTitleAndDateIssued(publicItem2, + "Public item 2", + "2016-02-13"), + ItemMatcher.matchItemWithTitleAndDateIssued(publicItem1, + "Public item 1", + "2017-10-17"), + ItemMatcher.matchItemWithTitleAndDateIssued(internalItem, + "Internal publication", + "2016-09-19"), + ItemMatcher.matchItemWithTitleAndDateIssued(embargoedItem, + "An embargoed publication", + "2017-08-10") + ))) + + //The private and internal items must not be present + .andExpect(jsonPath("$._embedded.items[*].metadata", Matchers.allOf( + not(matchMetadata("dc.title", "This is a private item")), + not(matchMetadata("dc.title", "Internal publication"))))); + } + + @Test + public void findBrowseByTitleItemsWithScope() throws Exception { + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community-collection structure with one parent community with sub-community and two collections. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + Collection col2 = CollectionBuilder.createCollection(context, child1).withName("Collection 2").build(); + + //2. Two public items that are readable by Anonymous + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Public item 1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald").withAuthor("Doe, John") + .withSubject("Java").withSubject("Unit Testing") + .build(); + + Item publicItem2 = ItemBuilder.createItem(context, col2) + .withTitle("Public item 2") + .withIssueDate("2016-02-13") + .withAuthor("Smith, Maria").withAuthor("Doe, Jane") + .withSubject("Angular").withSubject("Unit Testing") + .build(); + + //3. An item that has been made private + Item privateItem = ItemBuilder.createItem(context, col2) + .withTitle("This is a private item") + .withIssueDate("2015-03-12") + .withAuthor("Duck, Donald") + .withSubject("Cartoons").withSubject("Ducks") + .makeUnDiscoverable() + .build(); + + //4. An item with an item-level embargo + Item embargoedItem = ItemBuilder.createItem(context, col2) + .withTitle("An embargoed publication") + .withIssueDate("2017-08-10") + .withAuthor("Mouse, Mickey") + .withSubject("Cartoons").withSubject("Mice") + .withEmbargoPeriod("12 months") + .build(); + + //5. An item that is only readable for an internal groups + Group internalGroup = GroupBuilder.createGroup(context) + .withName("Internal Group") + .build(); + + Item internalItem = ItemBuilder.createItem(context, col2) + .withTitle("Internal publication") + .withIssueDate("2016-09-19") + .withAuthor("Doe, John") + .withSubject("Unknown") + .withReaderGroup(internalGroup) + .build(); + + context.restoreAuthSystemState(); + + //** WHEN ** + //An anonymous user browses the items in the Browse by item endpoint + //sorted descending by tile + getClient().perform(get("/api/discover/browses/title/items") + .param("scope", String.valueOf(col2.getID())) + .param("sort", "title,desc")) + + //** THEN ** + //The status has to be 200 OK + .andExpect(status().isOk()) + //We expect the content type to be "application/hal+json;charset=UTF-8" + .andExpect(content().contentType(contentType)) + + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", is(1))) + .andExpect(jsonPath("$.page.totalPages", is(1))) + .andExpect(jsonPath("$.page.number", is(0))) + + .andExpect(jsonPath("$._embedded.items", + contains(ItemMatcher.matchItemWithTitleAndDateIssued(publicItem2, + "Public item 2", + "2016-02-13")))) + + //The private and internal items must not be present + .andExpect(jsonPath("$._embedded.items[*].metadata", Matchers.allOf( + not(matchMetadata("dc.title", "This is a private item")), + not(matchMetadata("dc.title", "Internal publication"))))); + + String adminToken = getAuthToken(admin.getEmail(), password); + //** WHEN ** + //An admin user browses the items in the Browse by item endpoint + //sorted descending by tile + getClient(adminToken).perform(get("/api/discover/browses/title/items") + .param("scope", String.valueOf(col2.getID())) + .param("sort", "title,desc")) + + //** THEN ** + //The status has to be 200 OK + .andExpect(status().isOk()) + //We expect the content type to be "application/hal+json;charset=UTF-8" + .andExpect(content().contentType(contentType)) + + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", is(3))) + .andExpect(jsonPath("$.page.totalPages", is(1))) + .andExpect(jsonPath("$.page.number", is(0))) + .andExpect(jsonPath("$._embedded.items", contains( + ItemMatcher.matchItemWithTitleAndDateIssued(publicItem2, + "Public item 2", + "2016-02-13"), + ItemMatcher.matchItemWithTitleAndDateIssued(internalItem, + "Internal publication", + "2016-09-19"), + ItemMatcher.matchItemWithTitleAndDateIssued(embargoedItem, + "An embargoed publication", + "2017-08-10") + + ))) + + + //The private and internal items must not be present + .andExpect(jsonPath("$._embedded.items[*].metadata", Matchers.allOf( + not(matchMetadata("dc.title", "This is a private item")) + ))); } @Test /** * This test was introduced to reproduce the bug DS-4269 Pagination links must be consistent also when there is not * explicit pagination parameters in the request (i.e. defaults apply) - * + * * @throws Exception */ public void browsePaginationWithoutExplicitParams() throws Exception { @@ -623,6 +1096,18 @@ public class BrowsesResourceControllerIT extends AbstractControllerIntegrationTe .withIssueDate("2016-01-12") .build(); + Item withdrawnItem1 = ItemBuilder.createItem(context, col2) + .withTitle("Withdrawn item 1") + .withIssueDate("2016-02-13") + .withdrawn() + .build(); + + Item privateItem1 = ItemBuilder.createItem(context, col2) + .withTitle("Private item 1") + .makeUnDiscoverable() + .build(); + + context.restoreAuthSystemState(); //** WHEN ** @@ -682,6 +1167,159 @@ public class BrowsesResourceControllerIT extends AbstractControllerIntegrationTe ItemMatcher.matchItemWithTitleAndDateIssued(item7, "Item 7", "2016-01-12") ))); + + String adminToken = getAuthToken(admin.getEmail(), password); + //The next page gives us the last two items + getClient(adminToken).perform(get("/api/discover/browses/dateissued/items") + .param("sort", "title,asc") + .param("size", "5") + .param("page", "1")) + + //The status has to be 200 OK + .andExpect(status().isOk()) + //We expect the content type to be "application/hal+json;charset=UTF-8" + .andExpect(content().contentType(contentType)) + + //We expect only the first five items to be present + .andExpect(jsonPath("$.page.size", is(5))) + .andExpect(jsonPath("$.page.totalElements", is(7))) + .andExpect(jsonPath("$.page.totalPages", is(2))) + .andExpect(jsonPath("$.page.number", is(1))) + + //Verify that the title and date of the items match and that they are sorted ascending + .andExpect(jsonPath("$._embedded.items", + contains(ItemMatcher.matchItemWithTitleAndDateIssued(item6, + "Item 6", "2016-01-13"), + ItemMatcher.matchItemWithTitleAndDateIssued(item7, + "Item 7", "2016-01-12") + ))); + } + + @Test + public void testPaginationBrowseByDateIssuedItemsWithScope() throws Exception { + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community-collection structure with one parent community with sub-community and two collections. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + Collection col2 = CollectionBuilder.createCollection(context, child1).withName("Collection 2").build(); + + //2. 7 public items that are readable by Anonymous + Item item1 = ItemBuilder.createItem(context, col1) + .withTitle("Item 1") + .withIssueDate("2017-10-17") + .build(); + + Item item2 = ItemBuilder.createItem(context, col2) + .withTitle("Item 2") + .withIssueDate("2016-02-13") + .build(); + + Item item3 = ItemBuilder.createItem(context, col1) + .withTitle("Item 3") + .withIssueDate("2016-02-12") + .build(); + + Item item4 = ItemBuilder.createItem(context, col2) + .withTitle("Item 4") + .withIssueDate("2016-02-11") + .build(); + + Item item5 = ItemBuilder.createItem(context, col1) + .withTitle("Item 5") + .withIssueDate("2016-02-10") + .build(); + + Item item6 = ItemBuilder.createItem(context, col2) + .withTitle("Item 6") + .withIssueDate("2016-01-13") + .build(); + + Item item7 = ItemBuilder.createItem(context, col1) + .withTitle("Item 7") + .withIssueDate("2016-01-12") + .build(); + + Item withdrawnItem1 = ItemBuilder.createItem(context, col2) + .withTitle("Withdrawn item 1") + .withIssueDate("2016-02-13") + .withdrawn() + .build(); + + Item privateItem1 = ItemBuilder.createItem(context, col2) + .withTitle("Private item 1") + .makeUnDiscoverable() + .build(); + + + context.restoreAuthSystemState(); + + //** WHEN ** + //An anonymous user browses the items in the Browse by date issued endpoint + //sorted ascending by tile with a page size of 5 + getClient().perform(get("/api/discover/browses/dateissued/items") + .param("scope", String.valueOf(col2.getID())) + .param("sort", "title,asc") + .param("size", "5")) + + //** THEN ** + //The status has to be 200 OK + .andExpect(status().isOk()) + //We expect the content type to be "application/hal+json;charset=UTF-8" + .andExpect(content().contentType(contentType)) + + //We expect only the first five items to be present + .andExpect(jsonPath("$.page.size", is(5))) + .andExpect(jsonPath("$.page.totalElements", is(3))) + .andExpect(jsonPath("$.page.totalPages", is(1))) + .andExpect(jsonPath("$.page.number", is(0))) + + //Verify that the title and date of the items match and that they are sorted ascending + .andExpect(jsonPath("$._embedded.items", + contains( + ItemMatcher.matchItemWithTitleAndDateIssued(item2, + "Item 2", "2016-02-13"), + ItemMatcher.matchItemWithTitleAndDateIssued(item4, + "Item 4", "2016-02-11"), + ItemMatcher.matchItemWithTitleAndDateIssued(item6, + "Item 6", "2016-01-13") + ))); + + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/discover/browses/dateissued/items") + .param("scope", String.valueOf(col2.getID())) + .param("sort", "title,asc") + .param("size", "5")) + + //** THEN ** + //The status has to be 200 OK + .andExpect(status().isOk()) + //We expect the content type to be "application/hal+json;charset=UTF-8" + .andExpect(content().contentType(contentType)) + + //We expect only the first five items to be present + .andExpect(jsonPath("$.page.size", is(5))) + .andExpect(jsonPath("$.page.totalElements", is(3))) + .andExpect(jsonPath("$.page.totalPages", is(1))) + .andExpect(jsonPath("$.page.number", is(0))) + + //Verify that the title and date of the items match and that they are sorted ascending + .andExpect(jsonPath("$._embedded.items", + contains( + ItemMatcher.matchItemWithTitleAndDateIssued(item2, + "Item 2", "2016-02-13"), + ItemMatcher.matchItemWithTitleAndDateIssued(item4, + "Item 4", "2016-02-11"), + ItemMatcher.matchItemWithTitleAndDateIssued(item6, + "Item 6", "2016-01-13") + ))); + } @Test @@ -1111,7 +1749,7 @@ public class BrowsesResourceControllerIT extends AbstractControllerIntegrationTe //We expect the totalElements to be the 1 item present in the collection .andExpect(jsonPath("$.page.totalElements", is(1))) - //As this is is a small collection, we expect to go-to page 0 + //As this is a small collection, we expect to go-to page 0 .andExpect(jsonPath("$.page.number", is(0))) .andExpect(jsonPath("$._links.self.href", containsString("startsWith=Blade"))) @@ -1121,6 +1759,33 @@ public class BrowsesResourceControllerIT extends AbstractControllerIntegrationTe "Blade Runner", "1982-06-25") ))); + + //Test filtering with spaces: + //** WHEN ** + //An anonymous user browses the items in the Browse by Title endpoint + //with startsWith set to Blade Runner and scope set to Col 1 + getClient().perform(get("/api/discover/browses/title/items?startsWith=Blade Runner") + .param("scope", col1.getID().toString()) + .param("size", "2")) + + //** THEN ** + //The status has to be 200 OK + .andExpect(status().isOk()) + //We expect the content type to be "application/hal+json;charset=UTF-8" + .andExpect(content().contentType(contentType)) + + //We expect the totalElements to be the 1 item present in the collection + .andExpect(jsonPath("$.page.totalElements", is(1))) + //As this is a small collection, we expect to go-to page 0 + .andExpect(jsonPath("$.page.number", is(0))) + .andExpect(jsonPath("$._links.self.href", containsString("startsWith=Blade Runner"))) + + //Verify that the index jumps to the "Blade Runner" item. + .andExpect(jsonPath("$._embedded.items", + contains(ItemMatcher.matchItemWithTitleAndDateIssued(item2, + "Blade Runner", + "1982-06-25") + ))); } @Test @@ -1460,4 +2125,57 @@ public class BrowsesResourceControllerIT extends AbstractControllerIntegrationTe .andExpect(jsonPath("$._embedded.items[0]._embedded.owningCollection._embedded.adminGroup", nullValue())); } + + /** + * Expect a single author browse definition + * @throws Exception + */ + @Test + public void findOneLinked() throws Exception { + // When we call the search endpoint + getClient().perform(get("/api/discover/browses/search/byFields") + .param("fields", "dc.contributor.author")) + // The status has to be 200 OK + .andExpect(status().isOk()) + // We expect the content type to be "application/hal+json;charset=UTF-8" + .andExpect(content().contentType(contentType)) + // The browse definition ID should be "author" + .andExpect(jsonPath("$.id", is("author"))) + // It should be configured as a metadata browse + .andExpect(jsonPath("$.metadataBrowse", is(true))) + ; + } + + @Test + public void findOneLinkedPassingTwoFields() throws Exception { + // When we call the search endpoint + getClient().perform(get("/api/discover/browses/search/byFields") + .param("fields", "dc.contributor.author") + .param("fields", "dc.date.issued")) + // The status has to be 200 OK + .andExpect(status().isOk()) + // We expect the content type to be "application/hal+json;charset=UTF-8" + .andExpect(content().contentType(contentType)) + // The browse definition ID should be "author" + .andExpect(jsonPath("$.id", is("author"))) + // It should be configured as a metadata browse + .andExpect(jsonPath("$.metadataBrowse", is(true))); + } + + @Test + public void findUnconfiguredFields() throws Exception { + // When we call the search endpoint with a field that isn't configured for any browse links + getClient().perform(get("/api/discover/browses/search/byFields") + .param("fields", "dc.identifier.uri")) + // The status has to be 204 NO CONTENT + .andExpect(status().isNoContent()); + } + + @Test + public void findBrowseLinksWithMissingParameter() throws Exception { + // When we call the search endpoint with a field that isn't configured for any browse links + getClient().perform(get("/api/discover/browses/search/byFields")) + // The status has to be 400 BAD REQUEST + .andExpect(status().isBadRequest()); + } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BundleRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BundleRestRepositoryIT.java index 96385095a2..259580f8c0 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BundleRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BundleRestRepositoryIT.java @@ -37,6 +37,7 @@ import org.dspace.app.rest.model.MetadataRest; import org.dspace.app.rest.model.MetadataValueRest; import org.dspace.app.rest.model.patch.MoveOperation; import org.dspace.app.rest.model.patch.Operation; +import org.dspace.app.rest.model.patch.ReplaceOperation; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.authorize.ResourcePolicy; import org.dspace.authorize.service.ResourcePolicyService; @@ -51,6 +52,8 @@ import org.dspace.content.Bitstream; import org.dspace.content.Bundle; import org.dspace.content.Collection; import org.dspace.content.Item; +import org.dspace.content.MetadataSchemaEnum; +import org.dspace.content.service.BundleService; import org.dspace.content.service.ItemService; import org.dspace.core.Constants; import org.dspace.eperson.EPerson; @@ -68,6 +71,9 @@ public class BundleRestRepositoryIT extends AbstractControllerIntegrationTest { @Autowired ItemService itemService; + @Autowired + BundleService bundleService; + private Collection collection; private Item item; private Bundle bundle1; @@ -515,6 +521,77 @@ public class BundleRestRepositoryIT extends AbstractControllerIntegrationTest { ))); } + @Test + public void patchReplaceMultipleDescriptionBundle() throws Exception { + context.turnOffAuthorisationSystem(); + + List bundleDescriptions = List.of( + "FIRST", + "SECOND", + "THIRD" + ); + + bundle1 = BundleBuilder.createBundle(context, item) + .withName("testname") + .build(); + + this.bundleService + .addMetadata( + context, bundle1, + MetadataSchemaEnum.DC.getName(), "description", null, + Item.ANY, bundleDescriptions + ); + + context.restoreAuthSystemState(); + String token = getAuthToken(admin.getEmail(), password); + + getClient(token) + .perform(get("/api/core/bundles/" + bundle1.getID())) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$.metadata", + Matchers.allOf( + MetadataMatcher.matchMetadata("dc.description", bundleDescriptions.get(0), 0), + MetadataMatcher.matchMetadata("dc.description", bundleDescriptions.get(1), 1), + MetadataMatcher.matchMetadata("dc.description", bundleDescriptions.get(2), 2) + ) + ) + ); + + List ops = List.of( + new ReplaceOperation("/metadata/dc.description/0", bundleDescriptions.get(2)), + new ReplaceOperation("/metadata/dc.description/1", bundleDescriptions.get(0)), + new ReplaceOperation("/metadata/dc.description/2", bundleDescriptions.get(1)) + ); + String requestBody = getPatchContent(ops); + getClient(token) + .perform(patch("/api/core/bundles/" + bundle1.getID()) + .content(requestBody) + .contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$.metadata", + Matchers.allOf( + MetadataMatcher.matchMetadata("dc.description", bundleDescriptions.get(2), 0), + MetadataMatcher.matchMetadata("dc.description", bundleDescriptions.get(0), 1), + MetadataMatcher.matchMetadata("dc.description", bundleDescriptions.get(1), 2) + ) + ) + ); + getClient(token) + .perform(get("/api/core/bundles/" + bundle1.getID())) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$.metadata", + Matchers.allOf( + MetadataMatcher.matchMetadata("dc.description", bundleDescriptions.get(2), 0), + MetadataMatcher.matchMetadata("dc.description", bundleDescriptions.get(0), 1), + MetadataMatcher.matchMetadata("dc.description", bundleDescriptions.get(1), 2) + ) + ) + ); + } + @Test public void deleteBundle() throws Exception { context.turnOffAuthorisationSystem(); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java index ab37fac106..ee522db170 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java @@ -69,6 +69,7 @@ import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.EntityType; import org.dspace.content.Item; +import org.dspace.content.MetadataSchemaEnum; import org.dspace.content.service.CollectionService; import org.dspace.core.Constants; import org.dspace.eperson.EPerson; @@ -499,13 +500,13 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes getClient(tokenParentAdmin).perform(get("/api/core/collections/" + col1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$", - Matchers.is((CollectionMatcher.matchCollection(col1))))); + Matchers.is(CollectionMatcher.matchCollection(col1)))); String tokenCol1Admin = getAuthToken(col1Admin.getEmail(), "qwerty02"); getClient(tokenCol1Admin).perform(get("/api/core/collections/" + col1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$", - Matchers.is((CollectionMatcher.matchCollection(col1))))); + Matchers.is(CollectionMatcher.matchCollection(col1)))); String tokenCol2Admin = getAuthToken(col2Admin.getEmail(), "qwerty03"); getClient(tokenCol2Admin).perform(get("/api/core/collections/" + col1.getID())) @@ -1206,7 +1207,7 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes ) ))) .andDo(result -> idRef - .set(UUID.fromString(read(result.getResponse().getContentAsString(), "$.id"))));; + .set(UUID.fromString(read(result.getResponse().getContentAsString(), "$.id")))); getClient(authToken).perform(post("/api/core/collections") @@ -3101,6 +3102,81 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes .andExpect(status().isUnauthorized()); } + @Test + public void patchReplaceMultipleDescriptionCollection() throws Exception { + context.turnOffAuthorisationSystem(); + + List collectionDescriptions = List.of( + "FIRST", + "SECOND", + "THIRD" + ); + + parentCommunity = + CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("MyTest") + .build(); + + this.collectionService + .addMetadata( + context, col, MetadataSchemaEnum.DC.getName(), "description", null, Item.ANY, collectionDescriptions + ); + + context.restoreAuthSystemState(); + String token = getAuthToken(admin.getEmail(), password); + + getClient(token) + .perform(get("/api/core/collections/" + col.getID())) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$.metadata", + Matchers.allOf( + MetadataMatcher.matchMetadata("dc.description", collectionDescriptions.get(0), 0), + MetadataMatcher.matchMetadata("dc.description", collectionDescriptions.get(1), 1), + MetadataMatcher.matchMetadata("dc.description", collectionDescriptions.get(2), 2) + ) + ) + ); + + List ops = List.of( + new ReplaceOperation("/metadata/dc.description/0", collectionDescriptions.get(2)), + new ReplaceOperation("/metadata/dc.description/1", collectionDescriptions.get(0)), + new ReplaceOperation("/metadata/dc.description/2", collectionDescriptions.get(1)) + ); + String requestBody = getPatchContent(ops); + getClient(token) + .perform(patch("/api/core/collections/" + col.getID()) + .content(requestBody) + .contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$.metadata", + Matchers.allOf( + MetadataMatcher.matchMetadata("dc.description", collectionDescriptions.get(2), 0), + MetadataMatcher.matchMetadata("dc.description", collectionDescriptions.get(0), 1), + MetadataMatcher.matchMetadata("dc.description", collectionDescriptions.get(1), 2) + ) + ) + ); + getClient(token) + .perform(get("/api/core/collections/" + col.getID())) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$.metadata", + Matchers.allOf( + MetadataMatcher.matchMetadata("dc.description", collectionDescriptions.get(2), 0), + MetadataMatcher.matchMetadata("dc.description", collectionDescriptions.get(0), 1), + MetadataMatcher.matchMetadata("dc.description", collectionDescriptions.get(1), 2) + ) + ) + ); + } + @Test public void patchMetadataCheckReindexingTest() throws Exception { context.turnOffAuthorisationSystem(); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java index e084aa1746..30614e6125 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java @@ -20,6 +20,7 @@ import static org.springframework.data.rest.webmvc.RestMediaTypes.TEXT_URI_LIST_ import static org.springframework.http.MediaType.parseMediaType; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; @@ -44,6 +45,8 @@ import org.dspace.app.rest.model.CommunityRest; import org.dspace.app.rest.model.GroupRest; import org.dspace.app.rest.model.MetadataRest; import org.dspace.app.rest.model.MetadataValueRest; +import org.dspace.app.rest.model.patch.Operation; +import org.dspace.app.rest.model.patch.ReplaceOperation; import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.app.rest.test.MetadataPatchSuite; @@ -56,6 +59,8 @@ import org.dspace.builder.GroupBuilder; import org.dspace.builder.ResourcePolicyBuilder; import org.dspace.content.Collection; import org.dspace.content.Community; +import org.dspace.content.Item; +import org.dspace.content.MetadataSchemaEnum; import org.dspace.content.service.CommunityService; import org.dspace.core.Constants; import org.dspace.eperson.EPerson; @@ -1935,6 +1940,78 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest runPatchMetadataTests(eperson, 403); } + @Test + public void patchReplaceMultipleDescriptionCommunity() throws Exception { + context.turnOffAuthorisationSystem(); + + List communityDescriptions = List.of( + "FIRST", + "SECOND", + "THIRD" + ); + + parentCommunity = + CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + this.communityService + .addMetadata( + context, parentCommunity, + MetadataSchemaEnum.DC.getName(), "description", null, + Item.ANY, communityDescriptions + ); + + context.restoreAuthSystemState(); + String token = getAuthToken(admin.getEmail(), password); + + getClient(token) + .perform(get("/api/core/communities/" + parentCommunity.getID())) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$.metadata", + Matchers.allOf( + MetadataMatcher.matchMetadata("dc.description", communityDescriptions.get(0), 0), + MetadataMatcher.matchMetadata("dc.description", communityDescriptions.get(1), 1), + MetadataMatcher.matchMetadata("dc.description", communityDescriptions.get(2), 2) + ) + ) + ); + + List ops = List.of( + new ReplaceOperation("/metadata/dc.description/0", communityDescriptions.get(2)), + new ReplaceOperation("/metadata/dc.description/1", communityDescriptions.get(0)), + new ReplaceOperation("/metadata/dc.description/2", communityDescriptions.get(1)) + ); + String requestBody = getPatchContent(ops); + getClient(token) + .perform(patch("/api/core/communities/" + parentCommunity.getID()) + .content(requestBody) + .contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$.metadata", + Matchers.allOf( + MetadataMatcher.matchMetadata("dc.description", communityDescriptions.get(2), 0), + MetadataMatcher.matchMetadata("dc.description", communityDescriptions.get(0), 1), + MetadataMatcher.matchMetadata("dc.description", communityDescriptions.get(1), 2) + ) + ) + ); + getClient(token) + .perform(get("/api/core/communities/" + parentCommunity.getID())) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$.metadata", + Matchers.allOf( + MetadataMatcher.matchMetadata("dc.description", communityDescriptions.get(2), 0), + MetadataMatcher.matchMetadata("dc.description", communityDescriptions.get(0), 1), + MetadataMatcher.matchMetadata("dc.description", communityDescriptions.get(1), 2) + ) + ) + ); + } + private void runPatchMetadataTests(EPerson asUser, int expectedStatus) throws Exception { context.turnOffAuthorisationSystem(); parentCommunity = CommunityBuilder.createCommunity(context).withName("Community").build(); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/DataCiteImportMetadataSourceServiceIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/DataCiteImportMetadataSourceServiceIT.java new file mode 100644 index 0000000000..83ebc40c79 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/DataCiteImportMetadataSourceServiceIT.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.app.rest; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.when; + +import java.io.InputStream; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import javax.el.MethodNotFoundException; + +import org.apache.commons.io.IOUtils; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.impl.client.CloseableHttpClient; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.content.Item; +import org.dspace.importer.external.datacite.DataCiteImportMetadataSourceServiceImpl; +import org.dspace.importer.external.datamodel.ImportRecord; +import org.dspace.importer.external.liveimportclient.service.LiveImportClientImpl; +import org.dspace.importer.external.metadatamapping.MetadatumDTO; +import org.junit.Test; +import org.mockito.ArgumentMatchers; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; + + +/** + * Integration tests for {@link DataCiteImportMetadataSourceServiceImpl} + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.com) + */ +public class DataCiteImportMetadataSourceServiceIT extends AbstractLiveImportIntegrationTest { + + @Autowired + private LiveImportClientImpl liveImportClientImpl; + + @Autowired + private DataCiteImportMetadataSourceServiceImpl dataCiteServiceImpl; + + @Test + public void dataCiteImportMetadataGetRecordsTest() throws Exception { + context.turnOffAuthorisationSystem(); + CloseableHttpClient originalHttpClient = liveImportClientImpl.getHttpClient(); + CloseableHttpClient httpClient = Mockito.mock(CloseableHttpClient.class); + try (InputStream dataCiteResp = getClass().getResourceAsStream("dataCite-test.json")) { + String dataCiteRespXmlResp = IOUtils.toString(dataCiteResp, Charset.defaultCharset()); + + liveImportClientImpl.setHttpClient(httpClient); + CloseableHttpResponse response = mockResponse(dataCiteRespXmlResp, 200, "OK"); + when(httpClient.execute(ArgumentMatchers.any())).thenReturn(response); + + context.restoreAuthSystemState(); + ArrayList collection2match = getRecords(); + Collection recordsImported = dataCiteServiceImpl.getRecords("10.48550/arxiv.2207.04779", + 0, -1); + assertEquals(1, recordsImported.size()); + matchRecords(new ArrayList<>(recordsImported), collection2match); + } finally { + liveImportClientImpl.setHttpClient(originalHttpClient); + } + } + + @Test + public void dataCiteImportMetadataGetRecordsCountTest() throws Exception { + context.turnOffAuthorisationSystem(); + CloseableHttpClient originalHttpClient = liveImportClientImpl.getHttpClient(); + CloseableHttpClient httpClient = Mockito.mock(CloseableHttpClient.class); + try (InputStream dataciteResp = getClass().getResourceAsStream("dataCite-test.json")) { + String dataciteTextResp = IOUtils.toString(dataciteResp, Charset.defaultCharset()); + + liveImportClientImpl.setHttpClient(httpClient); + CloseableHttpResponse response = mockResponse(dataciteTextResp, 200, "OK"); + when(httpClient.execute(ArgumentMatchers.any())).thenReturn(response); + + context.restoreAuthSystemState(); + int tot = dataCiteServiceImpl.getRecordsCount("10.48550/arxiv.2207.04779"); + assertEquals(1, tot); + } finally { + liveImportClientImpl.setHttpClient(originalHttpClient); + } + } + + @Test(expected = MethodNotFoundException.class) + public void dataCiteImportMetadataFindMatchingRecordsTest() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + org.dspace.content.Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + Item testItem = ItemBuilder.createItem(context, col1) + .withTitle("test item") + .withIssueDate("2021") + .build(); + + context.restoreAuthSystemState(); + dataCiteServiceImpl.findMatchingRecords(testItem); + } + + private ArrayList getRecords() { + ArrayList records = new ArrayList<>(); + //define first record + List metadatums = new ArrayList<>(); + MetadatumDTO title = createMetadatumDTO("dc", "title", null, + "Mathematical Proof Between Generations"); + MetadatumDTO doi = createMetadatumDTO("dc", "identifier", "doi", "10.48550/arxiv.2207.04779"); + MetadatumDTO author1 = createMetadatumDTO("dc", "contributor", "author", "Bayer, Jonas"); + MetadatumDTO author2 = createMetadatumDTO("dc", "contributor", "author", "Benzmüller, Christoph"); + MetadatumDTO author3 = createMetadatumDTO("dc", "contributor", "author", "Buzzard, Kevin"); + MetadatumDTO author4 = createMetadatumDTO("dc", "contributor", "author", "David, Marco"); + MetadatumDTO author5 = createMetadatumDTO("dc", "contributor", "author", "Lamport, Leslie"); + MetadatumDTO author6 = createMetadatumDTO("dc", "contributor", "author", "Matiyasevich, Yuri"); + MetadatumDTO author7 = createMetadatumDTO("dc", "contributor", "author", "Paulson, Lawrence"); + MetadatumDTO author8 = createMetadatumDTO("dc", "contributor", "author", "Schleicher, Dierk"); + MetadatumDTO author9 = createMetadatumDTO("dc", "contributor", "author", "Stock, Benedikt"); + MetadatumDTO author10 = createMetadatumDTO("dc", "contributor", "author", "Zelmanov, Efim"); + metadatums.add(title); + metadatums.add(doi); + metadatums.add(author1); + metadatums.add(author2); + metadatums.add(author3); + metadatums.add(author4); + metadatums.add(author5); + metadatums.add(author6); + metadatums.add(author7); + metadatums.add(author8); + metadatums.add(author9); + metadatums.add(author10); + + ImportRecord firstRecord = new ImportRecord(metadatums); + + records.add(firstRecord); + return records; + } + +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java index 4f366b0944..a115c8aa2f 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java @@ -9,11 +9,13 @@ package org.dspace.app.rest; import static com.google.common.net.UrlEscapers.urlPathSegmentEscaper; import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; +import static org.dspace.app.rest.matcher.FacetValueMatcher.entrySupervisedBy; import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.emptyOrNullString; +import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; @@ -48,6 +50,7 @@ import org.dspace.builder.EPersonBuilder; import org.dspace.builder.GroupBuilder; import org.dspace.builder.ItemBuilder; import org.dspace.builder.PoolTaskBuilder; +import org.dspace.builder.SupervisionOrderBuilder; import org.dspace.builder.WorkflowItemBuilder; import org.dspace.builder.WorkspaceItemBuilder; import org.dspace.content.Bitstream; @@ -63,6 +66,7 @@ import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; +import org.dspace.supervision.SupervisionOrder; import org.dspace.xmlworkflow.storedcomponents.ClaimedTask; import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; import org.hamcrest.Matchers; @@ -360,6 +364,237 @@ public class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest ; } + @Test + public void discoverFacetsAuthorTestWithPrefix_Capital_And_Special_Chars() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection").build(); + + Item publicItem1 = ItemBuilder.createItem(context, collection) + .withTitle("Public item 1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, John") + .withAuthor("Jan, Doe") + .build(); + + Item publicItem2 = ItemBuilder.createItem(context, collection) + .withTitle("Public item 2") + .withIssueDate("2016-02-13") + .withAuthor("S’Idan, Mo") + .withAuthor("Tick&Tock") + .build(); + + Item publicItem3 = ItemBuilder.createItem(context, collection) + .withTitle("Public item 3") + .withIssueDate("2016-02-13") + .withAuthor("M Akai") + .withAuthor("stIjn, SmITH") + .build(); + + Item publicItem4 = ItemBuilder.createItem(context, collection) + .withTitle("Public item 4") + .withIssueDate("2012-05-13") + .withSubject("St Augustine") + .build(); + + Item publicItem5 = ItemBuilder.createItem(context, collection) + .withTitle("Public item 5") + .withIssueDate("2015-11-23") + .withSubject("Health & Medicine") + .build(); + + Item publicItem6 = ItemBuilder.createItem(context, collection) + .withTitle("Public item 6") + .withIssueDate("2003-07-11") + .withSubject("1% economy") + .build(); + + Item publicItem7 = ItemBuilder.createItem(context, collection) + .withTitle("Public item 7") + .withIssueDate("2008-12-31") + .withSubject("I.T.") + .build(); + + Item publicItem8 = ItemBuilder.createItem(context, collection) + .withTitle("Public item 8") + .withIssueDate("2013-07-21") + .withSubject("?Unknown") + .build(); + + context.restoreAuthSystemState(); + + // The prefix query for author queries should be case-insensitive and correctly handle special characters + + getClient().perform(get("/api/discover/facets/author") + .param("prefix", "Smith")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.values", containsInAnyOrder( + FacetValueMatcher.entryFacetWithoutSelfLink("Smith, John"), + FacetValueMatcher.entryFacetWithoutSelfLink("stIjn, SmITH")))); + + getClient().perform(get("/api/discover/facets/author") + .param("prefix", "S")) + .andExpect(jsonPath("$._embedded.values", + containsInAnyOrder(FacetValueMatcher + .entryFacetWithoutSelfLink("Smith, John"), + FacetValueMatcher + .entryFacetWithoutSelfLink("S’Idan, Mo"), + // gets returned once for smith, once for stijn + FacetValueMatcher + .entryFacetWithoutSelfLink("stIjn, SmITH"), + FacetValueMatcher + .entryFacetWithoutSelfLink("stIjn, SmITH")))); + + getClient().perform(get("/api/discover/facets/author") + .param("prefix", "M A")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.values", + containsInAnyOrder(FacetValueMatcher.entryFacetWithoutSelfLink("M Akai")))); + + getClient().perform(get("/api/discover/facets/author") + .param("prefix", "S’I")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.values", + containsInAnyOrder(FacetValueMatcher.entryFacetWithoutSelfLink("S’Idan, Mo")))); + + getClient().perform(get("/api/discover/facets/author") + .param("prefix", "Jan, D")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.values", + containsInAnyOrder(FacetValueMatcher.entryFacetWithoutSelfLink("Jan, Doe")))); + + getClient().perform(get("/api/discover/facets/author") + .param("prefix", "Tick&")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.values", + containsInAnyOrder(FacetValueMatcher.entryFacetWithoutSelfLink("Tick&Tock")))); + + // Should also be the case for subject queries + + getClient().perform(get("/api/discover/facets/subject") + .param("prefix", "St A")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.values", + containsInAnyOrder(FacetValueMatcher + .entryFacetWithoutSelfLink("St Augustine")))); + + getClient().perform(get("/api/discover/facets/subject") + .param("prefix", "Health & M")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.values", + containsInAnyOrder(FacetValueMatcher + .entryFacetWithoutSelfLink("Health & Medicine")))); + + getClient().perform(get("/api/discover/facets/subject") + .param("prefix", "1% e")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.values", + containsInAnyOrder(FacetValueMatcher.entryFacetWithoutSelfLink("1% economy")))); + + getClient().perform(get("/api/discover/facets/subject") + .param("prefix", "I.")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.values", + containsInAnyOrder(FacetValueMatcher.entryFacetWithoutSelfLink("I.T.")))); + + getClient().perform(get("/api/discover/facets/subject") + .param("prefix", "U")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.values", + containsInAnyOrder(FacetValueMatcher.entryFacetWithoutSelfLink("?Unknown")))); + } + + @Test + public void discoverFacetsAuthorTestWithPrefixFirstName() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community").build(); + + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Parent Collection").build(); + + Item item1 = ItemBuilder.createItem(context, collection) + .withTitle("Item 1") + .withAuthor("Smith, John") + .build(); + + Item item2 = ItemBuilder.createItem(context, collection) + .withTitle("Item 2") + .withAuthor("Smith, Jane") + .build(); + + context.restoreAuthSystemState(); + + getClient().perform(get("/api/discover/facets/author") + .param("prefix", "john")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.values", + containsInAnyOrder( + FacetValueMatcher.entryAuthor("Smith, John")))); + + getClient().perform(get("/api/discover/facets/author") + .param("prefix", "jane")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.values", + containsInAnyOrder( + FacetValueMatcher.entryAuthor("Smith, Jane")))); + + getClient().perform(get("/api/discover/facets/author") + .param("prefix", "j")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.values", + containsInAnyOrder( + FacetValueMatcher.entryAuthor("Smith, John"), + FacetValueMatcher.entryAuthor("Smith, Jane")))); + } + + @Test + public void discoverFacetsAuthorWithAuthorityTestWithPrefixFirstName() throws Exception { + configurationService.setProperty("choices.plugin.dc.contributor.author", "SolrAuthorAuthority"); + configurationService.setProperty("authority.controlled.dc.contributor.author", "true"); + + metadataAuthorityService.clearCache(); + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community").build(); + + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Parent Collection").build(); + + Item item1 = ItemBuilder.createItem(context, collection) + .withTitle("Item 1") + .withAuthor("Smith, John", "test_authority_1", Choices.CF_ACCEPTED) + .build(); + + Item item2 = ItemBuilder.createItem(context, collection) + .withTitle("Item 2") + .withAuthor("Smith, Jane", "test_authority_2", Choices.CF_ACCEPTED) + .build(); + + context.restoreAuthSystemState(); + + getClient().perform(get("/api/discover/facets/author") + .param("prefix", "j")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.values", + containsInAnyOrder( + FacetValueMatcher.entryAuthorWithAuthority( + "Smith, John", "test_authority_1", 1), + FacetValueMatcher.entryAuthorWithAuthority( + "Smith, Jane", "test_authority_2", 1)))); + + DSpaceServicesFactory.getInstance().getConfigurationService().reloadConfig(); + + metadataAuthorityService.clearCache(); + } + @Test public void discoverFacetsAuthorTestForHasMoreFalse() throws Exception { //Turn of the authorization system so that we can create the structure specified below @@ -5847,4 +6082,727 @@ public class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest .andExpect(jsonPath("$._embedded.values").value(Matchers.hasSize(1))); } + + @Test + public void discoverFacetsSubjectTestWithCapitalAndSpecialChars() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community").build(); + + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Parent Collection").build(); + + Item item1 = ItemBuilder.createItem(context, collection) + .withTitle("Item 1") + .withSubject("Value with: Multiple Words ") + .build(); + + Item item2 = ItemBuilder.createItem(context, collection) + .withTitle("Item 2") + .withSubject("Multiple worded subject ") + .build(); + + Item item3 = ItemBuilder.createItem(context, collection) + .withTitle("Item 3") + .withSubject("Subject with a lot of Word values") + .build(); + + Item item4 = ItemBuilder.createItem(context, collection) + .withTitle("Item 4") + .withSubject("With, Values") + .build(); + + Item item5 = ItemBuilder.createItem(context, collection) + .withTitle("Item 5") + .withSubject("Test:of:the:colon") + .build(); + + Item item6 = ItemBuilder.createItem(context, collection) + .withTitle("Item 6") + .withSubject("Test,of,comma") + .build(); + + Item item7 = ItemBuilder.createItem(context, collection) + .withTitle("Item 7") + .withSubject("N’guyen") + .build(); + + Item item8 = ItemBuilder.createItem(context, collection) + .withTitle("Item 8") + .withSubject("test;Semicolon") + .build(); + + Item item9 = ItemBuilder.createItem(context, collection) + .withTitle("Item 9") + .withSubject("test||of|Pipe") + .build(); + + Item item10 = ItemBuilder.createItem(context, collection) + .withTitle("Item 10") + .withSubject("Test-Subject") + .build(); + + context.restoreAuthSystemState(); + + getClient().perform(get("/api/discover/facets/subject") + .param("prefix", "with a lot of word")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.values", containsInAnyOrder( + FacetValueMatcher.entrySubject("Subject with a lot of Word values", 1)))); + + getClient().perform(get("/api/discover/facets/subject") + .param("prefix", "multiple words")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.values", containsInAnyOrder( + FacetValueMatcher.entrySubject("Value with: Multiple Words", 1)))); + + getClient().perform(get("/api/discover/facets/subject") + .param("prefix", "mUltiPle wor")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.values", containsInAnyOrder( + FacetValueMatcher.entrySubject("Multiple worded subject", 1), + FacetValueMatcher.entrySubject("Value with: Multiple Words", 1)))); + + getClient().perform(get("/api/discover/facets/subject") + .param("prefix", "with")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.values", containsInAnyOrder( + FacetValueMatcher.entrySubject("With, Values", 1), + FacetValueMatcher.entrySubject("Subject with a lot of Word values", 1), + FacetValueMatcher.entrySubject("Value with: Multiple Words", 1)))); + + getClient().perform(get("/api/discover/facets/subject") + .param("prefix", "of")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.values", containsInAnyOrder( + FacetValueMatcher.entrySubject("Subject with a lot of Word values", 1), + FacetValueMatcher.entrySubject("Test,of,comma", 1), + FacetValueMatcher.entrySubject("Test:of:the:colon", 1), + FacetValueMatcher.entrySubject("test||of|Pipe", 1)))); + + getClient().perform(get("/api/discover/facets/subject") + .param("prefix", "tEsT")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.values", containsInAnyOrder( + FacetValueMatcher.entrySubject("Test,of,comma", 1), + FacetValueMatcher.entrySubject("Test-Subject", 1), + FacetValueMatcher.entrySubject("Test:of:the:colon", 1), + FacetValueMatcher.entrySubject("test;Semicolon", 1), + FacetValueMatcher.entrySubject("test||of|Pipe", 1)))); + + getClient().perform(get("/api/discover/facets/subject") + .param("prefix", "colon")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.values", containsInAnyOrder( + FacetValueMatcher.entrySubject("Test:of:the:colon", 1)))); + + getClient().perform(get("/api/discover/facets/subject") + .param("prefix", "coMma")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.values", containsInAnyOrder( + FacetValueMatcher.entrySubject("Test,of,comma", 1)))); + + getClient().perform(get("/api/discover/facets/subject") + .param("prefix", "guyen")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.values", containsInAnyOrder( + FacetValueMatcher.entrySubject("N’guyen", 1)))); + + getClient().perform(get("/api/discover/facets/subject") + .param("prefix", "semiColon")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.values", containsInAnyOrder( + FacetValueMatcher.entrySubject("test;Semicolon", 1)))); + + getClient().perform(get("/api/discover/facets/subject") + .param("prefix", "pipe")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.values", containsInAnyOrder( + FacetValueMatcher.entrySubject("test||of|Pipe", 1)))); + + getClient().perform(get("/api/discover/facets/subject") + .param("prefix", "Subject")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.values", containsInAnyOrder( + FacetValueMatcher.entrySubject("Multiple worded subject", 1), + FacetValueMatcher.entrySubject("Test-Subject", 1), + FacetValueMatcher.entrySubject("Subject with a lot of Word values", 1)))); + + getClient().perform(get("/api/discover/facets/subject") + .param("prefix", "Subject of word")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.values").isEmpty()) + .andExpect(jsonPath("$.page.number", is(0))); + + getClient().perform(get("/api/discover/facets/subject") + .param("prefix", "Value with words")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.values").isEmpty()) + .andExpect(jsonPath("$.page.number", is(0))); + } + + @Test + public void discoverFacetsSubjectWithAuthorityTest() throws Exception { + configurationService.setProperty("choices.plugin.dc.subject", "SolrSubjectAuthority"); + configurationService.setProperty("authority.controlled.dc.subject", "true"); + + metadataAuthorityService.clearCache(); + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community").build(); + + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Parent Collection").build(); + + Item item1 = ItemBuilder.createItem(context, collection) + .withTitle("Item 1") + .withSubject("Value with: Multiple Words", + "test_authority_1", Choices.CF_ACCEPTED) + .build(); + + Item item2 = ItemBuilder.createItem(context, collection) + .withTitle("Item 2") + .withSubject("Multiple worded subject ", + "test_authority_2", Choices.CF_ACCEPTED) + .build(); + + Item item3 = ItemBuilder.createItem(context, collection) + .withTitle("Item 3") + .withSubject("Subject with a lot of Word values", + "test_authority_3", Choices.CF_ACCEPTED) + .build(); + + context.restoreAuthSystemState(); + + getClient().perform(get("/api/discover/facets/subject") + .param("prefix", "with a lot of word")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.values", containsInAnyOrder( + FacetValueMatcher.entrySubjectWithAuthority("Subject with a lot of Word values", + "test_authority_3", 1)))); + + getClient().perform(get("/api/discover/facets/subject") + .param("prefix", "mUltiPle wor")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.values", containsInAnyOrder( + FacetValueMatcher.entrySubjectWithAuthority("Multiple worded subject", + "test_authority_2", 1), + FacetValueMatcher.entrySubjectWithAuthority("Value with: Multiple Words", + "test_authority_1", 1)))); + + getClient().perform(get("/api/discover/facets/subject") + .param("prefix", "Subject")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.values", containsInAnyOrder( + FacetValueMatcher.entrySubjectWithAuthority("Multiple worded subject", + "test_authority_2", 1), + FacetValueMatcher.entrySubjectWithAuthority("Subject with a lot of Word values", + "test_authority_3", 1)))); + + getClient().perform(get("/api/discover/facets/subject") + .param("prefix", "Subject of word")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.values").isEmpty()) + .andExpect(jsonPath("$.page.number", is(0))); + + getClient().perform(get("/api/discover/facets/subject") + .param("prefix", "Value with words")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.values").isEmpty()) + .andExpect(jsonPath("$.page.number", is(0))); + + DSpaceServicesFactory.getInstance().getConfigurationService().reloadConfig(); + + metadataAuthorityService.clearCache(); + } + + @Test + public void discoverFacetsSupervisedByTest() throws Exception { + //We turn off the authorization system in order to create the structure defined below + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community-collection structure with one parent community and one collection + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection collection = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + //2. Two workspace items + WorkspaceItem wsItem1 = + WorkspaceItemBuilder.createWorkspaceItem(context, collection) + .withTitle("Workspace Item 1") + .withIssueDate("2010-07-23") + .build(); + + WorkspaceItem wsItem2 = + WorkspaceItemBuilder.createWorkspaceItem(context, collection) + .withTitle("Workspace Item 2") + .withIssueDate("2010-11-03") + .build(); + + //3. Two groups + Group groupA = + GroupBuilder.createGroup(context) + .withName("group A") + .addMember(eperson) + .build(); + + Group groupB = + GroupBuilder.createGroup(context) + .withName("group B") + .addMember(eperson) + .build(); + + //4. Four supervision orders + SupervisionOrder supervisionOrderOne = + SupervisionOrderBuilder.createSupervisionOrder(context, wsItem1.getItem(), groupA).build(); + + SupervisionOrder supervisionOrderTwo = + SupervisionOrderBuilder.createSupervisionOrder(context, wsItem1.getItem(), groupB).build(); + + SupervisionOrder supervisionOrderThree = + SupervisionOrderBuilder.createSupervisionOrder(context, wsItem2.getItem(), groupA).build(); + + SupervisionOrder supervisionOrderFour = + SupervisionOrderBuilder.createSupervisionOrder(context, wsItem2.getItem(), groupB).build(); + + context.restoreAuthSystemState(); + + //** WHEN ** + //The Admin user browses this endpoint to find the supervisedBy results by the facet + getClient(getAuthToken(admin.getEmail(), password)).perform(get("/api/discover/facets/supervisedBy") + .param("configuration", "supervision")) + //** THEN ** + //The status has to be 200 OK + .andExpect(status().isOk()) + //The type has to be 'discover' + .andExpect(jsonPath("$.type", is("discover"))) + //The name has to be 'supervisedBy' as that's the facet that we've called + .andExpect(jsonPath("$.name", is("supervisedBy"))) + //The facetType has to be `authority` because that's the default configuration for this facet + .andExpect(jsonPath("$.facetType", equalTo("authority"))) + //There always needs to be a self link available + .andExpect(jsonPath("$._links.self.href", + containsString("api/discover/facets/supervisedBy?configuration=supervision"))) + //This is how the page object must look like because it's the default with size 20 + .andExpect(jsonPath("$.page", + is(PageMatcher.pageEntry(0, 20)))) + //The supervisedBy values need to be as specified below + .andExpect(jsonPath("$._embedded.values", containsInAnyOrder( + entrySupervisedBy(groupA.getName(), groupA.getID().toString(), 2), + entrySupervisedBy(groupB.getName(), groupB.getID().toString(), 2) + ))); + } + + @Test + public void discoverFacetsSupervisedByWithPrefixTest() throws Exception { + //We turn off the authorization system in order to create the structure defined below + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community-collection structure with one parent community and one collection + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection collection = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + //2. Two workspace items + WorkspaceItem wsItem1 = + WorkspaceItemBuilder.createWorkspaceItem(context, collection) + .withTitle("Workspace Item 1") + .withIssueDate("2010-07-23") + .build(); + + WorkspaceItem wsItem2 = + WorkspaceItemBuilder.createWorkspaceItem(context, collection) + .withTitle("Workspace Item 2") + .withIssueDate("2010-11-03") + .build(); + + Group groupA = + GroupBuilder.createGroup(context) + .withName("group A") + .addMember(eperson) + .build(); + + Group groupB = + GroupBuilder.createGroup(context) + .withName("group B") + .addMember(eperson) + .build(); + + SupervisionOrder supervisionOrderOneA = + SupervisionOrderBuilder.createSupervisionOrder(context, wsItem1.getItem(), groupA).build(); + + SupervisionOrder supervisionOrderOneB = + SupervisionOrderBuilder.createSupervisionOrder(context, wsItem1.getItem(), groupB).build(); + + SupervisionOrder supervisionOrderTwoA = + SupervisionOrderBuilder.createSupervisionOrder(context, wsItem2.getItem(), groupA).build(); + + SupervisionOrder supervisionOrderTwoB = + SupervisionOrderBuilder.createSupervisionOrder(context, wsItem2.getItem(), groupB).build(); + + context.restoreAuthSystemState(); + + //** WHEN ** + //The Admin user browses this endpoint to find the supervisedBy results by the facet + getClient(getAuthToken(admin.getEmail(), password)).perform(get("/api/discover/facets/supervisedBy") + .param("configuration", "supervision") + .param("prefix", "group B")) + //** THEN ** + //The status has to be 200 OK + .andExpect(status().isOk()) + //The type has to be 'discover' + .andExpect(jsonPath("$.type", is("discover"))) + //The name has to be 'supervisedBy' as that's the facet that we've called + .andExpect(jsonPath("$.name", is("supervisedBy"))) + //The facetType has to be `authority` because that's the default configuration for this facet + .andExpect(jsonPath("$.facetType", equalTo("authority"))) + //There always needs to be a self link available + .andExpect(jsonPath("$._links.self.href", + containsString("api/discover/facets/supervisedBy?prefix=group%20B&configuration=supervision"))) + //This is how the page object must look like because it's the default with size 20 + .andExpect(jsonPath("$.page", + is(PageMatcher.pageEntry(0, 20)))) + //The supervisedBy values need to be as specified below + .andExpect(jsonPath("$._embedded.values", containsInAnyOrder( + entrySupervisedBy(groupB.getName(), groupB.getID().toString(), 2) + ))); + } + + @Test + /** + * This test is intent to verify that tasks are only visible to the admin users + * + * @throws Exception + */ + public void discoverSearchObjectsSupervisionConfigurationTest() throws Exception { + + //We turn off the authorization system in order to create the structure defined below + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + // 1. Two reviewers and two users and two groups + EPerson reviewer1 = + EPersonBuilder.createEPerson(context) + .withEmail("reviewer1@example.com") + .withPassword(password) + .build(); + + EPerson reviewer2 = + EPersonBuilder.createEPerson(context) + .withEmail("reviewer2@example.com") + .withPassword(password) + .build(); + + EPerson userA = + EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withEmail("userA@test.com") + .withPassword(password) + .build(); + + EPerson userB = + EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withEmail("userB@test.com") + .withPassword(password) + .build(); + + Group groupA = + GroupBuilder.createGroup(context) + .withName("group A") + .addMember(userA) + .build(); + + Group groupB = + GroupBuilder.createGroup(context) + .withName("group B") + .addMember(userB) + .build(); + + // 2. A community-collection structure with one parent community with sub-community and two collections. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, child1) + .withName("Collection 1") + .build(); + + // the second collection has two workflow steps active + Collection col2 = CollectionBuilder.createCollection(context, child1) + .withName("Collection 2") + .withWorkflowGroup(1, admin, reviewer1) + .withWorkflowGroup(2, reviewer2) + .build(); + + // 2. Three public items that are readable by Anonymous with different subjects + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Test") + .withIssueDate("2010-10-17") + .withAuthor("Smith, Donald") + .withAuthor("Testing, Works") + .withSubject("ExtraEntry") + .build(); + + Item publicItem2 = ItemBuilder.createItem(context, col2) + .withTitle("Test 2") + .withIssueDate("1990-02-13") + .withAuthor("Smith, Maria") + .withAuthor("Doe, Jane") + .withAuthor("Testing, Works") + .withSubject("TestingForMore") + .withSubject("ExtraEntry") + .build(); + + Item publicItem3 = ItemBuilder.createItem(context, col2) + .withTitle("Public item 2") + .withIssueDate("2010-02-13") + .withAuthor("Smith, Maria") + .withAuthor("Doe, Jane") + .withAuthor("test,test") + .withAuthor("test2, test2") + .withAuthor("Maybe, Maybe") + .withSubject("AnotherTest") + .withSubject("TestingForMore") + .withSubject("ExtraEntry") + .build(); + + //3. three inprogress submission from a normal user (2 ws, 1 wf that will produce also a pooltask) + context.setCurrentUser(eperson); + WorkspaceItem wsItem1 = WorkspaceItemBuilder.createWorkspaceItem(context, col1) + .withTitle("Workspace Item 1") + .withIssueDate("2010-07-23") + .build(); + + WorkspaceItem wsItem2 = WorkspaceItemBuilder.createWorkspaceItem(context, col2) + .withTitle("Workspace Item 2") + .withIssueDate("2010-11-03") + .build(); + + XmlWorkflowItem wfItem1 = WorkflowItemBuilder.createWorkflowItem(context, col2) + .withTitle("Workflow Item 1") + .withIssueDate("2010-11-03") + .build(); + + // create Four supervision orders for above items + SupervisionOrderBuilder.createSupervisionOrder(context, wsItem1.getItem(), groupA).build(); + + SupervisionOrderBuilder.createSupervisionOrder(context, wsItem2.getItem(), groupA).build(); + + SupervisionOrderBuilder.createSupervisionOrder(context, wfItem1.getItem(), groupA).build(); + SupervisionOrderBuilder.createSupervisionOrder(context, wfItem1.getItem(), groupB).build(); + + // 4. a claimed task from the administrator + ClaimedTask cTask = ClaimedTaskBuilder.createClaimedTask(context, col2, admin).withTitle("Claimed Item") + .withIssueDate("2010-11-03") + .build(); + + // 5. other inprogress submissions made by the administrator + context.setCurrentUser(admin); + WorkspaceItem wsItem1Admin = WorkspaceItemBuilder.createWorkspaceItem(context, col1) + .withIssueDate("2010-07-23") + .withTitle("Admin Workspace Item 1").build(); + + WorkspaceItem wsItem2Admin = WorkspaceItemBuilder.createWorkspaceItem(context, col2) + .withIssueDate("2010-11-03") + .withTitle("Admin Workspace Item 2").build(); + + XmlWorkflowItem wfItem1Admin = WorkflowItemBuilder.createWorkflowItem(context, col2) + .withIssueDate("2010-11-03") + .withTitle("Admin Workflow Item 1").build(); + + // create Four supervision orders for above items + SupervisionOrderBuilder.createSupervisionOrder(context, wsItem1Admin.getItem(), groupA).build(); + + SupervisionOrderBuilder.createSupervisionOrder(context, wsItem2Admin.getItem(), groupA).build(); + + SupervisionOrderBuilder.createSupervisionOrder(context, wfItem1Admin.getItem(), groupA).build(); + SupervisionOrderBuilder.createSupervisionOrder(context, wfItem1Admin.getItem(), groupB).build(); + + // 6. a pool taks in the second step of the workflow + ClaimedTask cTask2 = ClaimedTaskBuilder.createClaimedTask(context, col2, admin).withTitle("Pool Step2 Item") + .withIssueDate("2010-11-04") + .build(); + + String epersonToken = getAuthToken(eperson.getEmail(), password); + String adminToken = getAuthToken(admin.getEmail(), password); + String reviewer1Token = getAuthToken(reviewer1.getEmail(), password); + String reviewer2Token = getAuthToken(reviewer2.getEmail(), password); + + getClient(adminToken).perform(post("/api/workflow/claimedtasks/" + cTask2.getID()) + .param("submit_approve", "true") + .contentType(MediaType.APPLICATION_FORM_URLENCODED)) + .andExpect(status().isNoContent()); + + context.restoreAuthSystemState(); + + // summary of the structure, we have: + // a simple collection + // a second collection with 2 workflow steps that have 1 reviewer each (reviewer1 and reviewer2) + // 3 public items + // 2 workspace items submitted by a regular submitter + // 2 workspace items submitted by the admin + // 4 workflow items: + // 1 pool task in step 1, submitted by the same regular submitter + // 1 pool task in step 1, submitted by the admin + // 1 claimed task in the first workflow step from the repository admin + // 1 pool task task in step 2, from the repository admin + // (This one is created by creating a claimed task for step 1 and approving it) + + //** WHEN ** + // the submitter should not see anything in the workflow configuration + getClient(epersonToken) + .perform(get("/api/discover/search/objects").param("configuration", "supervision")) + //** THEN ** + //The status has to be 200 OK + .andExpect(status().isOk()) + //The type has to be 'discover' + .andExpect(jsonPath("$.type", is("discover"))) + //There needs to be a page object that shows the total pages and total elements as well as the + // size and the current page (number) + .andExpect(jsonPath("$._embedded.searchResult.page", is( + PageMatcher.pageEntryWithTotalPagesAndElements(0, 20, 0, 0) + ))) + //There always needs to be a self link + .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))); + + // reviewer1 should not see pool items, as it is not an administrator + getClient(reviewer1Token) + .perform(get("/api/discover/search/objects").param("configuration", "supervision")) + //** THEN ** + //The status has to be 200 OK + .andExpect(status().isOk()) + //The type has to be 'discover' + .andExpect(jsonPath("$.type", is("discover"))) + //There needs to be a page object that shows the total pages and total elements as well as the + // size and the current page (number) + .andExpect(jsonPath("$._embedded.searchResult.page", is( + PageMatcher.pageEntryWithTotalPagesAndElements(0, 20, 0, 0) + ))) + //There always needs to be a self link + .andExpect(jsonPath("$._links.self.href", + containsString("/api/discover/search/objects"))); + + // admin should see seven pool items and a claimed task + // Three pool items from the submitter and Five from the admin + getClient(adminToken) + .perform(get("/api/discover/search/objects").param("configuration", "supervision")) + //** THEN ** + //The status has to be 200 OK + .andExpect(status().isOk()) + //The type has to be 'discover' + .andExpect(jsonPath("$.type", is("discover"))) + //There needs to be a page object that shows the total pages and total elements as well as the + // size and the current page (number) + .andExpect(jsonPath("$._embedded.searchResult.page", is( + PageMatcher.pageEntryWithTotalPagesAndElements(0, 20, 1, 8) + ))) + // These search results have to be shown in the embedded.objects section: + // three workflow items and one claimed task. + // For step 1 one submitted by the user and one submitted by the admin and one for step 2. + //Seeing as everything fits onto one page, they have to all be present + .andExpect(jsonPath("$._embedded.searchResult._embedded.objects", Matchers.containsInAnyOrder( + Matchers.allOf( + SearchResultMatcher.match("workflow", "workflowitem", "workflowitems"), + JsonPathMatchers.hasJsonPath("$._embedded.indexableObject", + is(WorkflowItemMatcher.matchItemWithTitleAndDateIssued( + null, "Workflow Item 1", "2010-11-03"))) + ), + Matchers.allOf( + SearchResultMatcher.match("workflow", "workflowitem", "workflowitems"), + JsonPathMatchers.hasJsonPath("$._embedded.indexableObject", + is(WorkflowItemMatcher.matchItemWithTitleAndDateIssued( + null, "Admin Workflow Item 1", "2010-11-03"))) + ), + Matchers.allOf( + SearchResultMatcher.match("workflow", "workflowitem", "workflowitems"), + JsonPathMatchers.hasJsonPath("$._embedded.indexableObject", + is(WorkflowItemMatcher.matchItemWithTitleAndDateIssued( + null, "Pool Step2 Item", "2010-11-04"))) + ), + Matchers.allOf( + SearchResultMatcher.match("workflow", "workflowitem", "workflowitems"), + JsonPathMatchers.hasJsonPath("$._embedded.indexableObject", + is(WorkflowItemMatcher.matchItemWithTitleAndDateIssued( + null, "Claimed Item", "2010-11-03"))) + ), + Matchers.allOf( + SearchResultMatcher.match("submission", "workspaceitem", "workspaceitems"), + JsonPathMatchers.hasJsonPath("$._embedded.indexableObject", + is(WorkspaceItemMatcher.matchItemWithTitleAndDateIssued(wsItem1, + "Workspace Item 1", "2010-07-23"))) + ), + Matchers.allOf( + SearchResultMatcher.match("submission", "workspaceitem", "workspaceitems"), + JsonPathMatchers.hasJsonPath("$._embedded.indexableObject", + is(WorkspaceItemMatcher.matchItemWithTitleAndDateIssued(wsItem2, + "Workspace Item 2", "2010-11-03"))) + ), + Matchers.allOf( + SearchResultMatcher.match("submission", "workspaceitem", "workspaceitems"), + JsonPathMatchers.hasJsonPath("$._embedded.indexableObject", + is(WorkspaceItemMatcher.matchItemWithTitleAndDateIssued(wsItem1Admin, + "Admin Workspace Item 1", "2010-07-23"))) + ), + Matchers.allOf( + SearchResultMatcher.match("submission", "workspaceitem", "workspaceitems"), + JsonPathMatchers.hasJsonPath("$._embedded.indexableObject", + is(WorkspaceItemMatcher.matchItemWithTitleAndDateIssued(wsItem2Admin, + "Admin Workspace Item 2", "2010-11-03"))) + ) + ))) + //These facets have to show up in the embedded.facets section as well with the given hasMore + //property because we don't exceed their default limit for a hasMore true (the default is 10) + .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( + FacetEntryMatcher.resourceTypeFacet(false), + FacetEntryMatcher.typeFacet(false), + FacetEntryMatcher.dateIssuedFacet(false), + FacetEntryMatcher.submitterFacet(false), + FacetEntryMatcher.supervisedByFacet(false) + ))) + //check supervisedBy Facet values + .andExpect(jsonPath("$._embedded.facets[4]._embedded.values", + contains( + entrySupervisedBy(groupA.getName(), groupA.getID().toString(), 6), + entrySupervisedBy(groupB.getName(), groupB.getID().toString(), 2) + ))) + //There always needs to be a self link + .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))); + + // reviewer2 should not see pool items, as it is not an administrator + getClient(reviewer2Token) + .perform(get("/api/discover/search/objects").param("configuration", "supervision")) + //** THEN ** + //The status has to be 200 OK + .andExpect(status().isOk()) + //The type has to be 'discover' + .andExpect(jsonPath("$.type", is("discover"))) + //There needs to be a page object that shows the total pages and total elements as well as the + // size and the current page (number) + .andExpect(jsonPath("$._embedded.searchResult.page", is( + PageMatcher.pageEntryWithTotalPagesAndElements(0, 20, 0, 0) + ))) + //There always needs to be a self link + .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))); + } + } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonAuthorityIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonAuthorityIT.java index 3b785bbfa0..d7921a2c86 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonAuthorityIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonAuthorityIT.java @@ -119,7 +119,7 @@ public class EPersonAuthorityIT extends AbstractControllerIntegrationTest { getClient().perform(get("/api/submission/vocabularies/EPersonAuthority/entries") .param("filter", "Luca")) - .andExpect(status().isUnauthorized()); + .andExpect(status().isOk()); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java index b4ddbfec2f..1f09779ab0 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java @@ -11,6 +11,8 @@ import static com.jayway.jsonpath.JsonPath.read; import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; import static org.dspace.app.rest.matcher.MetadataMatcher.matchMetadata; import static org.dspace.app.rest.matcher.MetadataMatcher.matchMetadataDoesNotExist; +import static org.dspace.app.rest.repository.RegistrationRestRepository.TYPE_QUERY_PARAM; +import static org.dspace.app.rest.repository.RegistrationRestRepository.TYPE_REGISTER; import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.empty; @@ -70,6 +72,7 @@ import org.dspace.builder.GroupBuilder; import org.dspace.builder.WorkflowItemBuilder; import org.dspace.content.Collection; import org.dspace.content.Community; +import org.dspace.content.Item; import org.dspace.core.I18nUtil; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; @@ -153,7 +156,7 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$", HalMatcher.matchNoEmbeds())) .andDo(result -> idRefNoEmbeds - .set(UUID.fromString(read(result.getResponse().getContentAsString(), "$.id"))));; + .set(UUID.fromString(read(result.getResponse().getContentAsString(), "$.id")))); } finally { EPersonBuilder.deleteEPerson(idRef.get()); @@ -1215,7 +1218,7 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { .content(patchBody) .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) .andExpect(status().isOk()) - .andExpect(jsonPath("$.canLogIn", Matchers.is(true)));; + .andExpect(jsonPath("$.canLogIn", Matchers.is(true))); List ops2 = new ArrayList(); @@ -1293,7 +1296,7 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { .content(patchBody) .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) .andExpect(status().isOk()) - .andExpect(jsonPath("$.requireCertificate", Matchers.is(true)));; + .andExpect(jsonPath("$.requireCertificate", Matchers.is(true))); List ops2 = new ArrayList(); ReplaceOperation replaceOperation2 = new ReplaceOperation("/certificate",null); @@ -1856,6 +1859,78 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { matchMetadata("eperson.firstname", newName))))); } + @Test + public void patchMultipleReplaceMetadataByAdmin() throws Exception { + + context.turnOffAuthorisationSystem(); + + String first = "First"; + String second = "Second"; + String third = "Third"; + + EPerson ePerson = EPersonBuilder.createEPerson(context) + .withEmail("Johndoe@example.com") + .build(); + + this.ePersonService + .addMetadata(context, ePerson, "eperson", "firstname", null, Item.ANY, List.of(first, second, third)); + + context.restoreAuthSystemState(); + + String token = getAuthToken(admin.getEmail(), password); + + // The replacement of the eperson.firstname value is persisted + getClient(token).perform(get("/api/eperson/epersons/" + ePerson.getID())) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$.metadata", + Matchers.allOf( + MetadataMatcher.matchMetadata("eperson.firstname", first, 0), + MetadataMatcher.matchMetadata("eperson.firstname", second, 1), + MetadataMatcher.matchMetadata("eperson.firstname", third, 2) + ) + ) + ); + + List ops = new ArrayList(); + + ReplaceOperation replaceFirst = new ReplaceOperation("/metadata/eperson.firstname/0", third); + ReplaceOperation replaceSecond = new ReplaceOperation("/metadata/eperson.firstname/1", second); + ReplaceOperation replaceThird = new ReplaceOperation("/metadata/eperson.firstname/2", first); + + ops.add(replaceFirst); + ops.add(replaceSecond); + ops.add(replaceThird); + + String patchBody = getPatchContent(ops); + + getClient(token).perform(patch("/api/eperson/epersons/" + ePerson.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$.metadata", + Matchers.allOf( + MetadataMatcher.matchMetadata("eperson.firstname", third, 0), + MetadataMatcher.matchMetadata("eperson.firstname", second, 1), + MetadataMatcher.matchMetadata("eperson.firstname", first, 2) + ) + ) + ); + + getClient(token).perform(get("/api/eperson/epersons/" + ePerson.getID())) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$.metadata", + Matchers.allOf( + MetadataMatcher.matchMetadata("eperson.firstname", third, 0), + MetadataMatcher.matchMetadata("eperson.firstname", second, 1), + MetadataMatcher.matchMetadata("eperson.firstname", first, 2) + ) + ) + ); + } + @Test public void patchOwnMetadataByNonAdminUser() throws Exception { @@ -2178,6 +2253,7 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { RegistrationRest registrationRest = new RegistrationRest(); registrationRest.setEmail(newRegisterEmail); getClient().perform(post("/api/eperson/registrations") + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .contentType(MediaType.APPLICATION_JSON) .content(mapper.writeValueAsBytes(registrationRest))) .andExpect(status().isCreated()); @@ -2226,6 +2302,7 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { RegistrationRest registrationRest = new RegistrationRest(); registrationRest.setEmail(newRegisterEmail); getClient().perform(post("/api/eperson/registrations") + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .contentType(MediaType.APPLICATION_JSON) .content(mapper.writeValueAsBytes(registrationRest))) .andExpect(status().isCreated()); @@ -2290,6 +2367,7 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { RegistrationRest registrationRest = new RegistrationRest(); registrationRest.setEmail(newRegisterEmail); getClient().perform(post("/api/eperson/registrations") + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .contentType(MediaType.APPLICATION_JSON) .content(mapper.writeValueAsBytes(registrationRest))) .andExpect(status().isCreated()); @@ -2352,6 +2430,7 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { RegistrationRest registrationRest = new RegistrationRest(); registrationRest.setEmail(newRegisterEmail); getClient().perform(post("/api/eperson/registrations") + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .contentType(MediaType.APPLICATION_JSON) .content(mapper.writeValueAsBytes(registrationRest))) .andExpect(status().isCreated()); @@ -2418,6 +2497,7 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { RegistrationRest registrationRest = new RegistrationRest(); registrationRest.setEmail(newRegisterEmail); getClient().perform(post("/api/eperson/registrations") + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .contentType(MediaType.APPLICATION_JSON) .content(mapper.writeValueAsBytes(registrationRest))) .andExpect(status().isCreated()); @@ -2427,6 +2507,7 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { RegistrationRest registrationRestTwo = new RegistrationRest(); registrationRestTwo.setEmail(newRegisterEmailTwo); getClient().perform(post("/api/eperson/registrations") + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .contentType(MediaType.APPLICATION_JSON) .content(mapper.writeValueAsBytes(registrationRestTwo))) .andExpect(status().isCreated()); @@ -2477,6 +2558,7 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { RegistrationRest registrationRest = new RegistrationRest(); registrationRest.setEmail(newRegisterEmail); getClient().perform(post("/api/eperson/registrations") + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .contentType(MediaType.APPLICATION_JSON) .content(mapper.writeValueAsBytes(registrationRest))) .andExpect(status().isCreated()); @@ -2525,6 +2607,7 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { RegistrationRest registrationRest = new RegistrationRest(); registrationRest.setEmail(newRegisterEmail); getClient().perform(post("/api/eperson/registrations") + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .contentType(MediaType.APPLICATION_JSON) .content(mapper.writeValueAsBytes(registrationRest))) .andExpect(status().isCreated()); @@ -2574,6 +2657,7 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { RegistrationRest registrationRest = new RegistrationRest(); registrationRest.setEmail(newRegisterEmail); getClient().perform(post("/api/eperson/registrations") + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .contentType(MediaType.APPLICATION_JSON) .content(mapper.writeValueAsBytes(registrationRest))) .andExpect(status().isCreated()); @@ -2641,6 +2725,7 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { RegistrationRest registrationRest = new RegistrationRest(); registrationRest.setEmail(newRegisterEmail); getClient().perform(post("/api/eperson/registrations") + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .contentType(MediaType.APPLICATION_JSON) .content(mapper.writeValueAsBytes(registrationRest))) .andExpect(status().isCreated()); @@ -2709,6 +2794,7 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { RegistrationRest registrationRest = new RegistrationRest(); registrationRest.setEmail(newRegisterEmail); getClient().perform(post("/api/eperson/registrations") + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .contentType(MediaType.APPLICATION_JSON) .content(mapper.writeValueAsBytes(registrationRest))) .andExpect(status().isCreated()); @@ -2756,6 +2842,7 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { RegistrationRest registrationRest = new RegistrationRest(); registrationRest.setEmail(eperson.getEmail()); getClient().perform(post("/api/eperson/registrations") + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .contentType(MediaType.APPLICATION_JSON) .content(mapper.writeValueAsBytes(registrationRest))) .andExpect(status().isCreated()); @@ -2805,6 +2892,7 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { RegistrationRest registrationRest = new RegistrationRest(); registrationRest.setEmail(newRegisterEmail); getClient().perform(post("/api/eperson/registrations") + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .contentType(MediaType.APPLICATION_JSON) .content(mapper.writeValueAsBytes(registrationRest))) .andExpect(status().isCreated()); @@ -3137,6 +3225,7 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { registrationRest.setEmail(newRegisterEmail); getClient().perform(post("/api/eperson/registrations") + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .contentType(MediaType.APPLICATION_JSON) .content(mapper.writeValueAsBytes(registrationRest))) .andExpect(status().isCreated()); @@ -3182,6 +3271,7 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { registrationRest.setEmail(newRegisterEmail); getClient().perform(post("/api/eperson/registrations") + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .contentType(MediaType.APPLICATION_JSON) .content(mapper.writeValueAsBytes(registrationRest))) .andExpect(status().isCreated()); @@ -3417,4 +3507,4 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ExternalSourcesRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ExternalSourcesRestControllerIT.java index cd2c2fe53d..65492cbbe8 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ExternalSourcesRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ExternalSourcesRestControllerIT.java @@ -53,7 +53,7 @@ public class ExternalSourcesRestControllerIT extends AbstractControllerIntegrati ExternalSourceMatcher.matchExternalSource( "openAIREFunding", "openAIREFunding", false) ))) - .andExpect(jsonPath("$.page.totalElements", Matchers.is(9))); + .andExpect(jsonPath("$.page.totalElements", Matchers.is(10))); } @Test @@ -153,23 +153,19 @@ public class ExternalSourcesRestControllerIT extends AbstractControllerIntegrati getClient().perform(get("/api/integration/externalsources/search/findByEntityType") .param("entityType", "Publication")) .andExpect(status().isOk()) - // Expect *at least* 3 Publication sources - .andExpect(jsonPath("$._embedded.externalsources", Matchers.hasItems( - ExternalSourceMatcher.matchExternalSource(publicationProviders.get(0)), - ExternalSourceMatcher.matchExternalSource(publicationProviders.get(1)) - ))) + // Expect that Publication sources match (check a max of 20 as that is default page size) + .andExpect(jsonPath("$._embedded.externalsources", + ExternalSourceMatcher.matchAllExternalSources(publicationProviders, 20) + )) .andExpect(jsonPath("$.page.totalElements", Matchers.is(publicationProviders.size()))); getClient().perform(get("/api/integration/externalsources/search/findByEntityType") .param("entityType", "Journal")) .andExpect(status().isOk()) - // Expect *at least* 5 Journal sources - .andExpect(jsonPath("$._embedded.externalsources", Matchers.hasItems( - ExternalSourceMatcher.matchExternalSource(journalProviders.get(0)), - ExternalSourceMatcher.matchExternalSource(journalProviders.get(1)), - ExternalSourceMatcher.matchExternalSource(journalProviders.get(2)), - ExternalSourceMatcher.matchExternalSource(journalProviders.get(3)) - ))) + // Check that Journal sources match (check a max of 20 as that is default page size) + .andExpect(jsonPath("$._embedded.externalsources", + ExternalSourceMatcher.matchAllExternalSources(journalProviders, 20) + )) .andExpect(jsonPath("$.page.totalElements", Matchers.is(journalProviders.size()))); } @@ -193,10 +189,9 @@ public class ExternalSourcesRestControllerIT extends AbstractControllerIntegrati .param("entityType", "Journal") .param("size", String.valueOf(pageSize))) .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.externalsources", Matchers.contains( - ExternalSourceMatcher.matchExternalSource(journalProviders.get(0)), - ExternalSourceMatcher.matchExternalSource(journalProviders.get(1)) - ))) + .andExpect(jsonPath("$._embedded.externalsources", + ExternalSourceMatcher.matchAllExternalSources(journalProviders, pageSize) + )) .andExpect(jsonPath("$.page.totalPages", Matchers.is(numberOfPages))) .andExpect(jsonPath("$.page.totalElements", Matchers.is(numJournalProviders))); @@ -205,10 +200,12 @@ public class ExternalSourcesRestControllerIT extends AbstractControllerIntegrati .param("page", "1") .param("size", String.valueOf(pageSize))) .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.externalsources", Matchers.contains( - ExternalSourceMatcher.matchExternalSource(journalProviders.get(2)), - ExternalSourceMatcher.matchExternalSource(journalProviders.get(3)) - ))) + // Check that second page has journal sources starting at index 2. + .andExpect(jsonPath("$._embedded.externalsources", + ExternalSourceMatcher.matchAllExternalSources( + journalProviders.subList(2, journalProviders.size()), + pageSize) + )) .andExpect(jsonPath("$.page.totalPages", Matchers.is(numberOfPages))) .andExpect(jsonPath("$.page.totalElements", Matchers.is(numJournalProviders))); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/GroupRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/GroupRestRepositoryIT.java index 7121e11953..fda8b15eff 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/GroupRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/GroupRestRepositoryIT.java @@ -40,6 +40,7 @@ import org.dspace.app.rest.exception.GroupNameNotProvidedException; import org.dspace.app.rest.matcher.EPersonMatcher; import org.dspace.app.rest.matcher.GroupMatcher; import org.dspace.app.rest.matcher.HalMatcher; +import org.dspace.app.rest.matcher.MetadataMatcher; import org.dspace.app.rest.model.GroupRest; import org.dspace.app.rest.model.MetadataRest; import org.dspace.app.rest.model.MetadataValueRest; @@ -56,6 +57,8 @@ import org.dspace.builder.GroupBuilder; import org.dspace.builder.ResourcePolicyBuilder; import org.dspace.content.Collection; import org.dspace.content.Community; +import org.dspace.content.Item; +import org.dspace.content.MetadataSchemaEnum; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.CollectionService; import org.dspace.content.service.CommunityService; @@ -558,6 +561,68 @@ public class GroupRestRepositoryIT extends AbstractControllerIntegrationTest { )); } + @Test + public void patchReplaceMultipleDescriptionGroupName() throws Exception { + context.turnOffAuthorisationSystem(); + List groupDescription = List.of( + "FIRST", + "SECOND", + "THIRD" + ); + + Group group = + GroupBuilder.createGroup(context) + .build(); + GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); + groupService + .addMetadata( + context, group, MetadataSchemaEnum.DC.getName(), "description", null, Item.ANY, groupDescription + ); + context.restoreAuthSystemState(); + String token = getAuthToken(admin.getEmail(), password); + + getClient(token) + .perform(get("/api/eperson/groups/" + group.getID())) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$.metadata", + Matchers.allOf( + MetadataMatcher.matchMetadata("dc.description", groupDescription.get(0), 0), + MetadataMatcher.matchMetadata("dc.description", groupDescription.get(1), 1), + MetadataMatcher.matchMetadata("dc.description", groupDescription.get(2), 2) + ) + ) + ); + + List ops = List.of( + new ReplaceOperation("/metadata/dc.description/0", groupDescription.get(2)), + new ReplaceOperation("/metadata/dc.description/1", groupDescription.get(0)), + new ReplaceOperation("/metadata/dc.description/2", groupDescription.get(1)) + ); + String requestBody = getPatchContent(ops); + + getClient(token) + .perform( + patch("/api/eperson/groups/" + group.getID()) + .content(requestBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON) + ) + .andExpect(status().isOk()); + + getClient(token) + .perform(get("/api/eperson/groups/" + group.getID())) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$.metadata", + Matchers.allOf( + MetadataMatcher.matchMetadata("dc.description", groupDescription.get(2), 0), + MetadataMatcher.matchMetadata("dc.description", groupDescription.get(0), 1), + MetadataMatcher.matchMetadata("dc.description", groupDescription.get(1), 2) + ) + ) + ); + } + @Test public void patchGroupWithParentUnprocessable() throws Exception { context.turnOffAuthorisationSystem(); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/IdentifierRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/IdentifierRestControllerIT.java deleted file mode 100644 index 9927d37286..0000000000 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/IdentifierRestControllerIT.java +++ /dev/null @@ -1,101 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.app.rest; - -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -import org.dspace.app.rest.test.AbstractControllerIntegrationTest; -import org.dspace.builder.CollectionBuilder; -import org.dspace.builder.CommunityBuilder; -import org.dspace.builder.ItemBuilder; -import org.dspace.content.Collection; -import org.dspace.content.Item; -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Test; - -/** - * Integration test for the identifier resolver - * - * @author Andrea Bollini (andrea.bollini at 4science.it) - */ -public class IdentifierRestControllerIT extends AbstractControllerIntegrationTest { - - @Before - public void setup() throws Exception { - super.setUp(); - } - - @Test - public void testValidIdentifier() throws Exception { - //We turn off the authorization system in order to create the structure as defined below - context.turnOffAuthorisationSystem(); - - // We create a top community to receive an identifier - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") - .build(); - - context.restoreAuthSystemState(); - - String handle = parentCommunity.getHandle(); - String communityDetail = REST_SERVER_URL + "core/communities/" + parentCommunity.getID(); - - getClient().perform(get("/api/pid/find?id={handle}",handle)) - .andExpect(status().isFound()) - //We expect a Location header to redirect to the community details - .andExpect(header().string("Location", communityDetail)); - } - - @Test - - public void testValidIdentifierItemHandlePrefix() throws Exception { - //We turn off the authorization system in order to create the structure as defined below - context.turnOffAuthorisationSystem(); - - // Create an item with a handle identifier - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") - .build(); - Collection owningCollection = CollectionBuilder.createCollection(context, parentCommunity) - .withName("Owning Collection") - .build(); - Item item = ItemBuilder.createItem(context, owningCollection) - .withTitle("Test item") - .build(); - - String handle = item.getHandle(); - String itemLocation = REST_SERVER_URL + "core/items/" + item.getID(); - - getClient().perform(get("/api/pid/find?id=hdl:{handle}", handle)) - .andExpect(status().isFound()) - // We expect a Location header to redirect to the item's page - .andExpect(header().string("Location", itemLocation)); - } - - @Test - public void testUnexistentIdentifier() throws Exception { - getClient().perform(get("/api/pid/find?id={id}","fakeIdentifier")) - .andExpect(status().isNotFound()); - } - - @Test - @Ignore - /** - * This test will check the return status code when no id is supplied. It currently fails as our - * RestResourceController take the precedence over the pid controller returning a 404 Repository not found - * - * @throws Exception - */ - public void testMissingIdentifierParameter() throws Exception { - getClient().perform(get("/api/pid/find")) - .andExpect(status().isUnprocessableEntity()); - } -} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/IdentifierRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/IdentifierRestRepositoryIT.java new file mode 100644 index 0000000000..27e21e4776 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/IdentifierRestRepositoryIT.java @@ -0,0 +1,334 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the 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.junit.Assert.assertNotNull; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.content.Collection; +import org.dspace.content.Item; +import org.dspace.handle.factory.HandleServiceFactory; +import org.dspace.handle.service.HandleService; +import org.dspace.identifier.DOI; +import org.dspace.identifier.DOIIdentifierProvider; +import org.dspace.identifier.factory.IdentifierServiceFactory; +import org.dspace.identifier.service.DOIService; +import org.dspace.services.factory.DSpaceServicesFactory; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.springframework.http.MediaType; + +/** + * Test getting and registering item identifiers + * + * @author Kim Shepherd + */ +public class IdentifierRestRepositoryIT extends AbstractControllerIntegrationTest { + @Before + public void setup() throws Exception { + super.setUp(); + } + + @Test + public void testValidIdentifier() throws Exception { + //We turn off the authorization system in order to create the structure as defined below + context.turnOffAuthorisationSystem(); + + // We create a top community to receive an identifier + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + context.restoreAuthSystemState(); + + String handle = parentCommunity.getHandle(); + String communityDetail = REST_SERVER_URL + "core/communities/" + parentCommunity.getID(); + + getClient().perform(get("/api/pid/find?id={handle}",handle)) + .andExpect(status().isFound()) + //We expect a Location header to redirect to the community details + .andExpect(header().string("Location", communityDetail)); + } + + @Test + + public void testValidIdentifierItemHandlePrefix() throws Exception { + //We turn off the authorization system in order to create the structure as defined below + context.turnOffAuthorisationSystem(); + + // Create an item with a handle identifier + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Collection owningCollection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Owning Collection") + .build(); + Item item = ItemBuilder.createItem(context, owningCollection) + .withTitle("Test item") + .build(); + + String handle = item.getHandle(); + String itemLocation = REST_SERVER_URL + "core/items/" + item.getID(); + + getClient().perform(get("/api/pid/find?id=hdl:{handle}", handle)) + .andExpect(status().isFound()) + // We expect a Location header to redirect to the item's page + .andExpect(header().string("Location", itemLocation)); + } + + @Test + public void testUnexistentIdentifier() throws Exception { + getClient().perform(get("/api/pid/find?id={id}","fakeIdentifier")) + .andExpect(status().isNotFound()); + } + + @Test + @Ignore + /** + * This test will check the return status code when no id is supplied. It currently fails as our + * RestResourceController take the precedence over the pid controller returning a 404 Repository not found + * + * @throws Exception + */ + public void testMissingIdentifierParameter() throws Exception { + getClient().perform(get("/api/pid/find")) + .andExpect(status().isUnprocessableEntity()); + } + + @Test + public void testRegisterDoiForItem() throws Exception { + //Turn off the authorization system, otherwise we can't make the objects + context.turnOffAuthorisationSystem(); + + DOIService doiService = IdentifierServiceFactory.getInstance().getDOIService(); + + //** GIVEN ** + //1. A community-collection structure with one parent community and two collections. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); + + //2. A public item that is readable by Anonymous + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Public item 1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald") + .build(); + + // This item should not have a DOI + DOIIdentifierProvider doiIdentifierProvider = + DSpaceServicesFactory.getInstance().getServiceManager() + .getServiceByName("org.dspace.identifier.DOIIdentifierProvider", + org.dspace.identifier.DOIIdentifierProvider.class); + doiIdentifierProvider.delete(context, publicItem1); + + // Body of POST to create an identifier for public item 1 + String uriList = "https://localhost:8080/server/api/core/items/" + publicItem1.getID(); + + // A non-admin should get an unauthorised error from REST method preauth + // Expect first forbidden + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(post("/api/pid/identifiers") + .queryParam("type", "doi") + .contentType(MediaType.parseMediaType("text/uri-list")) + .content(uriList)) + .andExpect(status().isForbidden()); + + // Set token to admin credentials + token = getAuthToken(admin.getEmail(), password); + + // Expect a successful 201 CREATED for this item with no DOI + getClient(token).perform(post("/api/pid/identifiers") + .queryParam("type", "doi") + .contentType(MediaType.parseMediaType("text/uri-list")) + .content(uriList)) + .andExpect(status().isCreated()); + + // Expected 400 BAD REQUEST status code for a DOI already in REGISTERED / TO_BE_REGISTERED state + getClient(token).perform(post("/api/pid/identifiers") + .queryParam("type", "doi") + .contentType(MediaType.parseMediaType("text/uri-list")) + .content(uriList)) + .andExpect(status().isBadRequest()); + + // Get the doi we minted and queued for registration + DOI doi = doiService.findDOIByDSpaceObject(context, publicItem1); + // The DOI should not be null + assertNotNull(doi); + // The DOI status should be TO_BE_REGISTERED + Assert.assertEquals(DOIIdentifierProvider.TO_BE_REGISTERED, doi.getStatus()); + + // Now, set the DOI status back to pending and update + doi.setStatus(DOIIdentifierProvider.PENDING); + doiService.update(context, doi); + + // Do another POST, again this should return 201 CREATED as we shift the DOI from PENDING to TO_BE_REGISTERED + getClient(token).perform(post("/api/pid/identifiers") + .queryParam("type", "doi") + .contentType(MediaType.parseMediaType("text/uri-list")) + .content(uriList)) + .andExpect(status().isCreated()); + + context.restoreAuthSystemState(); + } + + @Test + public void testGetIdentifiersForItemByLink() throws Exception { + //Turn off the authorization system, otherwise we can't make the objects + context.turnOffAuthorisationSystem(); + + DOIService doiService = IdentifierServiceFactory.getInstance().getDOIService(); + HandleService handleService = HandleServiceFactory.getInstance().getHandleService(); + + //1. A community-collection structure with one parent community and two collections. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); + + //2. A public item that is readable by Anonymous + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Public item 1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald") + .build(); + + String doiString = "10.5072/dspace-identifier-test-" + publicItem1.getID(); + + // Use the DOI service to directly manipulate the DOI on this object so that we can predict and + // test values via the REST request + DOI doi = doiService.findDOIByDSpaceObject(context, publicItem1); + + // Assert non-null DOI, since we should be minting them automatically here + assertNotNull(doi); + + // Set specific string and state we expect to get back from a REST request + doi.setDoi(doiString); + doi.setStatus(DOIIdentifierProvider.IS_REGISTERED); + doiService.update(context, doi); + + context.restoreAuthSystemState(); + + String token = getAuthToken(eperson.getEmail(), password); + + // Get identifiers for this item - we expect a 200 OK response and the type of the resource is plural + // "identifiers" + getClient(token).perform(get("/api/core/items/" + + publicItem1.getID().toString() + "/identifiers")) + .andExpect(status().isOk()).andExpect(jsonPath("$.type").value("identifiers")); + + // Expect an array of identifiers + getClient(token).perform(get("/api/core/items/" + + publicItem1.getID().toString() + "/identifiers")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.identifiers").isArray()); + + // Expect a valid DOI with the value, type and status we expect + getClient(token).perform(get("/api/core/items/" + + publicItem1.getID().toString() + "/identifiers")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.identifiers[0].type").value("identifier")) + .andExpect(jsonPath("$.identifiers[0].value").value(doiService.DOIToExternalForm(doiString))) + .andExpect(jsonPath("$.identifiers[0].identifierType").value("doi")) + .andExpect(jsonPath("$.identifiers[0].identifierStatus") + .value(DOIIdentifierProvider.statusText[DOIIdentifierProvider.IS_REGISTERED])); + + // Expect a valid Handle with the value, type we expect + getClient(token).perform(get("/api/core/items/" + + publicItem1.getID().toString() + "/identifiers")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.identifiers[1].type").value("identifier")) + .andExpect(jsonPath("$.identifiers[1].value") + .value(handleService.getCanonicalForm(publicItem1.getHandle()))) + .andExpect(jsonPath("$.identifiers[1].identifierType").value("handle")); + + } + + @Test + public void testFindIdentifiersByItem() throws Exception { + //Turn off the authorization system, otherwise we can't make the objects + context.turnOffAuthorisationSystem(); + + DOIService doiService = IdentifierServiceFactory.getInstance().getDOIService(); + HandleService handleService = HandleServiceFactory.getInstance().getHandleService(); + + //1. A community-collection structure with one parent community and two collections. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); + + //2. A public item that is readable by Anonymous + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Public item 1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald") + .build(); + + String doiString = "10.5072/dspace-identifier-test-" + publicItem1.getID(); + + // Use the DOI service to directly manipulate the DOI on this object so that we can predict and + // test values via the REST request + DOI doi = doiService.findDOIByDSpaceObject(context, publicItem1); + + // Assert non-null DOI, since we should be minting them automatically here + assertNotNull(doi); + + // Set specific string and state we expect to get back from a REST request + doi.setDoi(doiString); + doi.setStatus(DOIIdentifierProvider.IS_REGISTERED); + doiService.update(context, doi); + + context.restoreAuthSystemState(); + + String token = getAuthToken(eperson.getEmail(), password); + + // Get identifiers for this item - we expect a 200 OK response and the type of the resource is plural + // "identifiers" + getClient(token).perform(get("/api/pid/identifiers/search/findByItem").queryParam("uuid", + publicItem1.getID().toString())) + .andExpect(status().isOk()).andExpect(jsonPath("$._embedded.identifiers").exists()); + + // Expect an array of identifiers + getClient(token).perform(get("/api/pid/identifiers/search/findByItem").queryParam("uuid", + publicItem1.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.identifiers").isArray()); + + // Expect a valid DOI with the value, type and status we expect + getClient(token).perform(get("/api/pid/identifiers/search/findByItem").queryParam("uuid", + publicItem1.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.identifiers[0].type").value("identifier")) + .andExpect(jsonPath("$._embedded.identifiers[0].value").value(doiService.DOIToExternalForm(doiString))) + .andExpect(jsonPath("$._embedded.identifiers[0].identifierType").value("doi")) + .andExpect(jsonPath("$._embedded.identifiers[0].identifierStatus") + .value(DOIIdentifierProvider.statusText[DOIIdentifierProvider.IS_REGISTERED])); + + // Expect a valid Handle with the value, type we expect + getClient(token).perform(get("/api/pid/identifiers/search/findByItem").queryParam("uuid", + publicItem1.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.identifiers[1].type").value("identifier")) + .andExpect(jsonPath("$._embedded.identifiers[1].value") + .value(handleService.getCanonicalForm(publicItem1.getHandle()))) + .andExpect(jsonPath("$._embedded.identifiers[1].identifierType").value("handle")); + + } +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/MetadatafieldRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/MetadatafieldRestRepositoryIT.java index 56c8f637f1..1826cd0fbb 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/MetadatafieldRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/MetadatafieldRestRepositoryIT.java @@ -1052,7 +1052,8 @@ public class MetadatafieldRestRepositoryIT extends AbstractControllerIntegration // Metadata fields are returned alphabetically. // So, on the last page we'll just ensure it *at least* includes the last field alphabetically .andExpect(jsonPath("$._embedded.metadatafields", Matchers.hasItems( - MetadataFieldMatcher.matchMetadataFieldByKeys("workflow", "score", null) + MetadataFieldMatcher.matchMetadataField( + alphabeticMdFields.get(alphabeticMdFields.size() - 1)) ))) .andExpect(jsonPath("$._links.first.href", Matchers.allOf( Matchers.containsString("/api/core/metadatafields?"), diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/PatchMetadataIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/PatchMetadataIT.java index b78436f1fb..58781cf589 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/PatchMetadataIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/PatchMetadataIT.java @@ -11,8 +11,8 @@ import static com.jayway.jsonpath.JsonPath.read; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.CoreMatchers.startsWith; +import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; @@ -23,8 +23,11 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import java.io.IOException; import java.sql.SQLException; import java.util.ArrayList; +import java.util.Comparator; import java.util.List; +import java.util.Map; import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; import java.util.stream.IntStream; import org.dspace.app.rest.matcher.MetadataMatcher; @@ -50,6 +53,7 @@ import org.dspace.content.service.EntityTypeService; import org.dspace.content.service.ItemService; import org.dspace.content.service.RelationshipTypeService; import org.dspace.content.service.WorkspaceItemService; +import org.dspace.services.ConfigurationService; import org.hamcrest.Matcher; import org.hamcrest.Matchers; import org.junit.After; @@ -63,6 +67,13 @@ import org.springframework.http.MediaType; */ public class PatchMetadataIT extends AbstractEntityIntegrationTest { + private static final String SECTIONS_TRADITIONALPAGEONE_DC_CONTRIBUTOR_AUTHOR = + "/sections/traditionalpageone/dc.contributor.author/%1$s"; + + private static final String getPath(Object element) { + return String.format(SECTIONS_TRADITIONALPAGEONE_DC_CONTRIBUTOR_AUTHOR, element); + } + @Autowired private RelationshipTypeService relationshipTypeService; @@ -75,6 +86,9 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { @Autowired private WorkspaceItemService workspaceItemService; + @Autowired + private ConfigurationService configurationService; + private Collection collection; private Collection collection2; private WorkspaceItem publicationWorkspaceItem; @@ -297,8 +311,6 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { .withEntityType("Publication") .build(); - String adminToken = getAuthToken(admin.getEmail(), password); - // Make sure we grab the latest instance of the Item from the database before adding a regular author WorkspaceItem publication = workspaceItemService.find(context, publicationWorkspaceItem.getID()); itemService.addMetadata(context, publication.getItem(), @@ -920,6 +932,41 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { replaceTraditionalPageOneAuthorTest(3, expectedOrder); } + @Test + public void replaceMultipleTraditionalPageOnePlainTextAuthorTest() throws Exception { + final boolean virtualMetadataEnabled = + configurationService.getBooleanProperty("item.enable-virtual-metadata", false); + + configurationService.setProperty("item.enable-virtual-metadata", false); + try { + initPlainTextPublicationWorkspace(); + + Map replacedAuthors = + Map.of( + 0, authorsOriginalOrder.get(4), + 1, authorsOriginalOrder.get(1), + 2, authorsOriginalOrder.get(2), + 3, authorsOriginalOrder.get(3), + 4, authorsOriginalOrder.get(0) + ); + + List expectedOrder = + List.of( + authorsOriginalOrder.get(4), + authorsOriginalOrder.get(1), + authorsOriginalOrder.get(2), + authorsOriginalOrder.get(3), + authorsOriginalOrder.get(0) + ); + + replaceTraditionalPageMultipleAuthorsTest(replacedAuthors, expectedOrder); + } catch (Exception e) { + throw e; + } finally { + configurationService.setProperty("item.enable-virtual-metadata", virtualMetadataEnabled); + } + } + /** * This test will add an author (dc.contributor.author) within a workspace publication's "traditionalpageone" @@ -1393,24 +1440,7 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { ops.add(moveOperation); String patchBody = getPatchContent(ops); - String token = getAuthToken(admin.getEmail(), password); - - getClient(token).perform(patch("/api/submission/workspaceitems/" + publicationWorkspaceItem.getID()) - .content(patchBody) - .contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON)) - .andExpect(status().isOk()); - - String authorField = "dc.contributor.author"; - getClient(token).perform(get("/api/submission/workspaceitems/" + publicationWorkspaceItem.getID())) - .andExpect(status().isOk()) - .andExpect(content().contentType(contentType)) - .andExpect(jsonPath("$.sections.traditionalpageone", Matchers.allOf( - Matchers.is(MetadataMatcher.matchMetadata(authorField, expectedOrder.get(0), 0)), - Matchers.is(MetadataMatcher.matchMetadata(authorField, expectedOrder.get(1), 1)), - Matchers.is(MetadataMatcher.matchMetadata(authorField, expectedOrder.get(2), 2)), - Matchers.is(MetadataMatcher.matchMetadata(authorField, expectedOrder.get(3), 3)), - Matchers.is(MetadataMatcher.matchMetadata(authorField, expectedOrder.get(4), 4)) - ))); + assertReplacementOrder(expectedOrder, patchBody); } /** @@ -1450,33 +1480,66 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { * @param expectedOrder A list of author names sorted in the expected order */ private void replaceTraditionalPageOneAuthorTest(int path, List expectedOrder) throws Exception { - List ops = new ArrayList(); - MetadataValueRest value = new MetadataValueRest(); - value.setValue(replacedAuthor); + String patchBody = + getPatchContent( + List.of( + this.mapToReplaceOperation(path, replacedAuthor) + ) + ); + + assertReplacementOrder(expectedOrder, patchBody); + } + + private void replaceTraditionalPageMultipleAuthorsTest( + Map values, List expectedOrder + ) throws Exception { + List ops = + values + .entrySet() + .stream() + .sorted(Comparator.comparing(Map.Entry::getKey)) + .map(entry -> mapToReplaceOperation(entry.getKey(), entry.getValue())) + .collect(Collectors.toList()); - ReplaceOperation replaceOperation = new ReplaceOperation("/sections/traditionalpageone/dc.contributor.author/" - + path, value); - ops.add(replaceOperation); String patchBody = getPatchContent(ops); + assertReplacementOrder(expectedOrder, patchBody); + } + + private ReplaceOperation mapToReplaceOperation(int path, String author) { + return new ReplaceOperation(getPath(path), new MetadataValueRest(author)); + } + + private void assertReplacementOrder(List expectedOrder, String patchBody) throws Exception, SQLException { String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(patch("/api/submission/workspaceitems/" + publicationWorkspaceItem.getID()) - .content(patchBody) - .contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON)) - .andExpect(status().isOk()); + getClient(token) + .perform( + patch("/api/submission/workspaceitems/" + publicationWorkspaceItem.getID()) + .content(patchBody) + .contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON) + ) + .andExpect(status().isOk()); String authorField = "dc.contributor.author"; - getClient(token).perform(get("/api/submission/workspaceitems/" + publicationWorkspaceItem.getID())) - .andExpect(status().isOk()) - .andExpect(content().contentType(contentType)) - .andExpect(jsonPath("$.sections.traditionalpageone", Matchers.allOf( - Matchers.is(MetadataMatcher.matchMetadata(authorField, expectedOrder.get(0), 0)), - Matchers.is(MetadataMatcher.matchMetadata(authorField, expectedOrder.get(1), 1)), - Matchers.is(MetadataMatcher.matchMetadata(authorField, expectedOrder.get(2), 2)), - Matchers.is(MetadataMatcher.matchMetadata(authorField, expectedOrder.get(3), 3)), - Matchers.is(MetadataMatcher.matchMetadata(authorField, expectedOrder.get(4), 4)) - ))); + getClient(token) + .perform(get("/api/submission/workspaceitems/" + publicationWorkspaceItem.getID())) + .andExpect(status().isOk()) + .andExpect( + content().contentType(contentType) + ) + .andExpect( + jsonPath( + "$.sections.traditionalpageone", + Matchers.allOf( + Matchers.is(MetadataMatcher.matchMetadata(authorField, expectedOrder.get(0), 0)), + Matchers.is(MetadataMatcher.matchMetadata(authorField, expectedOrder.get(1), 1)), + Matchers.is(MetadataMatcher.matchMetadata(authorField, expectedOrder.get(2), 2)), + Matchers.is(MetadataMatcher.matchMetadata(authorField, expectedOrder.get(3), 3)), + Matchers.is(MetadataMatcher.matchMetadata(authorField, expectedOrder.get(4), 4)) + ) + ) + ); } /** @@ -1490,8 +1553,7 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { List ops = new ArrayList(); MetadataValueRest value = new MetadataValueRest(); value.setValue(addedAuthor); - AddOperation addOperation = new AddOperation("/sections/traditionalpageone/dc.contributor.author/" + path, - value); + AddOperation addOperation = new AddOperation(getPath(path), value); ops.add(addOperation); String patchBody = getPatchContent(ops); @@ -1525,8 +1587,7 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { */ private void removeTraditionalPageOneAuthorTest(int path, List expectedOrder) throws Exception { List ops = new ArrayList(); - RemoveOperation removeOperation = new RemoveOperation("/sections/traditionalpageone/dc.contributor.author/" - + path); + RemoveOperation removeOperation = new RemoveOperation(getPath(path)); ops.add(removeOperation); String patchBody = getPatchContent(ops); @@ -1600,8 +1661,10 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { * @param path The "path" index to use for the Move operation */ private MoveOperation getTraditionalPageOneMoveAuthorOperation(int from, int path) { - return new MoveOperation("/sections/traditionalpageone/dc.contributor.author/" + path, - "/sections/traditionalpageone/dc.contributor.author/" + from); + return new MoveOperation( + getPath(path), + getPath(from) + ); } /** diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RegistrationRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RegistrationRestRepositoryIT.java index 4a8abf3f59..3ba84f344b 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RegistrationRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RegistrationRestRepositoryIT.java @@ -7,6 +7,9 @@ */ package org.dspace.app.rest; +import static org.dspace.app.rest.repository.RegistrationRestRepository.TYPE_FORGOT; +import static org.dspace.app.rest.repository.RegistrationRestRepository.TYPE_QUERY_PARAM; +import static org.dspace.app.rest.repository.RegistrationRestRepository.TYPE_REGISTER; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; @@ -29,6 +32,7 @@ import org.dspace.app.rest.matcher.RegistrationMatcher; import org.dspace.app.rest.model.RegistrationRest; import org.dspace.app.rest.repository.RegistrationRestRepository; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.builder.EPersonBuilder; import org.dspace.eperson.CaptchaServiceImpl; import org.dspace.eperson.InvalidReCaptchaException; import org.dspace.eperson.RegistrationData; @@ -111,6 +115,7 @@ public class RegistrationRestRepositoryIT extends AbstractControllerIntegrationT RegistrationRest registrationRest = new RegistrationRest(); registrationRest.setEmail(email); getClient().perform(post("/api/eperson/registrations") + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .content(mapper.writeValueAsBytes(registrationRest)) .contentType(contentType)) .andExpect(status().isCreated()); @@ -127,6 +132,7 @@ public class RegistrationRestRepositoryIT extends AbstractControllerIntegrationT try { getClient().perform(post("/api/eperson/registrations") + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .content(mapper.writeValueAsBytes(registrationRest)) .contentType(contentType)) .andExpect(status().isCreated()); @@ -137,6 +143,7 @@ public class RegistrationRestRepositoryIT extends AbstractControllerIntegrationT String newEmail = "newEPersonTest@gmail.com"; registrationRest.setEmail(newEmail); getClient().perform(post("/api/eperson/registrations") + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .content(mapper.writeValueAsBytes(registrationRest)) .contentType(contentType)) .andExpect(status().isCreated()); @@ -149,6 +156,7 @@ public class RegistrationRestRepositoryIT extends AbstractControllerIntegrationT newEmail = "newEPersonTestTwo@gmail.com"; registrationRest.setEmail(newEmail); getClient().perform(post("/api/eperson/registrations") + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .content(mapper.writeValueAsBytes(registrationRest)) .contentType(contentType)) .andExpect(status().is(HttpServletResponse.SC_UNAUTHORIZED)); @@ -165,6 +173,91 @@ public class RegistrationRestRepositoryIT extends AbstractControllerIntegrationT } } + @Test + public void testRegisterDomainRegistered() throws Exception { + List registrationDataList = registrationDataDAO.findAll(context, RegistrationData.class); + try { + configurationService.setProperty("authentication-password.domain.valid", "test.com"); + RegistrationRest registrationRest = new RegistrationRest(); + String email = "testPerson@test.com"; + registrationRest.setEmail(email); + + ObjectMapper mapper = new ObjectMapper(); + getClient().perform(post("/api/eperson/registrations") + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) + .content(mapper.writeValueAsBytes(registrationRest)) + .contentType(contentType)) + .andExpect(status().isCreated()); + registrationDataList = registrationDataDAO.findAll(context, RegistrationData.class); + assertEquals(1, registrationDataList.size()); + assertTrue(StringUtils.equalsIgnoreCase(registrationDataList.get(0).getEmail(), email)); + } finally { + Iterator iterator = registrationDataList.iterator(); + while (iterator.hasNext()) { + RegistrationData registrationData = iterator.next(); + registrationDataDAO.delete(context, registrationData); + } + } + } + + @Test + public void testRegisterDomainNotRegistered() throws Exception { + List registrationDataList; + try { + configurationService.setProperty("authentication-password.domain.valid", "test.com"); + RegistrationRest registrationRest = new RegistrationRest(); + String email = "testPerson@bladibla.com"; + registrationRest.setEmail(email); + + ObjectMapper mapper = new ObjectMapper(); + getClient().perform(post("/api/eperson/registrations") + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) + .content(mapper.writeValueAsBytes(registrationRest)) + .contentType(contentType)) + .andExpect(status().isUnprocessableEntity()); + } finally { + registrationDataList = registrationDataDAO.findAll(context, RegistrationData.class); + Iterator iterator = registrationDataList.iterator(); + while (iterator.hasNext()) { + RegistrationData registrationData = iterator.next(); + registrationDataDAO.delete(context, registrationData); + } + } + } + + @Test + public void testRegisterMailAddressRegistered() throws Exception { + List registrationDataList = registrationDataDAO.findAll(context, RegistrationData.class); + try { + context.turnOffAuthorisationSystem(); + String email = "test@gmail.com"; + EPersonBuilder.createEPerson(context) + .withEmail(email) + .withCanLogin(true) + .build(); + context.restoreAuthSystemState(); + + RegistrationRest registrationRest = new RegistrationRest(); + registrationRest.setEmail(email); + + ObjectMapper mapper = new ObjectMapper(); + getClient().perform(post("/api/eperson/registrations") + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) + .content(mapper.writeValueAsBytes(registrationRest)) + .contentType(contentType)) + .andExpect(status().isCreated()); + registrationDataList = registrationDataDAO.findAll(context, RegistrationData.class); + assertEquals(1, registrationDataList.size()); + assertTrue(StringUtils.equalsIgnoreCase(registrationDataList.get(0).getEmail(), email)); + } finally { + Iterator iterator = registrationDataList.iterator(); + while (iterator.hasNext()) { + RegistrationData registrationData = iterator.next(); + registrationDataDAO.delete(context, registrationData); + } + } + } + @Test public void forgotPasswordTest() throws Exception { configurationService.setProperty("user.registration", false); @@ -177,6 +270,7 @@ public class RegistrationRestRepositoryIT extends AbstractControllerIntegrationT RegistrationRest registrationRest = new RegistrationRest(); registrationRest.setEmail(eperson.getEmail()); getClient().perform(post("/api/eperson/registrations") + .param(TYPE_QUERY_PARAM, TYPE_FORGOT) .content(mapper.writeValueAsBytes(registrationRest)) .contentType(contentType)) .andExpect(status().isCreated()); @@ -205,6 +299,7 @@ public class RegistrationRestRepositoryIT extends AbstractControllerIntegrationT // when reCAPTCHA enabled and request doesn't contain "X-Recaptcha-Token” header getClient().perform(post("/api/eperson/registrations") + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .content(mapper.writeValueAsBytes(registrationRest)) .contentType(contentType)) .andExpect(status().isForbidden()); @@ -226,6 +321,7 @@ public class RegistrationRestRepositoryIT extends AbstractControllerIntegrationT String captchaToken = "invalid-captcha-Token"; // when reCAPTCHA enabled and request contains Invalid "X-Recaptcha-Token” header getClient().perform(post("/api/eperson/registrations") + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .header("X-Recaptcha-Token", captchaToken) .content(mapper.writeValueAsBytes(registrationRest)) .contentType(contentType)) @@ -262,12 +358,14 @@ public class RegistrationRestRepositoryIT extends AbstractControllerIntegrationT try { // will throw InvalidReCaptchaException because 'X-Recaptcha-Token' not equal captchaToken getClient().perform(post("/api/eperson/registrations") + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .header("X-Recaptcha-Token", captchaToken1) .content(mapper.writeValueAsBytes(registrationRest)) .contentType(contentType)) .andExpect(status().isForbidden()); getClient().perform(post("/api/eperson/registrations") + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .header("X-Recaptcha-Token", captchaToken) .content(mapper.writeValueAsBytes(registrationRest)) .contentType(contentType)) @@ -280,6 +378,7 @@ public class RegistrationRestRepositoryIT extends AbstractControllerIntegrationT String newEmail = "newEPersonTest@gmail.com"; registrationRest.setEmail(newEmail); getClient().perform(post("/api/eperson/registrations") + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .header("X-Recaptcha-Token", captchaToken) .content(mapper.writeValueAsBytes(registrationRest)) .contentType(contentType)) @@ -295,6 +394,7 @@ public class RegistrationRestRepositoryIT extends AbstractControllerIntegrationT newEmail = "newEPersonTestTwo@gmail.com"; registrationRest.setEmail(newEmail); getClient().perform(post("/api/eperson/registrations") + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .header("X-Recaptcha-Token", captchaToken) .content(mapper.writeValueAsBytes(registrationRest)) .contentType(contentType)) @@ -321,4 +421,27 @@ public class RegistrationRestRepositoryIT extends AbstractControllerIntegrationT captchaService.init(); } + @Test + public void accountEndpoint_WithoutAccountTypeParam() throws Exception { + ObjectMapper mapper = new ObjectMapper(); + RegistrationRest registrationRest = new RegistrationRest(); + registrationRest.setEmail(eperson.getEmail()); + getClient().perform(post("/api/eperson/registrations") + .content(mapper.writeValueAsBytes(registrationRest)) + .contentType(contentType)) + .andExpect(status().isBadRequest()); + } + + @Test + public void accountEndpoint_WrongAccountTypeParam() throws Exception { + ObjectMapper mapper = new ObjectMapper(); + RegistrationRest registrationRest = new RegistrationRest(); + registrationRest.setEmail(eperson.getEmail()); + getClient().perform(post("/api/eperson/registrations") + .param(TYPE_QUERY_PARAM, "nonValidValue") + .content(mapper.writeValueAsBytes(registrationRest)) + .contentType(contentType)) + .andExpect(status().isBadRequest()); + } + } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RequestItemRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RequestItemRepositoryIT.java index d7eb707bbb..2fb7dbbc96 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RequestItemRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RequestItemRepositoryIT.java @@ -8,14 +8,12 @@ package org.dspace.app.rest; import static com.jayway.jsonpath.JsonPath.read; -import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; import static org.exparity.hamcrest.date.DateMatchers.within; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.not; -import static org.hamcrest.text.IsEmptyString.emptyOrNullString; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; @@ -34,6 +32,7 @@ import java.io.InputStream; import java.sql.SQLException; import java.time.temporal.ChronoUnit; import java.util.Date; +import java.util.Iterator; import java.util.Map; import java.util.UUID; import java.util.concurrent.atomic.AtomicReference; @@ -221,33 +220,34 @@ public class RequestItemRepositoryIT // Create it and see if it was created correctly. ObjectMapper mapper = new ObjectMapper(); String authToken = getAuthToken(eperson.getEmail(), password); - AtomicReference requestTokenRef = new AtomicReference<>(); try { - getClient(authToken) - .perform(post(URI_ROOT) - .content(mapper.writeValueAsBytes(rir)) - .contentType(contentType)) - .andExpect(status().isCreated()) - .andExpect(content().contentType(contentType)) - .andExpect(jsonPath("$", Matchers.allOf( - hasJsonPath("$.id", not(is(emptyOrNullString()))), - hasJsonPath("$.type", is(RequestItemRest.NAME)), - hasJsonPath("$.token", not(is(emptyOrNullString()))), - hasJsonPath("$.requestEmail", is(eperson.getEmail())), - hasJsonPath("$.requestMessage", is(RequestItemBuilder.REQ_MESSAGE)), - hasJsonPath("$.requestName", is(eperson.getFullName())), - hasJsonPath("$.allfiles", is(true)), - // TODO should be an ISO datetime - hasJsonPath("$.requestDate", not(is(emptyOrNullString()))), - hasJsonPath("$._links.self.href", not(is(emptyOrNullString()))) - ))) - .andDo((var result) -> requestTokenRef.set( - read(result.getResponse().getContentAsString(), "token"))); + getClient(authToken) + .perform(post(URI_ROOT) + .content(mapper.writeValueAsBytes(rir)) + .contentType(contentType)) + .andExpect(status().isCreated()) + // verify the body is empty + .andExpect(jsonPath("$").doesNotExist()); } finally { - // Clean up the created request. - RequestItemBuilder.deleteRequestItem(requestTokenRef.get()); + Iterator itemRequests = requestItemService.findByItem(context, item); + String token = null; + for (Iterator it = itemRequests; it.hasNext();) { + RequestItem requestItem = it.next(); + // Find the created request via the eperson email + if (requestItem.getReqEmail().equals(eperson.getEmail())) { + // Verify request data + assertEquals(eperson.getFullName(), requestItem.getReqName()); + assertEquals(item.getID(), requestItem.getItem().getID()); + assertEquals(RequestItemBuilder.REQ_MESSAGE, requestItem.getReqMessage()); + assertEquals(true, requestItem.isAllfiles()); + assertNotNull(requestItem.getToken()); + token = requestItem.getToken(); + } + } + // Cleanup created request + RequestItemBuilder.deleteRequestItem(token); } - } +} /** * Test of createAndReturn method, with an UNauthenticated user. @@ -273,30 +273,32 @@ public class RequestItemRepositoryIT // Create it and see if it was created correctly. ObjectMapper mapper = new ObjectMapper(); - AtomicReference requestTokenRef = new AtomicReference<>(); try { - getClient().perform(post(URI_ROOT) - .content(mapper.writeValueAsBytes(rir)) - .contentType(contentType)) - .andExpect(status().isCreated()) - .andExpect(content().contentType(contentType)) - .andExpect(jsonPath("$", Matchers.allOf( - hasJsonPath("$.id", not(is(emptyOrNullString()))), - hasJsonPath("$.type", is(RequestItemRest.NAME)), - hasJsonPath("$.token", not(is(emptyOrNullString()))), - hasJsonPath("$.requestEmail", is(RequestItemBuilder.REQ_EMAIL)), - hasJsonPath("$.requestMessage", is(RequestItemBuilder.REQ_MESSAGE)), - hasJsonPath("$.requestName", is(RequestItemBuilder.REQ_NAME)), - hasJsonPath("$.allfiles", is(false)), - // TODO should be an ISO datetime - hasJsonPath("$.requestDate", not(is(emptyOrNullString()))), - hasJsonPath("$._links.self.href", not(is(emptyOrNullString()))) - ))) - .andDo((var result) -> requestTokenRef.set( - read(result.getResponse().getContentAsString(), "token"))); + getClient().perform(post(URI_ROOT) + .content(mapper.writeValueAsBytes(rir)) + .contentType(contentType)) + .andExpect(status().isCreated()) + // verify the body is empty + .andExpect(jsonPath("$").doesNotExist()); } finally { - // Clean up the created request. - RequestItemBuilder.deleteRequestItem(requestTokenRef.get()); + Iterator itemRequests = requestItemService.findByItem(context, item); + String token = null; + for (Iterator it = itemRequests; it.hasNext();) { + RequestItem requestItem = it.next(); + // Find the created request via the eperson email + if (requestItem.getReqEmail().equals(RequestItemBuilder.REQ_EMAIL)) { + // Verify request data + assertEquals(item.getID(), requestItem.getItem().getID()); + assertEquals(RequestItemBuilder.REQ_MESSAGE, requestItem.getReqMessage()); + assertEquals(RequestItemBuilder.REQ_NAME, requestItem.getReqName()); + assertEquals(bitstream.getID(), requestItem.getBitstream().getID()); + assertEquals(false, requestItem.isAllfiles()); + assertNotNull(requestItem.getToken()); + token = requestItem.getToken(); + } + } + // Cleanup created request + RequestItemBuilder.deleteRequestItem(token); } } @@ -517,6 +519,33 @@ public class RequestItemRepositoryIT within(1, ChronoUnit.MINUTES, new Date())); } + @Test + public void testPutUnauthenticated() + throws Exception { + System.out.println("put unauthenticated request"); + RequestItem itemRequest = RequestItemBuilder + .createRequestItem(context, item, bitstream) + .build(); + + Map parameters; + String content; + + ObjectWriter mapperWriter = new ObjectMapper().writer(); + + // Unauthenticated user should be allowed. + parameters = Map.of( + "acceptRequest", "true", + "subject", "put unauthenticated", + "responseMessage", "Request accepted", + "suggestOpenAccess", "false"); + + content = mapperWriter.writeValueAsString(parameters); + getClient().perform(put(URI_ROOT + '/' + itemRequest.getToken()) + .contentType(contentType) + .content(content)) + .andExpect(status().isOk()); + } + @Test public void testPutBadRequest() throws Exception { @@ -533,29 +562,6 @@ public class RequestItemRepositoryIT ObjectWriter mapperWriter = new ObjectMapper().writer(); - // Unauthenticated user - parameters = Map.of( - "acceptRequest", "true", - "subject", "subject", - "responseMessage", "Request accepted"); - content = mapperWriter.writeValueAsString(parameters); - getClient().perform(put(URI_ROOT + '/' + itemRequest.getToken()) - .contentType(contentType) - .content(content)) - .andExpect(status().isUnauthorized()); - - // Unauthorized user - parameters = Map.of( - "acceptRequest", "true", - "subject", "subject", - "responseMessage", "Request accepted"); - content = mapperWriter.writeValueAsString(parameters); - authToken = getAuthToken(admin.getEmail(), password); - getClient(authToken).perform(put(URI_ROOT + '/' + itemRequest.getToken()) - .contentType(contentType) - .content(content)) - .andExpect(status().isInternalServerError()); // Should be FORBIDDEN - // Missing acceptRequest parameters = Map.of( "subject", "subject", diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ScriptRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ScriptRestRepositoryIT.java index 0513a0d5f4..07edfeec33 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ScriptRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ScriptRestRepositoryIT.java @@ -10,6 +10,7 @@ package org.dspace.app.rest; import static com.jayway.jsonpath.JsonPath.read; import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.is; @@ -101,6 +102,25 @@ public class ScriptRestRepositoryIT extends AbstractControllerIntegrationTest { ))); } + @Test + public void findAllScriptsSortedAlphabeticallyTest() throws Exception { + String token = getAuthToken(admin.getEmail(), password); + + getClient(token).perform(get("/api/system/scripts") + .param("size", String.valueOf(scriptConfigurations.size()))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.scripts", contains( + scriptConfigurations + .stream() + .sorted(Comparator.comparing(ScriptConfiguration::getName)) + .map(scriptConfiguration -> ScriptMatcher.matchScript( + scriptConfiguration.getName(), + scriptConfiguration.getDescription() + )) + .collect(Collectors.toList()) + ))); + } + @Test public void findAllScriptsUnauthorizedTest() throws Exception { @@ -115,7 +135,7 @@ public class ScriptRestRepositoryIT extends AbstractControllerIntegrationTest { public void findAllScriptsPaginationTest() throws Exception { List alphabeticScripts = scriptConfigurations.stream() - .sorted(Comparator.comparing(s -> s.getClass().getName())) + .sorted(Comparator.comparing(ScriptConfiguration::getName)) .collect(Collectors.toList()); int totalPages = scriptConfigurations.size(); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SiteRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SiteRestRepositoryIT.java index 092ea32b3f..26b01071d1 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SiteRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SiteRestRepositoryIT.java @@ -9,22 +9,34 @@ package org.dspace.app.rest; import static org.hamcrest.Matchers.is; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import java.util.List; import java.util.UUID; +import org.dspace.app.rest.matcher.MetadataMatcher; import org.dspace.app.rest.matcher.SiteMatcher; +import org.dspace.app.rest.model.patch.Operation; +import org.dspace.app.rest.model.patch.ReplaceOperation; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.app.rest.test.MetadataPatchSuite; import org.dspace.builder.SiteBuilder; +import org.dspace.content.Item; +import org.dspace.content.MetadataSchemaEnum; import org.dspace.content.Site; +import org.dspace.content.service.SiteService; import org.dspace.eperson.EPerson; import org.hamcrest.Matchers; import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; public class SiteRestRepositoryIT extends AbstractControllerIntegrationTest { + @Autowired + private SiteService siteService; + @Test public void findAll() throws Exception { @@ -77,6 +89,75 @@ public class SiteRestRepositoryIT extends AbstractControllerIntegrationTest { runPatchMetadataTests(eperson, 403); } + @Test + public void patchReplaceMultipleDescriptionSite() throws Exception { + context.turnOffAuthorisationSystem(); + + List siteDescriptions = List.of( + "FIRST", + "SECOND", + "THIRD" + ); + + Site site = SiteBuilder.createSite(context).build(); + + this.siteService + .addMetadata( + context, site, + MetadataSchemaEnum.DC.getName(), "description", null, + Item.ANY, siteDescriptions + ); + + context.restoreAuthSystemState(); + String token = getAuthToken(admin.getEmail(), password); + + getClient(token) + .perform(get("/api/core/sites/" + site.getID())) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$.metadata", + Matchers.allOf( + MetadataMatcher.matchMetadata("dc.description", siteDescriptions.get(0), 0), + MetadataMatcher.matchMetadata("dc.description", siteDescriptions.get(1), 1), + MetadataMatcher.matchMetadata("dc.description", siteDescriptions.get(2), 2) + ) + ) + ); + + List ops = List.of( + new ReplaceOperation("/metadata/dc.description/0", siteDescriptions.get(2)), + new ReplaceOperation("/metadata/dc.description/1", siteDescriptions.get(0)), + new ReplaceOperation("/metadata/dc.description/2", siteDescriptions.get(1)) + ); + String requestBody = getPatchContent(ops); + getClient(token) + .perform(patch("/api/core/sites/" + site.getID()) + .content(requestBody) + .contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$.metadata", + Matchers.allOf( + MetadataMatcher.matchMetadata("dc.description", siteDescriptions.get(2), 0), + MetadataMatcher.matchMetadata("dc.description", siteDescriptions.get(0), 1), + MetadataMatcher.matchMetadata("dc.description", siteDescriptions.get(1), 2) + ) + ) + ); + getClient(token) + .perform(get("/api/core/sites/" + site.getID())) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$.metadata", + Matchers.allOf( + MetadataMatcher.matchMetadata("dc.description", siteDescriptions.get(2), 0), + MetadataMatcher.matchMetadata("dc.description", siteDescriptions.get(0), 1), + MetadataMatcher.matchMetadata("dc.description", siteDescriptions.get(1), 2) + ) + ) + ); + } + private void runPatchMetadataTests(EPerson asUser, int expectedStatus) throws Exception { context.turnOffAuthorisationSystem(); Site site = SiteBuilder.createSite(context).build(); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionDefinitionsControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionDefinitionsControllerIT.java index 5f93411bb9..e7d43ec4d6 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionDefinitionsControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionDefinitionsControllerIT.java @@ -205,7 +205,7 @@ public class SubmissionDefinitionsControllerIT extends AbstractControllerIntegra // We expect the content type to be "application/hal+json;charset=UTF-8" .andExpect(content().contentType(contentType)) // Match only that a section exists with a submission configuration behind - .andExpect(jsonPath("$._embedded.submissionsections", hasSize(8))) + .andExpect(jsonPath("$._embedded.submissionsections", hasSize(9))) .andExpect(jsonPath("$._embedded.submissionsections", Matchers.hasItem( allOf( diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionShowIdentifiersRestIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionShowIdentifiersRestIT.java new file mode 100644 index 0000000000..8d95f4627b --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionShowIdentifiersRestIT.java @@ -0,0 +1,135 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the 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.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.io.IOException; +import java.sql.SQLException; + +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.authorize.AuthorizeException; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.EPersonBuilder; +import org.dspace.builder.WorkspaceItemBuilder; +import org.dspace.content.Collection; +import org.dspace.content.WorkspaceItem; +import org.dspace.content.service.WorkspaceItemService; +import org.dspace.eperson.EPerson; +import org.dspace.handle.service.HandleService; +import org.dspace.identifier.DOIIdentifierProvider; +import org.dspace.services.ConfigurationService; +import org.junit.After; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Test suite for testing the Show Identifiers submission step + * + * @author Kim Shepherd + * + */ +public class SubmissionShowIdentifiersRestIT extends AbstractControllerIntegrationTest { + + @Autowired + private WorkspaceItemService workspaceItemService; + + @Autowired + private ConfigurationService configurationService; + + @Autowired + private HandleService handleService; + + private Collection collection; + private EPerson submitter; + + @Override + public void setUp() throws Exception { + super.setUp(); + + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Root community").build(); + + submitter = EPersonBuilder.createEPerson(context) + .withEmail("submitter.em@test.com") + .withPassword(password) + .build(); + + collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection") + .withEntityType("Publication") + .withSubmitterGroup(submitter).build(); + + // Manually set configuration to allow registration handles, DOIs at workspace item creation + configurationService.setProperty("identifiers.submission.register", true); + + context.restoreAuthSystemState(); + } + + @After + public void after() throws SQLException, IOException, AuthorizeException { + context.turnOffAuthorisationSystem(); + workspaceItemService.findAll(context).forEach(this::deleteWorkspaceItem); + // Manually restore identifiers configuration + configurationService.setProperty("identifiers.submission.register", false); + context.restoreAuthSystemState(); + } + + private void deleteWorkspaceItem(WorkspaceItem workspaceItem) { + try { + workspaceItemService.deleteAll(context, workspaceItem); + } catch (SQLException | AuthorizeException | IOException e) { + throw new RuntimeException(); + } + } + + @Test + public void testItemHandleReservation() throws Exception { + // Test publication that should get Handle and DOI + context.turnOffAuthorisationSystem(); + WorkspaceItem workspaceItem = createWorkspaceItem("Test publication", collection); + context.restoreAuthSystemState(); + // Expected handle + String expectedHandle = handleService.resolveToURL(context, workspaceItem.getItem().getHandle()); + String submitterToken = getAuthToken(submitter.getEmail(), password); + getClient(submitterToken).perform(get("/api/submission/workspaceitems/" + workspaceItem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.identifiers.identifiers[1].type").value("identifier")) + .andExpect(jsonPath("$.sections.identifiers.identifiers[1].value").value(expectedHandle)) + .andExpect(jsonPath("$.sections.identifiers.identifiers[1].identifierType").value("handle")); + } + + @Test + public void testItemDoiReservation() throws Exception { + // Test publication that should get Handle and DOI + context.turnOffAuthorisationSystem(); + WorkspaceItem workspaceItem = createWorkspaceItem("Test publication", collection); + context.restoreAuthSystemState(); + + String submitterToken = getAuthToken(submitter.getEmail(), password); + getClient(submitterToken).perform(get("/api/submission/workspaceitems/" + workspaceItem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.identifiers.identifiers[0].type").value("identifier")) + .andExpect(jsonPath("$.sections.identifiers.identifiers[0].identifierType").value("doi")) + .andExpect(jsonPath("$.sections.identifiers.identifiers[0].identifierStatus") + .value(DOIIdentifierProvider.statusText[DOIIdentifierProvider.PENDING])); + } + + private WorkspaceItem createWorkspaceItem(String title, Collection collection) { + WorkspaceItem workspaceItem = WorkspaceItemBuilder.createWorkspaceItem(context, collection) + .withTitle(title) + .withSubmitter(submitter) + .build(); + return workspaceItem; + } + +} \ No newline at end of file diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java new file mode 100644 index 0000000000..038acf7e73 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java @@ -0,0 +1,1249 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the 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 com.jayway.jsonpath.JsonPath.read; +import static org.hamcrest.Matchers.greaterThanOrEqualTo; +import static org.hamcrest.Matchers.is; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicReference; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.dspace.app.rest.matcher.EPersonMatcher; +import org.dspace.app.rest.matcher.SubscriptionMatcher; +import org.dspace.app.rest.model.SubscriptionParameterRest; +import org.dspace.app.rest.model.SubscriptionRest; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.authorize.service.ResourcePolicyService; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.EPersonBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.builder.SubscribeBuilder; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.Item; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.Subscription; +import org.dspace.eperson.SubscriptionParameter; +import org.hamcrest.Matchers; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; + +/** + * Integration test to test the /api/config/subscriptions endpoint + * (Class has to start or end with IT to be picked up by the failsafe plugin) + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.com) + */ +public class SubscriptionRestRepositoryIT extends AbstractControllerIntegrationTest { + + @Autowired + private ResourcePolicyService resourcePolicyService; + + private Community subCommunity; + private Collection collection; + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + subCommunity = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Test Sub Community") + .build(); + collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .withSubmitterGroup(eperson) + .build(); + + context.restoreAuthSystemState(); + } + + @Test + public void findAll() throws Exception { + context.turnOffAuthorisationSystem(); + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("D"); + subscriptionParameterList.add(subscriptionParameter); + Subscription subscription1 = SubscribeBuilder.subscribeBuilder(context, + "content", collection, eperson, subscriptionParameterList).build(); + List subscriptionParameterList2 = new ArrayList<>(); + SubscriptionParameter subscriptionParameter2 = new SubscriptionParameter(); + subscriptionParameter2.setName("frequency"); + subscriptionParameter2.setValue("W"); + subscriptionParameterList2.add(subscriptionParameter2); + Subscription subscription2 = SubscribeBuilder.subscribeBuilder(context, + "content", collection, admin, subscriptionParameterList2).build(); + + context.restoreAuthSystemState(); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/core/subscriptions")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.subscriptions", Matchers.containsInAnyOrder( + SubscriptionMatcher.matchSubscription(subscription1), + SubscriptionMatcher.matchSubscription(subscription2) + ))) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", greaterThanOrEqualTo(2))) + .andExpect(jsonPath("$.page.totalPages", greaterThanOrEqualTo(1))) + .andExpect(jsonPath("$.page.number", is(0))); + } + + @Test + public void findAllAnonymous() throws Exception { + getClient().perform(get("/api/core/subscriptions")) + .andExpect(status().isUnauthorized()); + } + + @Test + public void findAllAsUser() throws Exception { + String tokenEPerson = getAuthToken(eperson.getEmail(), password); + getClient(tokenEPerson).perform(get("/api/core/subscriptions")) + .andExpect(status().isForbidden()); + } + + @Test + public void findOneWithOwnerTest() throws Exception { + context.turnOffAuthorisationSystem(); + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("M"); + subscriptionParameterList.add(subscriptionParameter); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, + "content", collection, eperson, subscriptionParameterList).build(); + context.restoreAuthSystemState(); + + String tokenEPerson = getAuthToken(eperson.getEmail(), password); + getClient(tokenEPerson).perform(get("/api/core/subscriptions/" + subscription.getID())) + .andExpect(status().isOk()) + //We expect the content type to be "application/hal+json;charset=UTF-8" + .andExpect(content().contentType(contentType)) + //By default we expect at least 1 submission forms so this to be reflected in the page object + .andExpect(jsonPath("$.subscriptionType", is("content"))) + .andExpect(jsonPath("$.subscriptionParameterList[0].name", is("frequency"))) + .andExpect(jsonPath("$.subscriptionParameterList[0].value", is("M"))) + .andExpect(jsonPath("$._links.eperson.href", Matchers.endsWith("/eperson"))) + .andExpect(jsonPath("$._links.resource.href", Matchers.endsWith("/resource"))) + .andExpect(jsonPath("$._links.self.href", + Matchers.startsWith(REST_SERVER_URL + "core/subscriptions/" + subscription.getID()))) + .andExpect(jsonPath("$._links.resource.href", + Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) + .andExpect(jsonPath("$._links.eperson.href", + Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))); + } + + @Test + public void findOneAdminTest() throws Exception { + context.turnOffAuthorisationSystem(); + + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("W"); + subscriptionParameterList.add(subscriptionParameter); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, + "content", collection, admin, subscriptionParameterList).build(); + context.restoreAuthSystemState(); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/core/subscriptions/" + subscription.getID())) + .andExpect(status().isOk()) + //We expect the content type to be "application/hal+json;charset=UTF-8" + .andExpect(content().contentType(contentType)) + //By default we expect at least 1 submission forms so this to be reflected in the page object + .andExpect(jsonPath("$.subscriptionType", is("content"))) + .andExpect(jsonPath("$.subscriptionParameterList[0].name", is("frequency"))) + .andExpect(jsonPath("$.subscriptionParameterList[0].value", is("W"))) + .andExpect(jsonPath("$._links.self.href", + Matchers.startsWith(REST_SERVER_URL + "core/subscriptions/" + subscription.getID()))) + .andExpect(jsonPath("$._links.resource.href", + Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) + .andExpect(jsonPath("$._links.resource.href", Matchers.endsWith("/resource"))) + .andExpect(jsonPath("$._links.eperson.href", + Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) + .andExpect(jsonPath("$._links.eperson.href", Matchers.endsWith("/eperson"))); + } + + @Test + public void findOneAnonymousTest() throws Exception { + context.turnOffAuthorisationSystem(); + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("D"); + subscriptionParameterList.add(subscriptionParameter); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, + "content", collection, admin, subscriptionParameterList).build(); + context.restoreAuthSystemState(); + + getClient().perform(get("/api/core/subscriptions/" + subscription.getID())) + .andExpect(status().isUnauthorized()); + } + + @Test + public void findOneForbiddenTest() throws Exception { + context.turnOffAuthorisationSystem(); + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("W"); + subscriptionParameterList.add(subscriptionParameter); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, + "content", collection, admin, subscriptionParameterList).build(); + context.restoreAuthSystemState(); + + String tokenEPerson = getAuthToken(eperson.getEmail(), password); + getClient(tokenEPerson).perform(get("/api/core/subscriptions/" + subscription.getID())) + .andExpect(status().isForbidden()); + } + + @Test + public void findOneNotFoundTest() throws Exception { + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/core/subscriptions/" + Integer.MAX_VALUE)) + .andExpect(status().isNotFound()); + } + + @Test + public void findSubscriptionsByEPersonAdminTest() throws Exception { + context.turnOffAuthorisationSystem(); + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("D"); + subscriptionParameterList.add(subscriptionParameter); + Subscription subscription1 = SubscribeBuilder.subscribeBuilder(context, + "content", collection, eperson, subscriptionParameterList).build(); + List subscriptionParameterList2 = new ArrayList<>(); + SubscriptionParameter subscriptionParameter2 = new SubscriptionParameter(); + subscriptionParameter2.setName("frequency"); + subscriptionParameter2.setValue("W"); + subscriptionParameterList2.add(subscriptionParameter2); + Subscription subscription2 = SubscribeBuilder.subscribeBuilder(context, + "content", collection, eperson, subscriptionParameterList2).build(); + + context.restoreAuthSystemState(); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/core/subscriptions/search/findByEPerson") + .param("uuid", eperson.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.subscriptions", Matchers.containsInAnyOrder( + SubscriptionMatcher.matchSubscription(subscription1), + SubscriptionMatcher.matchSubscription(subscription2) + ))) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", greaterThanOrEqualTo(2))) + .andExpect(jsonPath("$.page.totalPages", greaterThanOrEqualTo(1))) + .andExpect(jsonPath("$.page.number", is(0))); + } + + @Test + public void findSubscriptionsByEPersonOwnerTest() throws Exception { + context.turnOffAuthorisationSystem(); + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("M"); + subscriptionParameterList.add(subscriptionParameter); + Subscription subscription1 = SubscribeBuilder.subscribeBuilder(context, + "content", collection, eperson, subscriptionParameterList).build(); + List subscriptionParameterList2 = new ArrayList<>(); + SubscriptionParameter subscriptionParameter2 = new SubscriptionParameter(); + subscriptionParameter2.setName("frequency"); + subscriptionParameter2.setValue("D"); + subscriptionParameterList2.add(subscriptionParameter2); + Subscription subscription2 = SubscribeBuilder.subscribeBuilder(context, + "content", collection, eperson, subscriptionParameterList2).build(); + + context.restoreAuthSystemState(); + + String tokenAdmin = getAuthToken(eperson.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/core/subscriptions/search/findByEPerson") + .param("uuid", eperson.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.subscriptions", Matchers.containsInAnyOrder( + SubscriptionMatcher.matchSubscription(subscription1), + SubscriptionMatcher.matchSubscription(subscription2) + ))) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", greaterThanOrEqualTo(2))) + .andExpect(jsonPath("$.page.totalPages", greaterThanOrEqualTo(1))) + .andExpect(jsonPath("$.page.number", is(0))); + } + + @Test + public void findSubscriptionsByEPersonUnauthorizedTest() throws Exception { + context.turnOffAuthorisationSystem(); + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("D"); + subscriptionParameterList.add(subscriptionParameter); + SubscribeBuilder.subscribeBuilder(context, "content", collection, eperson, subscriptionParameterList).build(); + context.restoreAuthSystemState(); + + getClient().perform(get("/api/core/subscriptions/search/findByEPerson") + .param("uuid", eperson.getID().toString())) + .andExpect(status().isUnauthorized()); + } + + @Test + public void findSubscriptionsByEPersonForbiddenTest() throws Exception { + context.turnOffAuthorisationSystem(); + EPerson user = EPersonBuilder.createEPerson(context) + .withEmail("user1@mail.com") + .withPassword(password) + .build(); + + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("D"); + subscriptionParameterList.add(subscriptionParameter); + SubscribeBuilder.subscribeBuilder(context, "content", collection, user, subscriptionParameterList).build(); + context.restoreAuthSystemState(); + + String tokenEPerson = getAuthToken(eperson.getEmail(), password); + getClient(tokenEPerson).perform(get("/api/core/subscriptions/search/findByEPerson") + .param("uuid", user.getID().toString())) + .andExpect(status().isForbidden()); + } + + @Test + public void findByEPersonAndDsoAdminTest() throws Exception { + context.turnOffAuthorisationSystem(); + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("D"); + subscriptionParameterList.add(subscriptionParameter); + Subscription subscription1 = SubscribeBuilder.subscribeBuilder(context, + "content", collection, eperson, subscriptionParameterList).build(); + + List subscriptionParameterList2 = new ArrayList<>(); + SubscriptionParameter subscriptionParameter2 = new SubscriptionParameter(); + subscriptionParameter2.setName("frequency"); + subscriptionParameter2.setValue("W"); + subscriptionParameterList2.add(subscriptionParameter2); + Subscription subscription2 = SubscribeBuilder.subscribeBuilder(context, + "content", collection, eperson, subscriptionParameterList2).build(); + + List subscriptionParameterList3 = new ArrayList<>(); + SubscriptionParameter subscriptionParameter3 = new SubscriptionParameter(); + subscriptionParameter3.setName("frequency"); + subscriptionParameter3.setValue("M"); + subscriptionParameterList3.add(subscriptionParameter3); + Subscription subscription3 = SubscribeBuilder.subscribeBuilder(context, + "content", subCommunity, eperson, subscriptionParameterList3).build(); + + context.restoreAuthSystemState(); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/core/subscriptions/search/findByEPersonAndDso") + .param("eperson_id", eperson.getID().toString()) + .param("resource", collection.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.subscriptions", Matchers.containsInAnyOrder( + SubscriptionMatcher.matchSubscription(subscription1), + SubscriptionMatcher.matchSubscription(subscription2) + ))) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", is(2))) + .andExpect(jsonPath("$.page.totalPages", is(1))) + .andExpect(jsonPath("$.page.number", is(0))); + + getClient(tokenAdmin).perform(get("/api/core/subscriptions/search/findByEPersonAndDso") + .param("eperson_id", eperson.getID().toString()) + .param("resource", subCommunity.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.subscriptions", Matchers.contains( + SubscriptionMatcher.matchSubscription(subscription3) + ))) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", is(1))) + .andExpect(jsonPath("$.page.totalPages", is(1))) + .andExpect(jsonPath("$.page.number", is(0))); + } + + @Test + public void findByEPersonAndDsoOwnerTest() throws Exception { + context.turnOffAuthorisationSystem(); + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("D"); + subscriptionParameterList.add(subscriptionParameter); + Subscription subscription1 = SubscribeBuilder.subscribeBuilder(context, + "content", subCommunity, eperson, subscriptionParameterList).build(); + + context.restoreAuthSystemState(); + + String tokenEPerson = getAuthToken(eperson.getEmail(), password); + getClient(tokenEPerson).perform(get("/api/core/subscriptions/search/findByEPersonAndDso") + .param("eperson_id", eperson.getID().toString()) + .param("resource", subCommunity.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.subscriptions", Matchers.contains( + SubscriptionMatcher.matchSubscription(subscription1) + ))) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", is(1))) + .andExpect(jsonPath("$.page.totalPages", is(1))) + .andExpect(jsonPath("$.page.number", is(0))); + } + + @Test + public void findByEPersonAndDsoUnauthorizedTest() throws Exception { + context.turnOffAuthorisationSystem(); + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("D"); + subscriptionParameterList.add(subscriptionParameter); + SubscribeBuilder.subscribeBuilder(context, "content", subCommunity, eperson, subscriptionParameterList).build(); + context.restoreAuthSystemState(); + + getClient().perform(get("/api/core/subscriptions/search/findByEPersonAndDso") + .param("eperson_id", eperson.getID().toString()) + .param("resource", subCommunity.getID().toString())) + .andExpect(status().isUnauthorized()); + } + + @Test + public void findByEPersonAndDsoForbiddenTest() throws Exception { + context.turnOffAuthorisationSystem(); + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("D"); + subscriptionParameterList.add(subscriptionParameter); + SubscribeBuilder.subscribeBuilder(context, "content", subCommunity, admin, subscriptionParameterList).build(); + context.restoreAuthSystemState(); + + String tokenEPerson = getAuthToken(eperson.getEmail(), password); + getClient(tokenEPerson).perform(get("/api/core/subscriptions/search/findByEPersonAndDso") + .param("eperson_id", admin.getID().toString()) + .param("resource", subCommunity.getID().toString())) + .andExpect(status().isForbidden()); + } + + @Test + public void createSubscriptionUnauthorizedTest() throws Exception { + context.turnOffAuthorisationSystem(); + + SubscriptionParameterRest subscriptionParameterRest = new SubscriptionParameterRest(); + subscriptionParameterRest.setValue("frequency"); + subscriptionParameterRest.setName("D"); + List subscriptionParameterRestList = new ArrayList<>(); + subscriptionParameterRestList.add(subscriptionParameterRest); + + SubscriptionRest subscriptionRest = new SubscriptionRest(); + subscriptionRest.setSubscriptionType("content"); + + context.restoreAuthSystemState(); + + ObjectMapper objectMapper = new ObjectMapper(); + + getClient().perform(post("/api/core/subscriptions") + .param("resource", collection.getID().toString()) + .param("eperson_id", eperson.getID().toString()) + .content(objectMapper.writeValueAsString(subscriptionRest)) + .contentType(contentType)) + .andExpect(status().isUnauthorized()); + } + + @Test + public void createSubscriptionAdminForOtherPersonTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Map map = new HashMap<>(); + map.put("subscriptionType", "content"); + List> list = new ArrayList<>(); + Map sub_list = new HashMap<>(); + sub_list.put("name", "frequency"); + sub_list.put("value", "D"); + list.add(sub_list); + map.put("subscriptionParameterList", list); + + context.restoreAuthSystemState(); + + AtomicReference idRef = new AtomicReference(); + + try { + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(post("/api/core/subscriptions") + .param("resource", collection.getID().toString()) + .param("eperson_id", eperson.getID().toString()) + .content(new ObjectMapper().writeValueAsString(map)) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.subscriptionType", is("content"))) + .andExpect(jsonPath("$.subscriptionParameterList[0].name", is("frequency"))) + .andExpect(jsonPath("$.subscriptionParameterList[0].value", is("D"))) + .andExpect(jsonPath("$._links.eperson.href", Matchers.endsWith("eperson"))) + .andExpect(jsonPath("$._links.resource.href", Matchers.endsWith("resource"))) + .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id"))); + } finally { + SubscribeBuilder.deleteSubscription(idRef.get()); + } + } + + @Test + public void createSubscriptionByEPersonTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Map map = new HashMap<>(); + map.put("subscriptionType", "content"); + List> list = new ArrayList<>(); + Map sub_list = new HashMap<>(); + sub_list.put("name", "frequency"); + sub_list.put("value", "W"); + list.add(sub_list); + map.put("subscriptionParameterList", list); + + context.restoreAuthSystemState(); + + AtomicReference idRef = new AtomicReference(); + + try { + String tokenEPerson = getAuthToken(eperson.getEmail(), password); + getClient(tokenEPerson).perform(post("/api/core/subscriptions") + .param("resource", collection.getID().toString()) + .param("eperson_id", eperson.getID().toString()) + .content(new ObjectMapper().writeValueAsString(map)) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.subscriptionType", is("content"))) + .andExpect(jsonPath("$.subscriptionParameterList[0].name", is("frequency"))) + .andExpect(jsonPath("$.subscriptionParameterList[0].value", is("W"))) + .andExpect(jsonPath("$._links.eperson.href", Matchers.endsWith("eperson"))) + .andExpect(jsonPath("$._links.resource.href", Matchers.endsWith("resource"))) + .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id"))); + } finally { + SubscribeBuilder.deleteSubscription(idRef.get()); + } + } + + @Test + public void createSubscriptionForItemByEPersonTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Item item1 = ItemBuilder.createItem(context, collection) + .withTitle("Public item") + .build(); + + Map map = new HashMap<>(); + map.put("subscriptionType", "content"); + List> list = new ArrayList<>(); + Map sub_list = new HashMap<>(); + sub_list.put("name", "frequency"); + sub_list.put("value", "W"); + list.add(sub_list); + map.put("subscriptionParameterList", list); + + context.restoreAuthSystemState(); + + String tokenEPerson = getAuthToken(eperson.getEmail(), password); + getClient(tokenEPerson).perform(post("/api/core/subscriptions") + .param("resource", item1.getID().toString()) + .param("eperson_id", eperson.getID().toString()) + .content(new ObjectMapper().writeValueAsString(map)) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isBadRequest()); + } + + @Test + public void createSubscriptionForItemByAdminTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Item item1 = ItemBuilder.createItem(context, collection) + .withTitle("Public item") + .build(); + + Map map = new HashMap<>(); + map.put("subscriptionType", "content"); + List> list = new ArrayList<>(); + Map sub_list = new HashMap<>(); + sub_list.put("name", "frequency"); + sub_list.put("value", "W"); + list.add(sub_list); + map.put("subscriptionParameterList", list); + + context.restoreAuthSystemState(); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(post("/api/core/subscriptions") + .param("resource", item1.getID().toString()) + .param("eperson_id", eperson.getID().toString()) + .content(new ObjectMapper().writeValueAsString(map)) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isBadRequest()); + } + + @Test + public void createSubscriptionWrongResourceUUIDTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Map map = new HashMap<>(); + map.put("subscriptionType", "content"); + List> list = new ArrayList<>(); + Map sub_list = new HashMap<>(); + sub_list.put("name", "frequency"); + sub_list.put("value", "W"); + list.add(sub_list); + map.put("subscriptionParameterList", list); + + context.restoreAuthSystemState(); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(post("/api/core/subscriptions") + .param("resource", UUID.randomUUID().toString()) + .param("eperson_id", eperson.getID().toString()) + .content(new ObjectMapper().writeValueAsString(map)) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isBadRequest()); + } + + @Test + public void createSubscriptionMissingResourceUUIDTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Map map = new HashMap<>(); + map.put("subscriptionType", "content"); + List> list = new ArrayList<>(); + Map sub_list = new HashMap<>(); + sub_list.put("name", "frequency"); + sub_list.put("value", "W"); + list.add(sub_list); + map.put("subscriptionParameterList", list); + + context.restoreAuthSystemState(); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(post("/api/core/subscriptions") + .param("eperson_id", eperson.getID().toString()) + .content(new ObjectMapper().writeValueAsString(map)) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isUnprocessableEntity()); + } + + @Test + public void createSubscriptionWithWrongSubscriptionParameterNameTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Map map = new HashMap<>(); + map.put("subscriptionType", "content"); + List> list = new ArrayList<>(); + Map sub_list = new HashMap<>(); + sub_list.put("name", "TestName"); + sub_list.put("value", "X"); + list.add(sub_list); + map.put("subscriptionParameterList", list); + + context.restoreAuthSystemState(); + + String tokenEPerson = getAuthToken(eperson.getEmail(), password); + getClient(tokenEPerson).perform(post("/api/core/subscriptions") + .param("resource", collection.getID().toString()) + .param("eperson_id", eperson.getID().toString()) + .content(new ObjectMapper().writeValueAsString(map)) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isUnprocessableEntity()); + } + + @Test + public void createSubscriptionWithInvalidSubscriptionParameterValueTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Map map = new HashMap<>(); + map.put("subscriptionType", "content"); + List> list = new ArrayList<>(); + Map sub_list = new HashMap<>(); + sub_list.put("name", "frequency"); + sub_list.put("value", "X"); + list.add(sub_list); + map.put("subscriptionParameterList", list); + + context.restoreAuthSystemState(); + + String tokenEPerson = getAuthToken(eperson.getEmail(), password); + getClient(tokenEPerson).perform(post("/api/core/subscriptions") + .param("resource", collection.getID().toString()) + .param("eperson_id", eperson.getID().toString()) + .content(new ObjectMapper().writeValueAsString(map)) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isUnprocessableEntity()); + } + + @Test + public void createSubscriptionWithInvalidSubscriptionTypeValueTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Map map = new HashMap<>(); + map.put("subscriptionType", "InvalidValue"); + List> list = new ArrayList<>(); + Map sub_list = new HashMap<>(); + sub_list.put("name", "frequency"); + sub_list.put("value", "W"); + list.add(sub_list); + map.put("subscriptionParameterList", list); + + context.restoreAuthSystemState(); + + String tokenEPerson = getAuthToken(eperson.getEmail(), password); + getClient(tokenEPerson).perform(post("/api/core/subscriptions") + .param("resource", collection.getID().toString()) + .param("eperson_id", eperson.getID().toString()) + .content(new ObjectMapper().writeValueAsString(map)) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isUnprocessableEntity()); + } + + @Test + public void createSubscriptionInvalidJsonTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Map map = new HashMap<>(); + map.put("type", "content"); + List> list = new ArrayList<>(); + Map sub_list = new HashMap<>(); + sub_list.put("name", "frequency"); + sub_list.put("value", "daily"); + list.add(sub_list); + map.put("subscriptionParameterList", list); + + context.restoreAuthSystemState(); + + String tokenEPerson = getAuthToken(eperson.getEmail(), password); + getClient(tokenEPerson).perform(post("/api/core/subscriptions") + .param("resource", collection.getID().toString()) + .param("eperson_id", eperson.getID().toString()) + .content(new ObjectMapper().writeValueAsString(map)) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isUnprocessableEntity()); + } + + @Test + public void createSubscriptionPersonForAnotherPersonTest() throws Exception { + context.turnOffAuthorisationSystem(); + + EPerson user = EPersonBuilder.createEPerson(context) + .withEmail("user1@mail.com") + .withPassword(password) + .build(); + + Map map = new HashMap<>(); + map.put("subscriptionType", "content"); + List> list = new ArrayList<>(); + Map sub_list = new HashMap<>(); + sub_list.put("name", "frequency"); + sub_list.put("value", "D"); + list.add(sub_list); + map.put("subscriptionParameterList", list); + + context.restoreAuthSystemState(); + + String tokenEPerson = getAuthToken(eperson.getEmail(), password); + getClient(tokenEPerson).perform(post("/api/core/subscriptions") + .param("resource", collection.getID().toString()) + .param("eperson_id", user.getID().toString()) + .content(new ObjectMapper().writeValueAsString(map)) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isForbidden()); + } + + @Test + public void deleteSubscriptionUnauthorizedTest() throws Exception { + context.turnOffAuthorisationSystem(); + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("D"); + subscriptionParameterList.add(subscriptionParameter); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, + "content", collection, eperson, subscriptionParameterList).build(); + context.restoreAuthSystemState(); + + getClient().perform(delete("/api/core/subscriptions/" + subscription.getID())) + .andExpect(status().isUnauthorized()); + } + + @Test + public void deleteSubscriptionAdminTest() throws Exception { + context.turnOffAuthorisationSystem(); + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("D"); + subscriptionParameterList.add(subscriptionParameter); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, + "content", collection, eperson, subscriptionParameterList).build(); + context.restoreAuthSystemState(); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(delete("/api/core/subscriptions/" + subscription.getID())) + .andExpect(status().isNoContent()); + + getClient(tokenAdmin).perform(get("/api/core/subscriptions/" + subscription.getID())) + .andExpect(status().isNotFound()); + } + + @Test + public void deleteSubscriptionForbiddenTest() throws Exception { + context.turnOffAuthorisationSystem(); + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("D"); + subscriptionParameterList.add(subscriptionParameter); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, + "content", collection, admin, subscriptionParameterList).build(); + context.restoreAuthSystemState(); + + String tokenEPerson = getAuthToken(eperson.getEmail(), password); + getClient(tokenEPerson).perform(delete("/api/core/subscriptions/" + subscription.getID())) + .andExpect(status().isForbidden()); + } + + @Test + public void deleteSubscriptionNotFoundTest() throws Exception { + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(delete("/api/core/subscriptions/" + Integer.MAX_VALUE)) + .andExpect(status().isNotFound()); + } + + @Test + public void putSubscriptionUnauthorizedTest() throws Exception { + context.turnOffAuthorisationSystem(); + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("Parameter1"); + subscriptionParameter.setValue("ValueParameter1"); + subscriptionParameterList.add(subscriptionParameter); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, + "content", collection, admin, subscriptionParameterList).build(); + context.restoreAuthSystemState(); + + ObjectMapper objectMapper = new ObjectMapper(); + Map newSubscription = new HashMap<>(); + newSubscription.put("subscriptionType", "content"); + List> list = new ArrayList<>(); + Map sub_list = new HashMap<>(); + sub_list.put("name", "frequency"); + sub_list.put("value", "daily"); + list.add(sub_list); + newSubscription.put("subscriptionParameterList", list); + + getClient().perform(put("/api/core/subscriptions/" + subscription.getID()) + .content(objectMapper.writeValueAsString(newSubscription)) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isUnauthorized()); + } + + @Test + public void putSubscriptionForbiddenTest() throws Exception { + context.turnOffAuthorisationSystem(); + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("D"); + subscriptionParameterList.add(subscriptionParameter); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, + "content", collection, admin, subscriptionParameterList).build(); + context.restoreAuthSystemState(); + + ObjectMapper objectMapper = new ObjectMapper(); + Map newSubscription = new HashMap<>(); + newSubscription.put("subscriptionType", "content"); + List> list = new ArrayList<>(); + Map sub_list = new HashMap<>(); + sub_list.put("name", "frequency"); + sub_list.put("value", "W"); + list.add(sub_list); + newSubscription.put("subscriptionParameterList", list); + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(put("/api/core/subscriptions/" + subscription.getID()) + .content(objectMapper.writeValueAsString(newSubscription)) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isForbidden()); + } + + @Test + public void putSubscriptionTest() throws Exception { + context.turnOffAuthorisationSystem(); + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("D"); + subscriptionParameterList.add(subscriptionParameter); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, + "content", collection, eperson, subscriptionParameterList).build(); + context.restoreAuthSystemState(); + + ObjectMapper objectMapper = new ObjectMapper(); + Map newSubscription = new HashMap<>(); + newSubscription.put("subscriptionType", "content"); + List> list = new ArrayList<>(); + Map sub_list = new HashMap<>(); + sub_list.put("name", "frequency"); + sub_list.put("value", "W"); + list.add(sub_list); + newSubscription.put("subscriptionParameterList", list); + + String tokenSubscriber = getAuthToken(eperson.getEmail(), password); + getClient(tokenSubscriber).perform(put("/api/core/subscriptions/" + subscription.getID()) + .content(objectMapper.writeValueAsString(newSubscription)) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$.subscriptionType", is("content"))) + .andExpect(jsonPath("$.subscriptionParameterList[0].name", is("frequency"))) + .andExpect(jsonPath("$.subscriptionParameterList[0].value", is("W"))) + .andExpect(jsonPath("$._links.eperson.href", Matchers.endsWith("/eperson"))) + .andExpect(jsonPath("$._links.resource.href",Matchers.endsWith("/resource"))); + } + + @Test + public void putSubscriptionInvalidSubscriptionParameterNameTest() throws Exception { + context.turnOffAuthorisationSystem(); + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("D"); + subscriptionParameterList.add(subscriptionParameter); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, + "content", collection, eperson, subscriptionParameterList).build(); + context.restoreAuthSystemState(); + + ObjectMapper objectMapper = new ObjectMapper(); + Map newSubscription = new HashMap<>(); + newSubscription.put("subscriptionType", "content"); + List> list = new ArrayList<>(); + Map sub_list = new HashMap<>(); + sub_list.put("name", "InvalidName"); + sub_list.put("value", "W"); + list.add(sub_list); + newSubscription.put("subscriptionParameterList", list); + + String tokenSubscriber = getAuthToken(eperson.getEmail(), password); + getClient(tokenSubscriber).perform(put("/api/core/subscriptions/" + subscription.getID()) + .content(objectMapper.writeValueAsString(newSubscription)) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isUnprocessableEntity()); + } + + @Test + public void putSubscriptionInvalidSubscriptionParameterValueTest() throws Exception { + context.turnOffAuthorisationSystem(); + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("D"); + subscriptionParameterList.add(subscriptionParameter); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, + "content", collection, eperson, subscriptionParameterList).build(); + context.restoreAuthSystemState(); + + ObjectMapper objectMapper = new ObjectMapper(); + Map newSubscription = new HashMap<>(); + newSubscription.put("subscriptionType", "content"); + List> list = new ArrayList<>(); + Map sub_list = new HashMap<>(); + sub_list.put("name", "frequency"); + sub_list.put("value", "Y"); + list.add(sub_list); + newSubscription.put("subscriptionParameterList", list); + + String tokenSubscriber = getAuthToken(eperson.getEmail(), password); + getClient(tokenSubscriber).perform(put("/api/core/subscriptions/" + subscription.getID()) + .content(objectMapper.writeValueAsString(newSubscription)) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isUnprocessableEntity()); + } + + @Test + public void putSubscriptionInvalidSubscriptionTypeValueTest() throws Exception { + context.turnOffAuthorisationSystem(); + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("D"); + subscriptionParameterList.add(subscriptionParameter); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, + "content", collection, eperson, subscriptionParameterList).build(); + context.restoreAuthSystemState(); + + ObjectMapper objectMapper = new ObjectMapper(); + Map newSubscription = new HashMap<>(); + newSubscription.put("subscriptionType", "InvalidType"); + List> list = new ArrayList<>(); + Map sub_list = new HashMap<>(); + sub_list.put("name", "frequency"); + sub_list.put("value", "D"); + list.add(sub_list); + newSubscription.put("subscriptionParameterList", list); + + String tokenSubscriber = getAuthToken(eperson.getEmail(), password); + getClient(tokenSubscriber).perform(put("/api/core/subscriptions/" + subscription.getID()) + .content(objectMapper.writeValueAsString(newSubscription)) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isUnprocessableEntity()); + } + + @Test + public void putSubscriptionAdminTest() throws Exception { + context.turnOffAuthorisationSystem(); + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("D"); + subscriptionParameterList.add(subscriptionParameter); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, + "content", collection, eperson, subscriptionParameterList).build(); + context.restoreAuthSystemState(); + + ObjectMapper objectMapper = new ObjectMapper(); + Map newSubscription = new HashMap<>(); + newSubscription.put("subscriptionType", "content"); + List> list = new ArrayList<>(); + Map sub_list = new HashMap<>(); + sub_list.put("name", "frequency"); + sub_list.put("value", "W"); + list.add(sub_list); + newSubscription.put("subscriptionParameterList", list); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(put("/api/core/subscriptions/" + subscription.getID()) + .content(objectMapper.writeValueAsString(newSubscription)) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$.subscriptionType", is("content"))) + .andExpect(jsonPath("$.subscriptionParameterList[0].name", is("frequency"))) + .andExpect(jsonPath("$.subscriptionParameterList[0].value", is("W"))) + .andExpect(jsonPath("$._links.eperson.href", Matchers.endsWith("/eperson"))) + .andExpect(jsonPath("$._links.resource.href", Matchers.endsWith("/resource"))); + } + + @Test + public void linkedEpersonOfSubscriptionAdminTest() throws Exception { + context.turnOffAuthorisationSystem(); + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("M"); + subscriptionParameterList.add(subscriptionParameter); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, + "content", collection, eperson, subscriptionParameterList).build(); + context.restoreAuthSystemState(); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/core/subscriptions/" + subscription.getID() + "/eperson")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", is(EPersonMatcher.matchEPersonEntry(eperson)))); + } + + @Test + public void linkedEpersonOfSubscriptionTest() throws Exception { + context.turnOffAuthorisationSystem(); + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("M"); + subscriptionParameterList.add(subscriptionParameter); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, + "content", collection, eperson, subscriptionParameterList).build(); + context.restoreAuthSystemState(); + + String tokenEPerson = getAuthToken(eperson.getEmail(), password); + getClient(tokenEPerson).perform(get("/api/core/subscriptions/" + subscription.getID() + "/eperson")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", is(EPersonMatcher.matchEPersonEntry(eperson)))); + } + + @Test + public void linkedEpersonOfSubscriptionUnauthorizedTest() throws Exception { + context.turnOffAuthorisationSystem(); + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("M"); + subscriptionParameterList.add(subscriptionParameter); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, + "content", collection, eperson, subscriptionParameterList).build(); + context.restoreAuthSystemState(); + + getClient().perform(get("/api/core/subscriptions/" + subscription.getID() + "/eperson")) + .andExpect(status().isUnauthorized()); + } + + @Test + public void linkedEpersonOfSubscriptionForbiddenTest() throws Exception { + context.turnOffAuthorisationSystem(); + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("W"); + subscriptionParameterList.add(subscriptionParameter); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, + "content", collection, admin, subscriptionParameterList).build(); + context.restoreAuthSystemState(); + + String tokenEPerson = getAuthToken(eperson.getEmail(), password); + getClient(tokenEPerson).perform(get("/api/core/subscriptions/" + subscription.getID() + "/eperson")) + .andExpect(status().isForbidden()); + } + + @Test + public void linkedEpersonOfSubscriptionNotFoundTest() throws Exception { + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/core/subscriptions/" + Integer.MAX_VALUE + "/eperson")) + .andExpect(status().isNotFound()); + } + + @Test + public void linkedDSpaceObjectOfSubscriptionAdminTest() throws Exception { + context.turnOffAuthorisationSystem(); + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("W"); + subscriptionParameterList.add(subscriptionParameter); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, + "content", collection, eperson, subscriptionParameterList).build(); + context.restoreAuthSystemState(); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/core/subscriptions/" + subscription.getID() + "/resource")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.uuid", Matchers.is(collection.getID().toString()))) + .andExpect(jsonPath("$.name", Matchers.is(collection.getName()))) + .andExpect(jsonPath("$.type", Matchers.is("collection"))); + } + + @Test + public void linkedDSpaceObjectOfSubscriptionTest() throws Exception { + context.turnOffAuthorisationSystem(); + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("W"); + subscriptionParameterList.add(subscriptionParameter); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, + "content", collection, eperson, subscriptionParameterList).build(); + context.restoreAuthSystemState(); + + String tokenAdmin = getAuthToken(eperson.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/core/subscriptions/" + subscription.getID() + "/resource")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.uuid", Matchers.is(collection.getID().toString()))) + .andExpect(jsonPath("$.name", Matchers.is(collection.getName()))) + .andExpect(jsonPath("$.type", Matchers.is("collection"))); + } + + @Test + public void linkedDSpaceObjectOfSubscriptionUnauthorizedTest() throws Exception { + context.turnOffAuthorisationSystem(); + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("W"); + subscriptionParameterList.add(subscriptionParameter); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, + "content", collection, eperson, subscriptionParameterList).build(); + context.restoreAuthSystemState(); + + getClient().perform(get("/api/core/subscriptions/" + subscription.getID() + "/resource")) + .andExpect(status().isUnauthorized()); + } + + @Test + public void linkedDSpaceObjectOfSubscriptionForbiddenTest() throws Exception { + context.turnOffAuthorisationSystem(); + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("D"); + subscriptionParameterList.add(subscriptionParameter); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, + "content", collection, admin, subscriptionParameterList).build(); + context.restoreAuthSystemState(); + + String tokenEPerson = getAuthToken(eperson.getEmail(), password); + getClient(tokenEPerson).perform(get("/api/core/subscriptions/" + subscription.getID() + "/resource")) + .andExpect(status().isForbidden()); + } + + @Test + public void linkedDSpaceObjectAndRestrictedAccessAfterYouHaveSubscriptionToItTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1 Test") + .withSubmitterGroup(eperson) + .build(); + + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("D"); + subscriptionParameterList.add(subscriptionParameter); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, + "content", col1, eperson, subscriptionParameterList).build(); + context.restoreAuthSystemState(); + + String tokenEPerson = getAuthToken(eperson.getEmail(), password); + getClient(tokenEPerson).perform(get("/api/core/subscriptions/" + subscription.getID() + "/resource")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.uuid", Matchers.is(col1.getID().toString()))) + .andExpect(jsonPath("$.name", Matchers.is(col1.getName()))) + .andExpect(jsonPath("$.type", Matchers.is("collection"))); + + context.turnOffAuthorisationSystem(); + // remove all policies for col1 + resourcePolicyService.removeAllPolicies(context, col1); + context.restoreAuthSystemState(); + + // prove that col1 become not accessible + getClient(tokenEPerson).perform(get("/api/core/subscriptions/" + subscription.getID() + "/resource")) + .andExpect(status().isNoContent()); + } + + @Test + public void linkedDSpaceObjectOfSubscriptionNotFoundTest() throws Exception { + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/core/subscriptions/" + Integer.MAX_VALUE + "/resource")) + .andExpect(status().isNotFound()); + } + +} \ No newline at end of file diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SupervisionOrderRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SupervisionOrderRestRepositoryIT.java new file mode 100644 index 0000000000..27b436b6bc --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SupervisionOrderRestRepositoryIT.java @@ -0,0 +1,1501 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the 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 com.jayway.jsonpath.JsonPath.read; +import static org.dspace.app.rest.matcher.SupervisionOrderMatcher.matchSuperVisionOrder; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.io.InputStream; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import javax.ws.rs.core.MediaType; + +import org.dspace.app.rest.matcher.WorkflowItemMatcher; +import org.dspace.app.rest.matcher.WorkspaceItemMatcher; +import org.dspace.app.rest.model.patch.ReplaceOperation; +import org.dspace.app.rest.repository.SupervisionOrderRestRepository; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.EPersonBuilder; +import org.dspace.builder.GroupBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.builder.SupervisionOrderBuilder; +import org.dspace.builder.WorkflowItemBuilder; +import org.dspace.builder.WorkspaceItemBuilder; +import org.dspace.content.Collection; +import org.dspace.content.Item; +import org.dspace.content.WorkspaceItem; +import org.dspace.content.service.InstallItemService; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.Group; +import org.dspace.supervision.SupervisionOrder; +import org.dspace.supervision.service.SupervisionOrderService; +import org.dspace.workflow.WorkflowItem; +import org.hamcrest.Matchers; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Integration test against class {@link SupervisionOrderRestRepository}. + * + * @author Mohamed Eskander (mohamed.eskander at 4science.it) + */ +public class SupervisionOrderRestRepositoryIT extends AbstractControllerIntegrationTest { + + @Autowired + private SupervisionOrderService supervisionOrderService; + + @Autowired + private InstallItemService installItemService; + + @Before + public void init() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + context.restoreAuthSystemState(); + } + + @Test + public void findAllByAnonymousUserTest() throws Exception { + getClient().perform(get("/api/core/supervisionorders/")) + .andExpect(status().isUnauthorized()); + } + + @Test + public void findAllByNotAdminUserTest() throws Exception { + + String authToken = getAuthToken(eperson.getEmail(), password); + + getClient(authToken).perform(get("/api/core/supervisionorders/")) + .andExpect(status().isForbidden()); + } + + @Test + public void findAllByAdminTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + Collection col1 = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + Item item = + ItemBuilder.createItem(context, col1) + .withTitle("item title") + .build(); + + Group groupA = GroupBuilder.createGroup(context) + .withName("group A") + .addMember(admin) + .build(); + + Group groupB = GroupBuilder.createGroup(context) + .withName("group B") + .addMember(eperson) + .build(); + + SupervisionOrder supervisionOrderOne = + SupervisionOrderBuilder.createSupervisionOrder(context, item, groupA) + .build(); + + SupervisionOrder supervisionOrderTwo = + SupervisionOrderBuilder.createSupervisionOrder(context, item, groupB) + .build(); + + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + + getClient(adminToken).perform(get("/api/core/supervisionorders/")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(2))) + .andExpect(jsonPath("$._embedded.supervisionorders", + containsInAnyOrder( + matchSuperVisionOrder(supervisionOrderOne), + matchSuperVisionOrder(supervisionOrderTwo) + ))) + .andExpect(jsonPath("$._links.self.href", containsString("/api/core/supervisionorders"))); + } + + @Test + public void findOneByAnonymousUserTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + Collection col1 = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + Item item = + ItemBuilder.createItem(context, col1) + .withTitle("item title") + .build(); + + Group group = + GroupBuilder.createGroup(context) + .withName("group") + .addMember(eperson) + .build(); + + SupervisionOrder supervisionOrder = + SupervisionOrderBuilder.createSupervisionOrder(context, item, group) + .build(); + + context.restoreAuthSystemState(); + + getClient().perform(get("/api/core/supervisionorders/" + supervisionOrder.getID())) + .andExpect(status().isUnauthorized()); + } + + @Test + public void findOneByNotAdminUserTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + Collection col1 = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + Item item = + ItemBuilder.createItem(context, col1) + .withTitle("item title") + .build(); + + Group group = + GroupBuilder.createGroup(context) + .withName("group") + .addMember(eperson) + .build(); + + SupervisionOrder supervisionOrder = + SupervisionOrderBuilder.createSupervisionOrder(context, item, group) + .build(); + + context.restoreAuthSystemState(); + + String authToken = getAuthToken(eperson.getEmail(), password); + + getClient(authToken).perform(get("/api/core/supervisionorders/" + supervisionOrder.getID())) + .andExpect(status().isForbidden()); + } + + @Test + public void findOneByAdminTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + Collection col1 = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + Item item = + ItemBuilder.createItem(context, col1) + .withTitle("item title") + .build(); + + Group group = + GroupBuilder.createGroup(context) + .withName("group") + .addMember(eperson) + .build(); + + SupervisionOrder supervisionOrder = + SupervisionOrderBuilder.createSupervisionOrder(context, item, group) + .build(); + + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + + getClient(adminToken).perform(get("/api/core/supervisionorders/" + supervisionOrder.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", matchSuperVisionOrder(supervisionOrder))); + } + + @Test + public void findOneByAdminButNotFoundTest() throws Exception { + int fakeId = 12354326; + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/core/supervisionorders/" + fakeId)) + .andExpect(status().isNotFound()); + } + + @Test + public void findByItemByAnonymousUserTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + Collection col1 = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + Item item = + ItemBuilder.createItem(context, col1) + .withTitle("item title") + .build(); + + Group group = + GroupBuilder.createGroup(context) + .withName("group") + .addMember(eperson) + .build(); + + SupervisionOrderBuilder.createSupervisionOrder(context, item, group).build(); + + context.restoreAuthSystemState(); + + getClient().perform(get("/api/core/supervisionorders/search/byItem") + .param("uuid", item.getID().toString())) + .andExpect(status().isUnauthorized()); + } + + @Test + public void findByItemByNotAdminUserTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + Collection col1 = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + Item item = + ItemBuilder.createItem(context, col1) + .withTitle("item title") + .build(); + + Group group = + GroupBuilder.createGroup(context) + .withName("group") + .addMember(eperson) + .build(); + + SupervisionOrderBuilder.createSupervisionOrder(context, item, group).build(); + + context.restoreAuthSystemState(); + + String authToken = getAuthToken(eperson.getEmail(), password); + + getClient(authToken).perform(get("/api/core/supervisionorders/search/byItem") + .param("uuid", item.getID().toString())) + .andExpect(status().isForbidden()); + } + + @Test + public void findByItemByAdminButNotFoundItemTest() throws Exception { + String fakeItemId = "d9dcf4c3-093d-413e-a538-93d8589d3ea6"; + + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/core/supervisionorders/search/byItem") + .param("uuid", fakeItemId)) + .andExpect(status().isNotFound()); + } + + @Test + public void findByItemByAdminTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = + CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + Item itemOne = + ItemBuilder.createItem(context, col1) + .withTitle("item title") + .build(); + + Group groupA = + GroupBuilder.createGroup(context) + .withName("group A") + .addMember(admin) + .build(); + + Group groupB = + GroupBuilder.createGroup(context) + .withName("group B") + .addMember(eperson) + .build(); + + SupervisionOrder supervisionOrderOne = + SupervisionOrderBuilder.createSupervisionOrder(context, itemOne, groupA) + .build(); + + SupervisionOrder supervisionOrderTwo = + SupervisionOrderBuilder.createSupervisionOrder(context, itemOne, groupB) + .build(); + + Item itemTwo = + ItemBuilder.createItem(context, col1) + .withTitle("item two title") + .build(); + + SupervisionOrder supervisionOrderItemTwo = + SupervisionOrderBuilder.createSupervisionOrder(context, itemTwo, groupA) + .build(); + + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + + getClient(adminToken).perform(get("/api/core/supervisionorders/search/byItem") + .param("uuid", itemOne.getID().toString())) + .andExpect(jsonPath("$.page.totalElements", is(2))) + .andExpect(jsonPath("$._embedded.supervisionorders", containsInAnyOrder( + matchSuperVisionOrder(supervisionOrderOne), + matchSuperVisionOrder(supervisionOrderTwo) + ))) + .andExpect(jsonPath("$._links.self.href", containsString( + "/api/core/supervisionorders/search/byItem?uuid=" + itemOne.getID())) + ); + + getClient(adminToken).perform(get("/api/core/supervisionorders/search/byItem") + .param("uuid", itemTwo.getID().toString())) + .andExpect(jsonPath("$.page.totalElements", is(1))) + .andExpect(jsonPath("$._embedded.supervisionorders", contains( + matchSuperVisionOrder(supervisionOrderItemTwo) + ))) + .andExpect(jsonPath("$._links.self.href", containsString( + "/api/core/supervisionorders/search/byItem?uuid=" + itemTwo.getID())) + ); + } + + @Test + public void createByAnonymousUserTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = + CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + Item item = + ItemBuilder.createItem(context, col1) + .withTitle("item title") + .build(); + + Group group = + GroupBuilder.createGroup(context) + .withName("group") + .addMember(eperson) + .build(); + + context.restoreAuthSystemState(); + + getClient().perform(post("/api/core/supervisionorders/") + .param("uuid", item.getID().toString()) + .param("group", group.getID().toString()) + .param("type", "EDITOR") + .contentType(contentType)) + .andExpect(status().isUnauthorized()); + } + + @Test + public void createByNotAdminUserTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = + CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + Item item = + ItemBuilder.createItem(context, col1) + .withTitle("item title") + .build(); + + Group group = + GroupBuilder.createGroup(context) + .withName("group") + .addMember(eperson) + .build(); + + context.restoreAuthSystemState(); + + String authToken = getAuthToken(eperson.getEmail(), password); + getClient(authToken).perform(post("/api/core/supervisionorders/") + .param("uuid", item.getID().toString()) + .param("group", group.getID().toString()) + .param("type", "EDITOR") + .contentType(contentType)) + .andExpect(status().isForbidden()); + } + + @Test + public void createByAdminButMissingParametersTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = + CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + Item itemOne = + ItemBuilder.createItem(context, col1) + .withTitle("item title") + .build(); + + Group groupA = + GroupBuilder.createGroup(context) + .withName("group A") + .addMember(eperson) + .build(); + + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + + getClient(adminToken).perform(post("/api/core/supervisionorders/") + .param("group", groupA.getID().toString()) + .param("type", "EDITOR") + .contentType(contentType)) + .andExpect(status().isBadRequest()); + + getClient(adminToken).perform(post("/api/core/supervisionorders/") + .param("uuid", itemOne.getID().toString()) + .param("type", "EDITOR") + .contentType(contentType)) + .andExpect(status().isBadRequest()); + + getClient(adminToken).perform(post("/api/core/supervisionorders/") + .param("uuid", itemOne.getID().toString()) + .param("group", groupA.getID().toString()) + .contentType(contentType)) + .andExpect(status().isBadRequest()); + } + + @Test + public void createByAdminButIncorrectTypeParameterTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + Collection col1 = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + Item item = + ItemBuilder.createItem(context, col1) + .withTitle("item title") + .build(); + + Group group = + GroupBuilder.createGroup(context) + .withName("group") + .addMember(eperson) + .build(); + + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(post("/api/core/supervisionorders/") + .param("uuid", item.getID().toString()) + .param("group", group.getID().toString()) + .param("type", "WRONG") + .contentType(contentType)) + .andExpect(status().isBadRequest()); + } + + @Test + public void createByAdminButNotFoundItemOrGroupTest() throws Exception { + + String fakeItemId = "d9dcf4c3-093d-413e-a538-93d8589d3ea6"; + String fakeGroupId = "d9dcf4c3-093d-413e-a538-93d8589d3ea6"; + + context.turnOffAuthorisationSystem(); + + Collection col1 = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + Item itemOne = + ItemBuilder.createItem(context, col1) + .withTitle("item title") + .build(); + + Group groupA = GroupBuilder.createGroup(context) + .withName("group A") + .addMember(eperson) + .build(); + + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + + getClient(adminToken).perform(post("/api/core/supervisionorders/") + .param("uuid", fakeItemId) + .param("group", groupA.getID().toString()) + .param("type", "EDITOR") + .contentType(contentType)) + .andExpect(status().isUnprocessableEntity()); + + getClient(adminToken).perform(post("/api/core/supervisionorders/") + .param("uuid", itemOne.getID().toString()) + .param("group", fakeGroupId) + .param("type", "EDITOR") + .contentType(contentType)) + .andExpect(status().isUnprocessableEntity()); + } + + @Test + public void createTheSameSupervisionOrderTwiceTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = + CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + WorkspaceItem workspaceItem = + WorkspaceItemBuilder.createWorkspaceItem(context, col1) + .withTitle("Workspace Item 1") + .withIssueDate("2017-10-17") + .withSubject("ExtraEntry") + .grantLicense() + .build(); + + Item itemOne = workspaceItem.getItem(); + + Group groupA = + GroupBuilder.createGroup(context) + .withName("group A") + .addMember(eperson) + .build(); + + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + + getClient(adminToken).perform(post("/api/core/supervisionorders/") + .param("uuid", itemOne.getID().toString()) + .param("group", groupA.getID().toString()) + .param("type", "EDITOR") + .contentType(contentType)) + .andExpect(status().isCreated()); + + getClient(adminToken).perform(post("/api/core/supervisionorders/") + .param("uuid", itemOne.getID().toString()) + .param("group", groupA.getID().toString()) + .param("type", "OBSERVER") + .contentType(contentType)) + .andExpect(status().isUnprocessableEntity()); + } + + @Test + public void createSupervisionOrderOnArchivedItemTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = + CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + // create archived item + Item itemOne = + ItemBuilder.createItem(context, col1) + .withTitle("item title") + .withIssueDate("2017-10-17") + .build(); + + Group groupA = + GroupBuilder.createGroup(context) + .withName("group A") + .addMember(eperson) + .build(); + + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + + getClient(adminToken).perform(post("/api/core/supervisionorders/") + .param("uuid", itemOne.getID().toString()) + .param("group", groupA.getID().toString()) + .param("type", "OBSERVER") + .contentType(contentType)) + .andExpect(status().isUnprocessableEntity()); + } + + @Test + public void createByAdminTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + EPerson userA = + EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withEmail("test1@email.com") + .withPassword(password) + .build(); + + EPerson userB = + EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withEmail("test2@email.com") + .withPassword(password) + .build(); + + EPerson userC = + EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withEmail("test3@email.com") + .withPassword(password) + .build(); + + parentCommunity = + CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection publications = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Publications") + .withEntityType("Publication") + .build(); + + InputStream pdf = getClass().getResourceAsStream("simple-article.pdf"); + + WorkspaceItem witem = + WorkspaceItemBuilder.createWorkspaceItem(context, publications) + .withTitle("Workspace Item 1") + .withIssueDate("2017-10-17") + .withSubject("ExtraEntry") + .withFulltext("simple-article.pdf", "/local/path/simple-article.pdf", pdf) + .grantLicense() + .build(); + + Item itemOne = witem.getItem(); + + Group groupA = + GroupBuilder.createGroup(context) + .withName("group A") + .addMember(userA) + .build(); + + Group groupB = + GroupBuilder.createGroup(context) + .withName("group B") + .addMember(userB) + .build(); + + Group groupC = + GroupBuilder.createGroup(context) + .withName("group C") + .addMember(userC) + .build(); + + context.restoreAuthSystemState(); + + AtomicInteger supervisionOrderIdOne = new AtomicInteger(); + AtomicInteger supervisionOrderIdTwo = new AtomicInteger(); + AtomicInteger supervisionOrderIdThree = new AtomicInteger(); + + String adminToken = getAuthToken(admin.getEmail(), password); + + getClient(adminToken).perform(post("/api/core/supervisionorders/") + .param("uuid", itemOne.getID().toString()) + .param("group", groupA.getID().toString()) + .param("type", "EDITOR") + .contentType(contentType)) + .andExpect(status().isCreated()) + .andDo(result -> supervisionOrderIdOne + .set(read(result.getResponse().getContentAsString(), "$.id"))); + + getClient(adminToken).perform(post("/api/core/supervisionorders/") + .param("uuid", itemOne.getID().toString()) + .param("group", groupB.getID().toString()) + .param("type", "OBSERVER") + .contentType(contentType)) + .andExpect(status().isCreated()) + .andDo(result -> supervisionOrderIdTwo + .set(read(result.getResponse().getContentAsString(), "$.id"))); + + getClient(adminToken).perform(post("/api/core/supervisionorders/") + .param("uuid", itemOne.getID().toString()) + .param("group", groupC.getID().toString()) + .param("type", "NONE") + .contentType(contentType)) + .andExpect(status().isCreated()) + .andDo(result -> supervisionOrderIdThree + .set(read(result.getResponse().getContentAsString(), "$.id"))); + + SupervisionOrder supervisionOrderOne = + supervisionOrderService.find(context, supervisionOrderIdOne.get()); + + SupervisionOrder supervisionOrderTwo = + supervisionOrderService.find(context, supervisionOrderIdTwo.get()); + + SupervisionOrder supervisionOrderThree = + supervisionOrderService.find(context, supervisionOrderIdThree.get()); + + getClient(adminToken).perform(get("/api/core/supervisionorders/" + supervisionOrderOne.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", + matchSuperVisionOrder(context.reloadEntity(supervisionOrderOne)))); + + getClient(adminToken).perform(get("/api/core/supervisionorders/" + supervisionOrderTwo.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", + matchSuperVisionOrder(context.reloadEntity(supervisionOrderTwo)))); + + getClient(adminToken).perform(get("/api/core/supervisionorders/" + supervisionOrderThree.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", + matchSuperVisionOrder(context.reloadEntity(supervisionOrderThree)))); + + String authTokenA = getAuthToken(userA.getEmail(), password); + String authTokenB = getAuthToken(userB.getEmail(), password); + String authTokenC = getAuthToken(userC.getEmail(), password); + + String patchBody = getPatchContent(List.of( + new ReplaceOperation("/sections/traditionalpageone/dc.title/0", Map.of("value", "New Title")) + )); + + // update title of workspace item by userA is Ok + getClient(authTokenA).perform(patch("/api/submission/workspaceitems/" + witem.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.errors").doesNotExist()) + .andExpect(jsonPath("$", + // check the new title and untouched values + Matchers.is(WorkspaceItemMatcher + .matchItemWithTitleAndDateIssuedAndSubject( + witem, + "New Title", "2017-10-17", + "ExtraEntry")))); + + getClient(authTokenA).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.errors").doesNotExist()) + .andExpect(jsonPath("$", + Matchers.is( + WorkspaceItemMatcher + .matchItemWithTitleAndDateIssuedAndSubject( + witem, + "New Title", "2017-10-17", + "ExtraEntry") + ))); + + // supervisor of a NONE type cannot see workspace item + getClient(authTokenC).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isForbidden()); + + + // update title of workspace item by userB is Forbidden + getClient(authTokenB).perform(patch("/api/submission/workspaceitems/" + witem.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isForbidden()); + + // supervisor of a NONE type cannot patch workspace item + getClient(authTokenC).perform(patch("/api/submission/workspaceitems/" + witem.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isForbidden()); + } + + @Test + public void deleteByAnonymousUserTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + Collection col1 = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + Item item = + ItemBuilder.createItem(context, col1) + .withTitle("item title") + .build(); + + Group group = + GroupBuilder.createGroup(context) + .withName("group") + .addMember(eperson) + .build(); + + SupervisionOrder supervisionOrder = + SupervisionOrderBuilder.createSupervisionOrder(context, item, group) + .build(); + + context.restoreAuthSystemState(); + + getClient().perform(delete("/api/core/supervisionorders/" + supervisionOrder.getID())) + .andExpect(status().isUnauthorized()); + } + + @Test + public void deleteByNotAdminUserTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + Collection col1 = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + Item item = + ItemBuilder.createItem(context, col1) + .withTitle("item title") + .build(); + + Group group = + GroupBuilder.createGroup(context) + .withName("group") + .addMember(eperson) + .build(); + + SupervisionOrder supervisionOrder = + SupervisionOrderBuilder.createSupervisionOrder(context, item, group) + .build(); + + context.restoreAuthSystemState(); + + String authToken = getAuthToken(eperson.getEmail(), password); + + getClient(authToken).perform(delete("/api/core/supervisionorders/" + supervisionOrder.getID())) + .andExpect(status().isForbidden()); + } + + @Test + public void deleteByAdminButNotFoundTest() throws Exception { + int fakeId = 12354326; + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(delete("/api/core/supervisionorders/" + fakeId)) + .andExpect(status().isNotFound()); + } + + @Test + public void deleteByAdminTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + Collection col1 = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + Item item = + ItemBuilder.createItem(context, col1) + .withTitle("item title") + .build(); + + Group group = + GroupBuilder.createGroup(context) + .withName("group") + .addMember(eperson) + .build(); + + SupervisionOrder supervisionOrder = + SupervisionOrderBuilder.createSupervisionOrder(context, item, group) + .build(); + + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + + getClient(adminToken).perform(get("/api/core/supervisionorders/" + supervisionOrder.getID())) + .andExpect(status().isOk()); + + getClient(adminToken).perform(delete("/api/core/supervisionorders/" + supervisionOrder.getID())) + .andExpect(status().isNoContent()); + + getClient(adminToken).perform(get("/api/core/supervisionorders/" + supervisionOrder.getID())) + .andExpect(status().isNotFound()); + + String patchBody = getPatchContent(List.of(new ReplaceOperation("/withdrawn", true))); + + getClient(getAuthToken(eperson.getEmail(), password)).perform(patch("/api/core/items/" + item.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isForbidden()); + + Assert.assertTrue(item.getResourcePolicies().stream() + .noneMatch(rp -> group.getID().equals(rp.getGroup().getID()))); + } + + @Test + public void deleteItemThenSupervisionOrderBeDeletedTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + Collection col1 = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + Item item = + ItemBuilder.createItem(context, col1) + .withTitle("item title") + .build(); + + Group group = + GroupBuilder.createGroup(context) + .withName("group") + .addMember(eperson) + .build(); + + SupervisionOrder supervisionOrder = + SupervisionOrderBuilder.createSupervisionOrder(context, item, group) + .build(); + + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + + getClient(adminToken).perform(get("/api/core/supervisionorders/" + supervisionOrder.getID())) + .andExpect(status().isOk()); + + getClient(adminToken).perform(delete("/api/core/items/" + item.getID())) + .andExpect(status().isNoContent()); + + getClient(adminToken).perform(get("/api/core/supervisionorders/" + supervisionOrder.getID())) + .andExpect(status().isNotFound()); + + } + + @Test + public void deleteGroupThenSupervisionOrderBeDeletedTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + Collection col1 = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + Item item = + ItemBuilder.createItem(context, col1) + .withTitle("item title") + .build(); + + Group group = + GroupBuilder.createGroup(context) + .withName("group") + .addMember(eperson) + .build(); + + SupervisionOrder supervisionOrder = + SupervisionOrderBuilder.createSupervisionOrder(context, item, group) + .build(); + + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + + getClient(adminToken).perform(get("/api/core/supervisionorders/" + supervisionOrder.getID())) + .andExpect(status().isOk()); + + getClient(adminToken).perform(delete("/api/eperson/groups/" + group.getID())) + .andExpect(status().isNoContent()); + + getClient(adminToken).perform(get("/api/core/supervisionorders/" + supervisionOrder.getID())) + .andExpect(status().isNotFound()); + + } + + @Test + public void deleteWorkspaceItemThenSupervisionOrderIsDeletedTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = + CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection publications = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Publications") + .withEntityType("Publication") + .build(); + + Group groupA = + GroupBuilder.createGroup(context) + .withName("group A") + .addMember(eperson) + .build(); + + WorkspaceItem witem = + WorkspaceItemBuilder.createWorkspaceItem(context, publications) + .withTitle("Workspace Item 1") + .withIssueDate("2017-10-17") + .withSubject("ExtraEntry") + .grantLicense() + .build(); + + context.restoreAuthSystemState(); + + AtomicReference idRef = new AtomicReference<>(); + String adminToken = getAuthToken(admin.getEmail(), password); + + getClient(adminToken).perform(post("/api/core/supervisionorders/") + .param("uuid", witem.getItem().getID().toString()) + .param("group", groupA.getID().toString()) + .param("type", "EDITOR") + .contentType(contentType)) + .andExpect(status().isCreated()) + .andDo(result -> idRef + .set(read(result.getResponse().getContentAsString(), "$.id"))); + + getClient(adminToken).perform(get("/api/core/supervisionorders/" + idRef.get())) + .andExpect(status().isOk()); + + getClient(adminToken).perform(delete("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isNoContent()); + + getClient(adminToken).perform(get("/api/core/supervisionorders/" + idRef.get())) + .andExpect(status().isNotFound()); + + } + + @Test + public void installWorkspaceItemThenSupervisionOrderIsDeletedTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = + CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection publications = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Publications") + .withEntityType("Publication") + .build(); + + EPerson userA = + EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withEmail("test1@email.com") + .withPassword(password) + .build(); + + Group groupA = + GroupBuilder.createGroup(context) + .withName("group A") + .addMember(userA) + .build(); + + Group groupB = + GroupBuilder.createGroup(context) + .withName("group B") + .addMember(eperson) + .build(); + + WorkspaceItem witem = + WorkspaceItemBuilder.createWorkspaceItem(context, publications) + .withTitle("Workspace Item 1") + .withIssueDate("2017-10-17") + .withSubject("ExtraEntry") + .grantLicense() + .build(); + + context.restoreAuthSystemState(); + + AtomicReference idRefOne = new AtomicReference<>(); + AtomicReference idRefTwo = new AtomicReference<>(); + String adminToken = getAuthToken(admin.getEmail(), password); + + getClient(adminToken).perform(post("/api/core/supervisionorders/") + .param("uuid", witem.getItem().getID().toString()) + .param("group", groupA.getID().toString()) + .param("type", "EDITOR") + .contentType(contentType)) + .andExpect(status().isCreated()) + .andDo(result -> idRefOne + .set(read(result.getResponse().getContentAsString(), "$.id"))); + + getClient(adminToken).perform(post("/api/core/supervisionorders/") + .param("uuid", witem.getItem().getID().toString()) + .param("group", groupB.getID().toString()) + .param("type", "EDITOR") + .contentType(contentType)) + .andExpect(status().isCreated()) + .andDo(result -> idRefTwo + .set(read(result.getResponse().getContentAsString(), "$.id"))); + + getClient(adminToken).perform(get("/api/core/supervisionorders/" + idRefOne.get())) + .andExpect(status().isOk()); + + getClient(adminToken).perform(get("/api/core/supervisionorders/" + idRefTwo.get())) + .andExpect(status().isOk()); + + // install item then supervision orders will be deleted + context.turnOffAuthorisationSystem(); + installItemService.installItem(context, context.reloadEntity(witem)); + context.restoreAuthSystemState(); + + getClient(adminToken).perform(get("/api/core/supervisionorders/" + idRefOne.get())) + .andExpect(status().isNotFound()); + + getClient(adminToken).perform(get("/api/core/supervisionorders/" + idRefTwo.get())) + .andExpect(status().isNotFound()); + + } + + @Test + public void createOnArchivedAndWithdrawnItemsNotAllowedTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = + CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection publications = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Publications") + .withEntityType("Publication") + .build(); + + EPerson userA = + EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withEmail("test1@email.com") + .withPassword(password) + .build(); + + Group groupA = + GroupBuilder.createGroup(context) + .withName("group A") + .addMember(userA) + .build(); + + Group groupB = + GroupBuilder.createGroup(context) + .withName("group B") + .addMember(eperson) + .build(); + + Item item = ItemBuilder.createItem(context, publications) + .withTitle("Workspace Item 1") + .withIssueDate("2017-10-17") + .withSubject("ExtraEntry") + .grantLicense() + .build(); + + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + + getClient(adminToken).perform(post("/api/core/supervisionorders/") + .param("uuid",item.getID().toString()) + .param("group", groupA.getID().toString()) + .param("type", "EDITOR") + .contentType(contentType)) + .andExpect(status().isUnprocessableEntity()); + + getClient(adminToken).perform(post("/api/core/supervisionorders/") + .param("uuid", item.getID().toString()) + .param("group", groupB.getID().toString()) + .param("type", "OBSERVER") + .contentType(contentType)) + .andExpect(status().isUnprocessableEntity()); + + + // withdraw the item, supervision order creation still not possible + + String patchBody = getPatchContent(List.of(new ReplaceOperation("/withdrawn", true))); + + getClient(adminToken).perform(patch("/api/core/items/" + item.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()); + + getClient(adminToken).perform(post("/api/core/supervisionorders/") + .param("uuid",item.getID().toString()) + .param("group", groupA.getID().toString()) + .param("type", "EDITOR") + .contentType(contentType)) + .andExpect(status().isUnprocessableEntity()); + + getClient(adminToken).perform(post("/api/core/supervisionorders/") + .param("uuid", item.getID().toString()) + .param("group", groupB.getID().toString()) + .param("type", "OBSERVER") + .contentType(contentType)) + .andExpect(status().isUnprocessableEntity()); + + } + + @Test + public void createSupervisionOnWorkspaceThenSubmitToWorkflowTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = + CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + EPerson reviewer = + EPersonBuilder.createEPerson(context) + .withEmail("reviewer1@example.com") + .withPassword(password) + .build(); + + Collection collection = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .withWorkflowGroup("reviewer", reviewer) + .build(); + + EPerson userA = + EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withEmail("userA@test.com") + .withPassword(password) + .build(); + + Group groupA = + GroupBuilder.createGroup(context) + .withName("group A") + .addMember(userA) + .build(); + + InputStream pdf = getClass().getResourceAsStream("simple-article.pdf"); + + WorkspaceItem witem = + WorkspaceItemBuilder.createWorkspaceItem(context, collection) + .withTitle("Workspace Item 1") + .withIssueDate("2017-10-17") + .withSubject("ExtraEntry") + .withFulltext("simple-article.pdf", "/local/path/simple-article.pdf", pdf) + .grantLicense() + .build(); + + context.restoreAuthSystemState(); + + // create a supervision order on workspaceItem to groupA + getClient(getAuthToken(admin.getEmail(), password)) + .perform(post("/api/core/supervisionorders/") + .param("uuid", witem.getItem().getID().toString()) + .param("group", groupA.getID().toString()) + .param("type", "EDITOR") + .contentType(contentType)) + .andExpect(status().isCreated()); + + String authTokenA = getAuthToken(userA.getEmail(), password); + + // a simple patch to update an existent metadata + String patchBody = + getPatchContent(List.of( + new ReplaceOperation("/sections/traditionalpageone/dc.title/0", Map.of("value", "New Title")) + )); + + // supervisor update the title of the workspaceItem + getClient(authTokenA).perform(patch("/api/submission/workspaceitems/" + witem.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.errors").doesNotExist()) + .andExpect(jsonPath("$", + // check the new title and untouched values + Matchers.is( + WorkspaceItemMatcher.matchItemWithTitleAndDateIssuedAndSubject( + witem, "New Title", "2017-10-17", "ExtraEntry" + )))); + + // supervisor check that title has been updated + getClient(authTokenA).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.errors").doesNotExist()) + .andExpect(jsonPath("$", Matchers.is( + WorkspaceItemMatcher.matchItemWithTitleAndDateIssuedAndSubject( + witem, "New Title", "2017-10-17", "ExtraEntry" + )))); + + AtomicReference idRef = new AtomicReference(); + try { + String adminToken = getAuthToken(admin.getEmail(), password); + String reviewerToken = getAuthToken(reviewer.getEmail(), password); + + // submit the workspaceitem to start the workflow + getClient(adminToken).perform(post(BASE_REST_SERVER_URL + "/api/workflow/workflowitems") + .content("/api/submission/workspaceitems/" + witem.getID()) + .contentType(textUriContentType)) + .andExpect(status().isCreated()) + .andDo(result -> + idRef.set(read(result.getResponse().getContentAsString(), "$.id"))); + + // supervisor can read the workflowitem + getClient(authTokenA) + .perform(get("/api/workflow/workflowitems/" + idRef.get())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$",Matchers.is(WorkflowItemMatcher.matchItemWithTitleAndDateIssued( + null, "New Title", "2017-10-17")))) + .andExpect(jsonPath("$.sections.defaultAC.discoverable", is(true))); + + // reviewer can read the workflowitem + getClient(reviewerToken) + .perform(get("/api/workflow/workflowitems/" + idRef.get())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$",Matchers.is(WorkflowItemMatcher.matchItemWithTitleAndDateIssued( + null, "New Title", "2017-10-17")))) + .andExpect(jsonPath("$.sections.defaultAC.discoverable", is(true))); + + // a simple patch to update an existent metadata + String patchBodyTwo = + getPatchContent(List.of(new ReplaceOperation("/metadata/dc.title", Map.of("value", "edited title")))); + + // supervisor can't edit item in workflow + getClient(authTokenA).perform(patch("/api/core/items/" + witem.getItem().getID()) + .content(patchBodyTwo) + .contentType(contentType)) + .andExpect(status().isForbidden()); + + // supervisor can't edit the workflow item + getClient(authTokenA).perform(patch("/api/workflow/workflowitems/" + idRef.get()) + .content(patchBody) + .contentType(contentType)) + .andExpect(status().isUnprocessableEntity()); + + } finally { + if (idRef.get() != null) { + WorkflowItemBuilder.deleteWorkflowItem(idRef.get()); + } + } + } + + @Test + public void supervisionOrderAddedToWorkflowItemThenSentBackToWorkspace() throws Exception { + + context.turnOffAuthorisationSystem(); + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withEntityType("Publication") + .withWorkflowGroup("reviewer", admin).build(); + + WorkflowItem workflowItem = WorkflowItemBuilder.createWorkflowItem(context, collection) + .withSubmitter(admin) + .withTitle("this is the title") + .withIssueDate("1982-12-17") + .grantLicense().build(); + + + EPerson userA = + EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withEmail("userA@test.com") + .withPassword(password) + .build(); + + Group groupA = + GroupBuilder.createGroup(context) + .withName("group A") + .addMember(userA) + .build(); + context.restoreAuthSystemState(); + + getClient(getAuthToken(admin.getEmail(), password)) + .perform(post("/api/core/supervisionorders/") + .param("uuid", workflowItem.getItem().getID().toString()) + .param("group", groupA.getID().toString()) + .param("type", "EDITOR") + .contentType(contentType)) + .andExpect(status().isCreated()); + + String workflowItemPatchBody = + getPatchContent(List.of(new ReplaceOperation("/metadata/dc.title", Map.of("value", "edited title")))); + + String authTokenA = getAuthToken(userA.getEmail(), password); + // supervisor can't edit item in workflow + getClient(authTokenA).perform(patch("/api/core/items/" + workflowItem.getItem().getID()) + .content(workflowItemPatchBody) + .contentType(contentType)) + .andExpect(status().isForbidden()); + + AtomicReference idRef = new AtomicReference<>(); + + try { + // Delete the workflowitem + String adminToken = getAuthToken(admin.getEmail(), password); + + getClient(adminToken).perform(delete("/api/workflow/workflowitems/" + workflowItem.getID())) + .andExpect(status().is(204)); + + getClient(adminToken).perform(get("/api/submission/workspaceitems/search/findBySubmitter") + .param("uuid", admin.getID().toString())) + .andExpect(status().isOk()) + .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), + "$._embedded.workspaceitems[0].id"))); + + String workspaceItemPatchBody = getPatchContent(List.of( + new ReplaceOperation("/sections/traditionalpageone/dc.title/0", Map.of("value", "New Title")) + )); + + getClient(authTokenA).perform(patch("/api/submission/workspaceitems/" + idRef.get()) + .content(workspaceItemPatchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()); + } finally { + Integer id = idRef.get(); + if (Objects.nonNull(id)) { + WorkspaceItemBuilder.deleteWorkspaceItem(id); + } + } + } +} \ No newline at end of file diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SystemWideAlertRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SystemWideAlertRestRepositoryIT.java new file mode 100644 index 0000000000..522c476704 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SystemWideAlertRestRepositoryIT.java @@ -0,0 +1,493 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the 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 com.jayway.jsonpath.JsonPath.read; +import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; +import static org.dspace.matcher.DateMatcher.dateMatcher; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.is; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.util.Calendar; +import java.util.Date; +import java.util.concurrent.atomic.AtomicReference; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.lang3.time.DateUtils; +import org.dspace.alerts.AllowSessionsEnum; +import org.dspace.alerts.SystemWideAlert; +import org.dspace.app.rest.model.SystemWideAlertRest; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.builder.SystemWideAlertBuilder; +import org.junit.Test; + +/** + * Test class to test the operations in the SystemWideAlertRestRepository + */ +public class SystemWideAlertRestRepositoryIT extends AbstractControllerIntegrationTest { + + @Test + public void findAllTest() throws Exception { + // Create two alert entries in the db to fully test the findAll method + // Note: It is not possible to create two alerts through the REST API + context.turnOffAuthorisationSystem(); + Date dateToNearestSecond = DateUtils.round(new Date(), Calendar.SECOND); + SystemWideAlert systemWideAlert1 = SystemWideAlertBuilder.createSystemWideAlert(context, "Test alert 1") + .withAllowSessions( + AllowSessionsEnum.ALLOW_CURRENT_SESSIONS_ONLY) + .withCountdownDate(dateToNearestSecond) + .isActive(true) + .build(); + + SystemWideAlert systemWideAlert2 = SystemWideAlertBuilder.createSystemWideAlert(context, "Test alert 2") + .withAllowSessions( + AllowSessionsEnum.ALLOW_ADMIN_SESSIONS_ONLY) + .withCountdownDate(null) + .isActive(false) + .build(); + context.restoreAuthSystemState(); + + String authToken = getAuthToken(admin.getEmail(), password); + + getClient(authToken).perform(get("/api/system/systemwidealerts/")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.systemwidealerts", containsInAnyOrder( + allOf( + hasJsonPath("$.alertId", is(systemWideAlert1.getID())), + hasJsonPath("$.message", is(systemWideAlert1.getMessage())), + hasJsonPath("$.allowSessions", is(systemWideAlert1.getAllowSessions().getValue())), + hasJsonPath("$.countdownTo", dateMatcher(dateToNearestSecond)), + hasJsonPath("$.active", is(systemWideAlert1.isActive())) + ), + allOf( + hasJsonPath("$.alertId", is(systemWideAlert2.getID())), + hasJsonPath("$.message", is(systemWideAlert2.getMessage())), + hasJsonPath("$.allowSessions", is(systemWideAlert2.getAllowSessions().getValue())), + hasJsonPath("$.countdownTo", is(systemWideAlert2.getCountdownTo())), + hasJsonPath("$.active", is(systemWideAlert2.isActive())) + ) + ))); + } + + @Test + public void findAllUnauthorizedTest() throws Exception { + // Create two alert entries in the db to fully test the findAll method + // Note: It is not possible to create two alerts through the REST API + context.turnOffAuthorisationSystem(); + Date countdownDate = new Date(); + SystemWideAlert systemWideAlert1 = SystemWideAlertBuilder.createSystemWideAlert(context, "Test alert 1") + .withAllowSessions( + AllowSessionsEnum.ALLOW_CURRENT_SESSIONS_ONLY) + .withCountdownDate(countdownDate) + .isActive(true) + .build(); + + SystemWideAlert systemWideAlert2 = SystemWideAlertBuilder.createSystemWideAlert(context, "Test alert 2") + .withAllowSessions( + AllowSessionsEnum.ALLOW_ADMIN_SESSIONS_ONLY) + .withCountdownDate(null) + .isActive(false) + .build(); + context.restoreAuthSystemState(); + + getClient().perform(get("/api/system/systemwidealerts/")) + .andExpect(status().isUnauthorized()); + + } + + @Test + public void findAllForbiddenTest() throws Exception { + // Create two alert entries in the db to fully test the findAll method + // Note: It is not possible to create two alerts through the REST API + context.turnOffAuthorisationSystem(); + Date countdownDate = new Date(); + SystemWideAlert systemWideAlert1 = SystemWideAlertBuilder.createSystemWideAlert(context, "Test alert 1") + .withAllowSessions( + AllowSessionsEnum.ALLOW_CURRENT_SESSIONS_ONLY) + .withCountdownDate(countdownDate) + .isActive(true) + .build(); + + SystemWideAlert systemWideAlert2 = SystemWideAlertBuilder.createSystemWideAlert(context, "Test alert 2") + .withAllowSessions( + AllowSessionsEnum.ALLOW_ADMIN_SESSIONS_ONLY) + .withCountdownDate(null) + .isActive(false) + .build(); + context.restoreAuthSystemState(); + + String authToken = getAuthToken(eperson.getEmail(), password); + getClient(authToken).perform(get("/api/system/systemwidealerts/")) + .andExpect(status().isForbidden()); + + } + + @Test + public void findOneTest() throws Exception { + // Create two alert entries in the db to fully test the findOne method + // Note: It is not possible to create two alerts through the REST API + context.turnOffAuthorisationSystem(); + Date dateToNearestSecond = DateUtils.round(new Date(), Calendar.SECOND); + SystemWideAlert systemWideAlert1 = SystemWideAlertBuilder.createSystemWideAlert(context, "Test alert 1") + .withAllowSessions( + AllowSessionsEnum.ALLOW_CURRENT_SESSIONS_ONLY) + .withCountdownDate(dateToNearestSecond) + .isActive(true) + .build(); + SystemWideAlert systemWideAlert2 = SystemWideAlertBuilder.createSystemWideAlert(context, "Test alert 2") + .withAllowSessions( + AllowSessionsEnum.ALLOW_ADMIN_SESSIONS_ONLY) + .withCountdownDate(null) + .isActive(false) + .build(); + context.restoreAuthSystemState(); + + + String authToken = getAuthToken(admin.getEmail(), password); + + // When the alert is active and the user is not an admin, the user will be able to see the alert + getClient(authToken).perform(get("/api/system/systemwidealerts/" + systemWideAlert1.getID())) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$", allOf( + hasJsonPath("$.alertId", is(systemWideAlert1.getID())), + hasJsonPath("$.message", is(systemWideAlert1.getMessage())), + hasJsonPath("$.allowSessions", + is(systemWideAlert1.getAllowSessions().getValue())), + hasJsonPath("$.countdownTo", + dateMatcher(dateToNearestSecond)), + hasJsonPath("$.active", is(systemWideAlert1.isActive())) + ) + )); + + } + + + @Test + public void findOneUnauthorizedTest() throws Exception { + // Create two alert entries in the db to fully test the findOne method + // Note: It is not possible to create two alerts through the REST API + context.turnOffAuthorisationSystem(); + Date dateToNearestSecond = DateUtils.round(new Date(), Calendar.SECOND); + SystemWideAlert systemWideAlert1 = SystemWideAlertBuilder.createSystemWideAlert(context, "Test alert 1") + .withAllowSessions( + AllowSessionsEnum.ALLOW_CURRENT_SESSIONS_ONLY) + .withCountdownDate(dateToNearestSecond) + .isActive(true) + .build(); + + SystemWideAlert systemWideAlert2 = SystemWideAlertBuilder.createSystemWideAlert(context, "Test alert 2") + .withAllowSessions( + AllowSessionsEnum.ALLOW_ADMIN_SESSIONS_ONLY) + .withCountdownDate(null) + .isActive(false) + .build(); + context.restoreAuthSystemState(); + + // When the alert is active and the user is not an admin, the user will be able to see the alert + getClient().perform(get("/api/system/systemwidealerts/" + systemWideAlert1.getID())) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$", allOf( + hasJsonPath("$.alertId", is(systemWideAlert1.getID())), + hasJsonPath("$.message", is(systemWideAlert1.getMessage())), + hasJsonPath("$.allowSessions", + is(systemWideAlert1.getAllowSessions().getValue())), + hasJsonPath("$.countdownTo", + dateMatcher(dateToNearestSecond)), + hasJsonPath("$.active", is(systemWideAlert1.isActive())) + ) + )); + + // When the alert is inactive and the user is not an admin, the user will not be able to see the presence of the + // alert and a 404 will be returned by the findOne endpoint + getClient().perform(get("/api/system/systemwidealerts/" + systemWideAlert2.getID())) + .andExpect(status().isNotFound()); + + } + + @Test + public void findOneForbiddenTest() throws Exception { + // Create two alert entries in the db to fully test the findOne method + // Note: It is not possible to create two alerts through the REST API + context.turnOffAuthorisationSystem(); + Date dateToNearestSecond = DateUtils.round(new Date(), Calendar.SECOND); + SystemWideAlert systemWideAlert1 = SystemWideAlertBuilder.createSystemWideAlert(context, "Test alert 1") + .withAllowSessions( + AllowSessionsEnum.ALLOW_CURRENT_SESSIONS_ONLY) + .withCountdownDate(dateToNearestSecond) + .isActive(true) + .build(); + + SystemWideAlert systemWideAlert2 = SystemWideAlertBuilder.createSystemWideAlert(context, "Test alert 2") + .withAllowSessions( + AllowSessionsEnum.ALLOW_ADMIN_SESSIONS_ONLY) + .withCountdownDate(null) + .isActive(false) + .build(); + context.restoreAuthSystemState(); + + String authToken = getAuthToken(eperson.getEmail(), password); + + + getClient(authToken).perform(get("/api/system/systemwidealerts/" + systemWideAlert1.getID())) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$", allOf( + hasJsonPath("$.alertId", is(systemWideAlert1.getID())), + hasJsonPath("$.message", is(systemWideAlert1.getMessage())), + hasJsonPath("$.allowSessions", + is(systemWideAlert1.getAllowSessions().getValue())), + hasJsonPath("$.countdownTo", + dateMatcher(dateToNearestSecond)), + hasJsonPath("$.active", is(systemWideAlert1.isActive())) + ) + )); + + // When the alert is inactive and the user is not an admin, the user will not be able to see the presence of the + // alert and a 404 will be returned by the findOne endpoint + getClient(authToken).perform(get("/api/system/systemwidealerts/" + systemWideAlert2.getID())) + .andExpect(status().isNotFound()); + + } + + @Test + public void findAllActiveTest() throws Exception { + // Create three alert entries in the db to fully test the findActive search method + // Note: It is not possible to create two alerts through the REST API + context.turnOffAuthorisationSystem(); + Date dateToNearestSecond = DateUtils.round(new Date(), Calendar.SECOND); + SystemWideAlert systemWideAlert1 = SystemWideAlertBuilder.createSystemWideAlert(context, "Test alert 1") + .withAllowSessions( + AllowSessionsEnum.ALLOW_CURRENT_SESSIONS_ONLY) + .withCountdownDate(dateToNearestSecond) + .isActive(true) + .build(); + + SystemWideAlert systemWideAlert2 = SystemWideAlertBuilder.createSystemWideAlert(context, "Test alert 2") + .withAllowSessions( + AllowSessionsEnum.ALLOW_ADMIN_SESSIONS_ONLY) + .withCountdownDate(null) + .isActive(false) + .build(); + + SystemWideAlert systemWideAlert3 = SystemWideAlertBuilder.createSystemWideAlert(context, "Test alert 3") + .withAllowSessions( + AllowSessionsEnum.ALLOW_ADMIN_SESSIONS_ONLY) + .withCountdownDate(null) + .isActive(true) + .build(); + context.restoreAuthSystemState(); + + getClient().perform(get("/api/system/systemwidealerts/search/active")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.systemwidealerts", containsInAnyOrder( + allOf( + hasJsonPath("$.alertId", is(systemWideAlert1.getID())), + hasJsonPath("$.message", is(systemWideAlert1.getMessage())), + hasJsonPath("$.allowSessions", is(systemWideAlert1.getAllowSessions().getValue())), + hasJsonPath("$.countdownTo", + dateMatcher(dateToNearestSecond)), + hasJsonPath("$.active", is(systemWideAlert1.isActive())) + ), + allOf( + hasJsonPath("$.alertId", is(systemWideAlert3.getID())), + hasJsonPath("$.message", is(systemWideAlert3.getMessage())), + hasJsonPath("$.allowSessions", is(systemWideAlert3.getAllowSessions().getValue())), + hasJsonPath("$.countdownTo", is(systemWideAlert3.getCountdownTo())), + hasJsonPath("$.active", is(systemWideAlert3.isActive())) + ) + ))); + + } + + @Test + public void createTest() throws Exception { + Date dateToNearestSecond = DateUtils.round(new Date(), Calendar.SECOND); + + SystemWideAlertRest systemWideAlertRest = new SystemWideAlertRest(); + systemWideAlertRest.setMessage("Alert test message"); + systemWideAlertRest.setCountdownTo(dateToNearestSecond); + systemWideAlertRest.setAllowSessions(AllowSessionsEnum.ALLOW_CURRENT_SESSIONS_ONLY.getValue()); + systemWideAlertRest.setActive(true); + + ObjectMapper mapper = new ObjectMapper(); + + String authToken = getAuthToken(admin.getEmail(), password); + + AtomicReference idRef = new AtomicReference<>(); + + + getClient(authToken).perform(post("/api/system/systemwidealerts/") + .content(mapper.writeValueAsBytes(systemWideAlertRest)) + .contentType(contentType)) + .andExpect(status().isCreated()) + .andExpect( + jsonPath("$", allOf( + hasJsonPath("$.alertId"), + hasJsonPath("$.message", is(systemWideAlertRest.getMessage())), + hasJsonPath("$.allowSessions", + is(systemWideAlertRest.getAllowSessions())), + hasJsonPath("$.countdownTo", + dateMatcher(dateToNearestSecond)), + hasJsonPath("$.active", is(systemWideAlertRest.isActive())) + ) + )) + .andDo(result -> idRef + .set((read(result.getResponse().getContentAsString(), "$.alertId")))); + + getClient(authToken).perform(get("/api/system/systemwidealerts/" + idRef.get())) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$", allOf( + hasJsonPath("$.alertId", is(idRef.get())), + hasJsonPath("$.message", is(systemWideAlertRest.getMessage())), + hasJsonPath("$.allowSessions", is(systemWideAlertRest.getAllowSessions())), + hasJsonPath("$.countdownTo", + dateMatcher(dateToNearestSecond)), + hasJsonPath("$.active", is(systemWideAlertRest.isActive())) + ) + )); + + } + + @Test + public void createForbiddenTest() throws Exception { + + SystemWideAlertRest systemWideAlertRest = new SystemWideAlertRest(); + systemWideAlertRest.setMessage("Alert test message"); + systemWideAlertRest.setCountdownTo(new Date()); + systemWideAlertRest.setAllowSessions(AllowSessionsEnum.ALLOW_CURRENT_SESSIONS_ONLY.getValue()); + systemWideAlertRest.setActive(true); + + ObjectMapper mapper = new ObjectMapper(); + + String authToken = getAuthToken(eperson.getEmail(), password); + + + getClient(authToken).perform(post("/api/system/systemwidealerts/") + .content(mapper.writeValueAsBytes(systemWideAlertRest)) + .contentType(contentType)) + .andExpect(status().isForbidden()); + } + + @Test + public void createUnAuthorizedTest() throws Exception { + + SystemWideAlertRest systemWideAlertRest = new SystemWideAlertRest(); + systemWideAlertRest.setMessage("Alert test message"); + systemWideAlertRest.setCountdownTo(new Date()); + systemWideAlertRest.setAllowSessions(AllowSessionsEnum.ALLOW_CURRENT_SESSIONS_ONLY.getValue()); + systemWideAlertRest.setActive(true); + + ObjectMapper mapper = new ObjectMapper(); + + getClient().perform(post("/api/system/systemwidealerts/") + .content(mapper.writeValueAsBytes(systemWideAlertRest)) + .contentType(contentType)) + .andExpect(status().isUnauthorized()); + + } + + + @Test + public void createWhenAlreadyExistsTest() throws Exception { + context.turnOffAuthorisationSystem(); + + SystemWideAlert systemWideAlert = SystemWideAlertBuilder.createSystemWideAlert(context, "Test alert") + .withAllowSessions( + AllowSessionsEnum.ALLOW_ADMIN_SESSIONS_ONLY) + .withCountdownDate(null) + .isActive(false) + .build(); + + context.restoreAuthSystemState(); + + SystemWideAlertRest systemWideAlertRest = new SystemWideAlertRest(); + systemWideAlertRest.setMessage("Alert test message"); + systemWideAlertRest.setCountdownTo(new Date()); + systemWideAlertRest.setAllowSessions(AllowSessionsEnum.ALLOW_CURRENT_SESSIONS_ONLY.getValue()); + systemWideAlertRest.setActive(true); + + ObjectMapper mapper = new ObjectMapper(); + + String authToken = getAuthToken(admin.getEmail(), password); + + getClient(authToken).perform(post("/api/system/systemwidealerts/") + .content(mapper.writeValueAsBytes(systemWideAlertRest)) + .contentType(contentType)) + .andExpect(status().isBadRequest()); + + } + + @Test + public void putTest() throws Exception { + context.turnOffAuthorisationSystem(); + SystemWideAlert systemWideAlert = SystemWideAlertBuilder.createSystemWideAlert(context, "Alert test message") + .withAllowSessions( + AllowSessionsEnum.ALLOW_ADMIN_SESSIONS_ONLY) + .withCountdownDate(null) + .isActive(false) + .build(); + context.restoreAuthSystemState(); + + Date dateToNearestSecond = DateUtils.round(new Date(), Calendar.SECOND); + + SystemWideAlertRest systemWideAlertRest = new SystemWideAlertRest(); + systemWideAlertRest.setAlertId(systemWideAlert.getID()); + systemWideAlertRest.setMessage("Updated alert test message"); + systemWideAlertRest.setCountdownTo(dateToNearestSecond); + systemWideAlertRest.setAllowSessions(AllowSessionsEnum.ALLOW_CURRENT_SESSIONS_ONLY.getValue()); + systemWideAlertRest.setActive(true); + + ObjectMapper mapper = new ObjectMapper(); + + String authToken = getAuthToken(admin.getEmail(), password); + + + getClient(authToken).perform(put("/api/system/systemwidealerts/" + systemWideAlert.getID()) + .content(mapper.writeValueAsBytes(systemWideAlertRest)) + .contentType(contentType)) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$", allOf( + hasJsonPath("$.alertId"), + hasJsonPath("$.message", is(systemWideAlertRest.getMessage())), + hasJsonPath("$.allowSessions", + is(systemWideAlertRest.getAllowSessions())), + hasJsonPath("$.countdownTo", + dateMatcher(dateToNearestSecond)), + hasJsonPath("$.active", is(systemWideAlertRest.isActive())) + ) + )); + + getClient(authToken).perform(get("/api/system/systemwidealerts/" + systemWideAlert.getID())) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$", allOf( + hasJsonPath("$.alertId", is(systemWideAlert.getID())), + hasJsonPath("$.message", is(systemWideAlertRest.getMessage())), + hasJsonPath("$.allowSessions", is(systemWideAlertRest.getAllowSessions())), + hasJsonPath("$.countdownTo", + dateMatcher(dateToNearestSecond)), + hasJsonPath("$.active", is(systemWideAlertRest.isActive())) + ) + )); + + + } + + +} + diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/TaskRestRepositoriesIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/TaskRestRepositoriesIT.java index 17bc8edf46..a9b5c6a582 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/TaskRestRepositoriesIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/TaskRestRepositoriesIT.java @@ -44,6 +44,7 @@ import org.dspace.builder.ClaimedTaskBuilder; import org.dspace.builder.CollectionBuilder; import org.dspace.builder.CommunityBuilder; import org.dspace.builder.EPersonBuilder; +import org.dspace.builder.GroupBuilder; import org.dspace.builder.ItemBuilder; import org.dspace.builder.PoolTaskBuilder; import org.dspace.builder.WorkflowItemBuilder; @@ -52,11 +53,11 @@ import org.dspace.content.Community; import org.dspace.content.Item; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; -import org.dspace.eperson.factory.EPersonServiceFactory; import org.dspace.eperson.service.GroupService; import org.dspace.xmlworkflow.factory.XmlWorkflowFactory; import org.dspace.xmlworkflow.state.Step; import org.dspace.xmlworkflow.state.actions.WorkflowActionConfig; +import org.dspace.xmlworkflow.state.actions.processingaction.SelectReviewerAction; import org.dspace.xmlworkflow.storedcomponents.ClaimedTask; import org.dspace.xmlworkflow.storedcomponents.PoolTask; import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; @@ -78,6 +79,9 @@ public class TaskRestRepositoriesIT extends AbstractControllerIntegrationTest { @Autowired private XmlWorkflowFactory xmlWorkflowFactory; + @Autowired + GroupService groupService; + @Test /** * Retrieve a specific pooltask @@ -4174,9 +4178,6 @@ public class TaskRestRepositoriesIT extends AbstractControllerIntegrationTest { @Test public void addReviewerToRunningWorkflowTest() throws Exception { - - GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); - context.turnOffAuthorisationSystem(); EPerson reviewer1 = EPersonBuilder.createEPerson(context) @@ -4329,4 +4330,384 @@ public class TaskRestRepositoriesIT extends AbstractControllerIntegrationTest { .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id"))); } + /** + * Test the run of the selectSingleReviewer workflow + * - Creates ‘ReviewManagers’ and ‘Reviewers’, each with some members + * - Creates a normal user, not member of either group, this user is set on context + * - Tests selecting a single reviewer, multiple reviewers and selecting a non-reviewer + * + * @throws Exception + */ + @Test + public void selectReviewerWorkflowTest() throws Exception { + context.turnOffAuthorisationSystem(); + + // Create normal user, not member of "ReviewManagers" or "Reviewers" and set as current user + EPerson user = EPersonBuilder.createEPerson(context) + .withEmail("user@example.com") + .withPassword(password).build(); + context.setCurrentUser(user); + + // Create creator as this user is member of "ReviewManagers" for this item + EPerson creator = EPersonBuilder.createEPerson(context) + .withEmail("creator@example.com") + .withPassword(password).build(); + + // Create with some members to be added to "ReviewManagers" + EPerson reviewManager1 = EPersonBuilder.createEPerson(context) + .withEmail("reviewManager1@example.com") + .withPassword(password).build(); + + EPerson reviewManager2 = EPersonBuilder.createEPerson(context) + .withEmail("reviewManager2@example.com") + .withPassword(password).build(); + + EPerson reviewManager3 = EPersonBuilder.createEPerson(context) + .withEmail("reviewManager3@example.com") + .withPassword(password).build(); + + // The "selectSingleReviewer" requires the "ReviewManagers" repository group to be present with at least 1 + // member + GroupBuilder.createGroup(context) + .withName("ReviewManagers") + .addMember(reviewManager1) + .addMember(reviewManager2) + .addMember(reviewManager3) + .build(); + + // Create "Reviewers" with some members + Group reviewerGroup = GroupBuilder.createGroup(context).withName("Reviewers").build(); + + EPerson reviewer1 = EPersonBuilder.createEPerson(context) + .withEmail("reviewer1@example.com") + .withPassword(password) + .withGroupMembership(reviewerGroup).build(); + + EPerson reviewer2 = EPersonBuilder.createEPerson(context) + .withEmail("reviewer2@example.com") + .withPassword(password) + .withGroupMembership(reviewerGroup).build(); + + EPerson reviewer3 = EPersonBuilder.createEPerson(context) + .withEmail("reviewer3@example.com") + .withPassword(password) + .withGroupMembership(reviewerGroup).build(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community").build(); + + // Create collection with handle "123456789/workflow-test-1" to use "selectSingleReviewer" + Collection collection = + CollectionBuilder.createCollection(context, parentCommunity, "123456789/workflow-test-1") + .withName("Collection 1") + .build(); + + // Create 3 pool tasks + // First one for selecting a single reviewer + PoolTask poolTask1 = PoolTaskBuilder.createPoolTask(context, collection, reviewManager1) + .withTitle("Workflow Item 1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald") + .withAuthor("Doe, John") + .withSubject("ExtraEntry").build(); + XmlWorkflowItem witem1 = poolTask1.getWorkflowItem(); + // Second one for selecting multiple reviewers + PoolTask poolTask2 = PoolTaskBuilder.createPoolTask(context, collection, reviewManager2) + .withTitle("Workflow Item 2") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald") + .withAuthor("Doe, John") + .withSubject("ExtraEntry").build(); + XmlWorkflowItem witem2 = poolTask2.getWorkflowItem(); + // Third one for trying to add user not in "Reviewers" group + PoolTask poolTask3 = PoolTaskBuilder.createPoolTask(context, collection, reviewManager3) + .withTitle("Workflow Item 3") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald") + .withAuthor("Doe, John") + .withSubject("ExtraEntry").build(); + XmlWorkflowItem witem3 = poolTask3.getWorkflowItem(); + + context.restoreAuthSystemState(); + + String reviewManager1Token = getAuthToken(reviewManager1.getEmail(), password); + String reviewManager2Token = getAuthToken(reviewManager2.getEmail(), password); + String reviewManager3Token = getAuthToken(reviewManager3.getEmail(), password); + + String reviewer1Token = getAuthToken(reviewer1.getEmail(), password); + String reviewer2Token = getAuthToken(reviewer2.getEmail(), password); + String reviewer3Token = getAuthToken(reviewer3.getEmail(), password); + + String adminToken = getAuthToken(admin.getEmail(), password); + String userToken = getAuthToken(user.getEmail(), password); + + AtomicReference idRef = new AtomicReference<>(); + + // Verify as member of "ReviewManagers" you can find these pool tasks + getClient(reviewManager1Token).perform(get("/api/workflow/pooltasks/" + poolTask1.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is(PoolTaskMatcher.matchPoolTask(poolTask1, "selectReviewerStep")))); + + // Verify as member of "Reviewers" you can not find these pool tasks + getClient(reviewer1Token).perform(get("/api/workflow/pooltasks/" + poolTask1.getID())) + .andExpect(status().isForbidden()); + + // Verify as member of "ReviewManagers" you can claim in this tasks + getClient(reviewManager1Token).perform(post("/api/workflow/claimedtasks") + .contentType( + MediaType.parseMediaType(RestMediaTypes.TEXT_URI_LIST_VALUE)) + .content("/api/workflow/pooltasks/" + poolTask1.getID())) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$", Matchers.allOf(hasJsonPath("$.type", is("claimedtask"))))) + .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id"))); + + // Verify that pool task 1 no longer exists + getClient(reviewManager1Token).perform(get("/api/workflow/pooltasks/" + poolTask1.getID())) + .andExpect(status().isNotFound()); + + // Verify items now in claimed tasks /api/workflow/claimedtasks for user reviewManager1 + getClient(reviewManager1Token).perform(get("/api/workflow/claimedtasks/search/findByUser") + .param("uuid", reviewManager1.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.claimedtasks", Matchers.contains( + Matchers.allOf( + hasJsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks/")), + hasJsonPath("$.type", Matchers.is("claimedtask")), + hasJsonPath("$._embedded.owner", + Matchers.is(EPersonMatcher.matchEPersonOnEmail(reviewManager1.getEmail()))), + hasJsonPath("$._embedded.action.id", Matchers.is("selectrevieweraction")), + hasJsonPath("$._embedded.workflowitem", + Matchers.is(WorkflowItemMatcher.matchItemWithTitleAndDateIssuedAndSubject( + witem1, "Workflow Item 1", "2017-10-17", "ExtraEntry"))) + )))) + .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks"))) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", is(1))); + + // Verify items now not in claimed tasks /api/workflow/claimedtasks for user reviewer1 + getClient(reviewer1Token).perform(get("/api/workflow/claimedtasks/search/findByUser") + .param("uuid", reviewer1.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks"))) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", is(0))); + + // Test for single reviewer + SelectReviewerAction.resetGroup(); + // Select reviewer1 as a reviewer, wf step 1 + getClient(reviewManager1Token).perform(post("/api/workflow/claimedtasks/" + idRef.get()) + .param("submit_select_reviewer", "true") + .param("eperson", reviewer1.getID().toString()) + .contentType(MediaType.APPLICATION_FORM_URLENCODED)) + .andExpect(status().isNoContent()); + + // Verify reviewer1 has the claimed task + getClient(reviewer1Token).perform(get("/api/workflow/claimedtasks/search/findByUser") + .param("uuid", reviewer1.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.claimedtasks", Matchers.contains( + Matchers.allOf( + hasJsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks/")), + hasJsonPath("$.type", Matchers.is("claimedtask")), + hasJsonPath("$._embedded.owner", + Matchers.is(EPersonMatcher.matchEPersonOnEmail(reviewer1.getEmail()))), + hasJsonPath("$._embedded.action.id", Matchers.is("singleuserreviewaction")), + hasJsonPath("$._embedded.workflowitem", + Matchers.is(WorkflowItemMatcher.matchItemWithTitleAndDateIssuedAndSubject( + witem1, "Workflow Item 1", "2017-10-17", "ExtraEntry"))) + )))) + .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks"))) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", is(1))); + + // Verify other members of "Reviewers" don't have this task claimed + getClient(reviewer2Token).perform(get("/api/workflow/claimedtasks/search/findByUser") + .param("uuid", reviewer2.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks"))) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", is(0))); + getClient(reviewer3Token).perform(get("/api/workflow/claimedtasks/search/findByUser") + .param("uuid", reviewer3.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks"))) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", is(0))); + // Verify members of "ReviewManagers" don't have this task claimed + getClient(reviewManager1Token).perform(get("/api/workflow/claimedtasks/search/findByUser") + .param("uuid", reviewManager1.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks"))) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", is(0))); + getClient(reviewManager2Token).perform(get("/api/workflow/claimedtasks/search/findByUser") + .param("uuid", reviewManager2.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks"))) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", is(0))); + getClient(reviewManager3Token).perform(get("/api/workflow/claimedtasks/search/findByUser") + .param("uuid", reviewManager3.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks"))) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", is(0))); + // Verify other users don't have this task claimed + getClient(userToken).perform(get("/api/workflow/claimedtasks/search/findByUser") + .param("uuid", user.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks"))) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", is(0))); + + // Test for multiple reviewers + + // Claim pooltask2 as member of "ReviewManagers" + getClient(reviewManager2Token).perform(post("/api/workflow/claimedtasks") + .contentType( + MediaType.parseMediaType(RestMediaTypes.TEXT_URI_LIST_VALUE)) + .content("/api/workflow/pooltasks/" + poolTask2.getID())) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$", Matchers.allOf(hasJsonPath("$.type", is("claimedtask"))))) + .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id"))); + + // Select reviewer2 and reviewer3 as reviewers, wf step 1 + getClient(reviewManager2Token).perform(post("/api/workflow/claimedtasks/" + idRef.get()) + .param("submit_select_reviewer", "true") + .param("eperson", reviewer2.getID().toString()) + .param("eperson", reviewer3.getID().toString()) + .contentType(MediaType.APPLICATION_FORM_URLENCODED)) + .andExpect(status().isNoContent()); + + // Verify reviewer2 has the claimed task + getClient(reviewer2Token).perform(get("/api/workflow/claimedtasks/search/findByUser") + .param("uuid", reviewer2.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.claimedtasks", Matchers.contains( + Matchers.allOf( + hasJsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks/")), + hasJsonPath("$.type", Matchers.is("claimedtask")), + hasJsonPath("$._embedded.owner", + Matchers.is(EPersonMatcher.matchEPersonOnEmail(reviewer2.getEmail()))), + hasJsonPath("$._embedded.action.id", Matchers.is("singleuserreviewaction")), + hasJsonPath("$._embedded.workflowitem", + Matchers.is(WorkflowItemMatcher.matchItemWithTitleAndDateIssuedAndSubject( + witem2, "Workflow Item 2", "2017-10-17", "ExtraEntry"))) + )))) + .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks"))) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", is(1))); + + // Verify reviewer3 has the claimed task too + getClient(reviewer3Token).perform(get("/api/workflow/claimedtasks/search/findByUser") + .param("uuid", reviewer3.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.claimedtasks", Matchers.contains( + Matchers.allOf( + hasJsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks/")), + hasJsonPath("$.type", Matchers.is("claimedtask")), + hasJsonPath("$._embedded.owner", + Matchers.is(EPersonMatcher.matchEPersonOnEmail(reviewer3.getEmail()))), + hasJsonPath("$._embedded.action.id", Matchers.is("singleuserreviewaction")), + hasJsonPath("$._embedded.workflowitem", + Matchers.is(WorkflowItemMatcher.matchItemWithTitleAndDateIssuedAndSubject( + witem2, "Workflow Item 2", "2017-10-17", "ExtraEntry"))) + )))) + .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks"))) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", is(1))); + + // Verify reviewer1 of "Reviewers" doesn't have this task claimed, only the first task + getClient(reviewer1Token).perform(get("/api/workflow/claimedtasks/search/findByUser") + .param("uuid", reviewer1.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.claimedtasks", Matchers.contains( + Matchers.allOf( + hasJsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks/")), + hasJsonPath("$.type", Matchers.is("claimedtask")), + hasJsonPath("$._embedded.owner", + Matchers.is(EPersonMatcher.matchEPersonOnEmail(reviewer1.getEmail()))), + hasJsonPath("$._embedded.action.id", Matchers.is("singleuserreviewaction")), + hasJsonPath("$._embedded.workflowitem", + Matchers.is(WorkflowItemMatcher.matchItemWithTitleAndDateIssuedAndSubject( + witem1, "Workflow Item 1", "2017-10-17", "ExtraEntry"))), + hasJsonPath("$._embedded.workflowitem", + Matchers.not(WorkflowItemMatcher.matchItemWithTitleAndDateIssuedAndSubject( + witem2, "Workflow Item 2", "2017-10-17", "ExtraEntry"))) + )))) + .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks"))) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", is(1))); + // Verify members of "ReviewManagers" don't have this task claimed + getClient(reviewManager1Token).perform(get("/api/workflow/claimedtasks/search/findByUser") + .param("uuid", reviewManager1.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks"))) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", is(0))); + getClient(reviewManager2Token).perform(get("/api/workflow/claimedtasks/search/findByUser") + .param("uuid", reviewManager2.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks"))) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", is(0))); + getClient(reviewManager3Token).perform(get("/api/workflow/claimedtasks/search/findByUser") + .param("uuid", reviewManager3.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks"))) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", is(0))); + // Verify other users don't have this task claimed + getClient(userToken).perform(get("/api/workflow/claimedtasks/search/findByUser") + .param("uuid", user.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks"))) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", is(0))); + + // Test to assign non-reviewer + + // Claim pooltask3 as member of "ReviewManagers" + getClient(reviewManager3Token).perform(post("/api/workflow/claimedtasks") + .contentType( + MediaType.parseMediaType(RestMediaTypes.TEXT_URI_LIST_VALUE)) + .content("/api/workflow/pooltasks/" + poolTask3.getID())) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$", Matchers.allOf(hasJsonPath("$.type", is("claimedtask"))))) + .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id"))); + + // Select (non-reviewer) user as a reviewer, wf step 1 + getClient(reviewManager3Token).perform(post("/api/workflow/claimedtasks/" + idRef.get()) + .param("submit_select_reviewer", "true") + .param("eperson", user.getID().toString()) + .contentType(MediaType.APPLICATION_FORM_URLENCODED)) + .andExpect(status().isNoContent()); + + // Verify user does not have this task claimed + getClient(userToken).perform(get("/api/workflow/claimedtasks/search/findByUser") + .param("uuid", user.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks"))) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", is(0))); + + // Verify item still in claimed tasks for user reviewManager3 on step "selectrevieweraction" + getClient(reviewManager3Token).perform(get("/api/workflow/claimedtasks/search/findByUser") + .param("uuid", reviewManager3.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.claimedtasks", Matchers.contains( + Matchers.allOf( + hasJsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks/")), + hasJsonPath("$.type", Matchers.is("claimedtask")), + hasJsonPath("$._embedded.owner", + Matchers.is(EPersonMatcher.matchEPersonOnEmail(reviewManager3.getEmail()))), + hasJsonPath("$._embedded.action.id", Matchers.is("selectrevieweraction")), + hasJsonPath("$._embedded.workflowitem", + Matchers.is(WorkflowItemMatcher.matchItemWithTitleAndDateIssuedAndSubject( + witem3, "Workflow Item 3", "2017-10-17", "ExtraEntry"))) + )))) + .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks"))) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", is(1))); + } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VersionRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VersionRestRepositoryIT.java index 5878c16143..ed3e811db2 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VersionRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VersionRestRepositoryIT.java @@ -850,6 +850,55 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { .andExpect(status().isUnauthorized()); } + @Test + public void createNewVersionItemByCollectionAdminTest() throws Exception { + context.turnOffAuthorisationSystem(); + Community rootCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + EPerson colAdmin = EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withEmail("coladmin@email.com") + .withPassword(password) + .withNameInMetadata("Collection", "Admin") + .build(); + Collection col = CollectionBuilder + .createCollection(context, rootCommunity) + .withName("Collection 1") + .withAdminGroup(colAdmin) + .build(); + + Item item = ItemBuilder.createItem(context, col) + .withTitle("Public test item") + .withIssueDate("2022-12-19") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + item.setSubmitter(eperson); + + context.restoreAuthSystemState(); + + AtomicReference idRef = new AtomicReference(); + String token = getAuthToken(colAdmin.getEmail(), password); + try { + getClient(token).perform(post("/api/versioning/versions") + .param("summary", "test summary!") + .contentType(MediaType.parseMediaType(RestMediaTypes.TEXT_URI_LIST_VALUE)) + .content("/api/core/items/" + item.getID())) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$", Matchers.allOf( + hasJsonPath("$.version", is(2)), + hasJsonPath("$.summary", is("test summary!")), + hasJsonPath("$.type", is("version")) + ))) + .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id"))); + } finally { + VersionBuilder.delete(idRef.get()); + } + } + @Test public void patchReplaceSummaryTest() throws Exception { context.turnOffAuthorisationSystem(); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java index 4962e1aef2..dad6cd8b46 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java @@ -57,12 +57,12 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest .andExpect(status().isOk()) .andExpect(jsonPath("$", VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB110", "Religion/Theology", - "Research Subject Categories::HUMANITIES and RELIGION::Religion/Theology"))) + "HUMANITIES and RELIGION::Religion/Theology"))) .andExpect(jsonPath("$.selectable", is(true))) .andExpect(jsonPath("$.otherInformation.id", is("SCB110"))) .andExpect(jsonPath("$.otherInformation.note", is("Religionsvetenskap/Teologi"))) .andExpect(jsonPath("$.otherInformation.parent", - is("Research Subject Categories::HUMANITIES and RELIGION"))) + is("HUMANITIES and RELIGION"))) .andExpect(jsonPath("$._links.parent.href", endsWith("api/submission/vocabularyEntryDetails/srsc:SCB110/parent"))) .andExpect(jsonPath("$._links.children.href", @@ -70,10 +70,10 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest } @Test - public void findOneUnauthorizedTest() throws Exception { + public void findOneAnonymousTest() throws Exception { String idAuthority = "srsc:SCB110"; getClient().perform(get("/api/submission/vocabularyEntryDetails/" + idAuthority)) - .andExpect(status().isUnauthorized()); + .andExpect(status().isOk()); } @Test @@ -102,30 +102,30 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.vocabularyEntryDetails", Matchers.containsInAnyOrder( VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB11", "HUMANITIES and RELIGION", - "Research Subject Categories::HUMANITIES and RELIGION"), + "HUMANITIES and RELIGION"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB12", "LAW/JURISPRUDENCE", - "Research Subject Categories::LAW/JURISPRUDENCE"), + "LAW/JURISPRUDENCE"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB13", "SOCIAL SCIENCES", - "Research Subject Categories::SOCIAL SCIENCES"), + "SOCIAL SCIENCES"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB14", "MATHEMATICS", - "Research Subject Categories::MATHEMATICS"), + "MATHEMATICS"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB15", "NATURAL SCIENCES", - "Research Subject Categories::NATURAL SCIENCES"), + "NATURAL SCIENCES"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB16", "TECHNOLOGY", - "Research Subject Categories::TECHNOLOGY"), + "TECHNOLOGY"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB17", "FORESTRY, AGRICULTURAL SCIENCES and LANDSCAPE PLANNING", - "Research Subject Categories::FORESTRY, AGRICULTURAL SCIENCES and LANDSCAPE PLANNING"), + "FORESTRY, AGRICULTURAL SCIENCES and LANDSCAPE PLANNING"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB18", "MEDICINE", - "Research Subject Categories::MEDICINE"), + "MEDICINE"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB19", "ODONTOLOGY", - "Research Subject Categories::ODONTOLOGY"), + "ODONTOLOGY"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB21", "PHARMACY", - "Research Subject Categories::PHARMACY"), + "PHARMACY"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB22", "VETERINARY MEDICINE", - "Research Subject Categories::VETERINARY MEDICINE"), + "VETERINARY MEDICINE"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB23", "INTERDISCIPLINARY RESEARCH AREAS", - "Research Subject Categories::INTERDISCIPLINARY RESEARCH AREAS") + "INTERDISCIPLINARY RESEARCH AREAS") ))) .andExpect(jsonPath("$.page.totalElements", Matchers.is(12))); @@ -134,30 +134,30 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.vocabularyEntryDetails", Matchers.containsInAnyOrder( VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB11", "HUMANITIES and RELIGION", - "Research Subject Categories::HUMANITIES and RELIGION"), + "HUMANITIES and RELIGION"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB12", "LAW/JURISPRUDENCE", - "Research Subject Categories::LAW/JURISPRUDENCE"), + "LAW/JURISPRUDENCE"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB13", "SOCIAL SCIENCES", - "Research Subject Categories::SOCIAL SCIENCES"), + "SOCIAL SCIENCES"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB14", "MATHEMATICS", - "Research Subject Categories::MATHEMATICS"), + "MATHEMATICS"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB15", "NATURAL SCIENCES", - "Research Subject Categories::NATURAL SCIENCES"), + "NATURAL SCIENCES"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB16", "TECHNOLOGY", - "Research Subject Categories::TECHNOLOGY"), + "TECHNOLOGY"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB17", "FORESTRY, AGRICULTURAL SCIENCES and LANDSCAPE PLANNING", - "Research Subject Categories::FORESTRY, AGRICULTURAL SCIENCES and LANDSCAPE PLANNING"), + "FORESTRY, AGRICULTURAL SCIENCES and LANDSCAPE PLANNING"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB18", "MEDICINE", - "Research Subject Categories::MEDICINE"), + "MEDICINE"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB19", "ODONTOLOGY", - "Research Subject Categories::ODONTOLOGY"), + "ODONTOLOGY"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB21", "PHARMACY", - "Research Subject Categories::PHARMACY"), + "PHARMACY"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB22", "VETERINARY MEDICINE", - "Research Subject Categories::VETERINARY MEDICINE"), + "VETERINARY MEDICINE"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB23", "INTERDISCIPLINARY RESEARCH AREAS", - "Research Subject Categories::INTERDISCIPLINARY RESEARCH AREAS") + "INTERDISCIPLINARY RESEARCH AREAS") ))) .andExpect(jsonPath("$.page.totalElements", Matchers.is(12))); } @@ -170,14 +170,14 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest .andExpect(jsonPath("$._embedded.children", Matchers.containsInAnyOrder( VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB1401", "Algebra, geometry and mathematical analysis", - "Research Subject Categories::MATHEMATICS::Algebra, geometry and mathematical analysis"), + "MATHEMATICS::Algebra, geometry and mathematical analysis"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB1402", "Applied mathematics", - "Research Subject Categories::MATHEMATICS::Applied mathematics"), + "MATHEMATICS::Applied mathematics"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB1409", "Other mathematics", - "Research Subject Categories::MATHEMATICS::Other mathematics") + "MATHEMATICS::Other mathematics") ))) .andExpect(jsonPath("$._embedded.children[*].otherInformation.parent", - Matchers.everyItem(is("Research Subject Categories::MATHEMATICS")))) + Matchers.everyItem(is("MATHEMATICS")))) .andExpect(jsonPath("$.page.totalElements", Matchers.is(3))); } @@ -191,15 +191,15 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.vocabularyEntryDetails", Matchers.containsInAnyOrder( VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB11", "HUMANITIES and RELIGION", - "Research Subject Categories::HUMANITIES and RELIGION"), + "HUMANITIES and RELIGION"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB12", "LAW/JURISPRUDENCE", - "Research Subject Categories::LAW/JURISPRUDENCE"), + "LAW/JURISPRUDENCE"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB13", "SOCIAL SCIENCES", - "Research Subject Categories::SOCIAL SCIENCES"), + "SOCIAL SCIENCES"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB14", "MATHEMATICS", - "Research Subject Categories::MATHEMATICS"), + "MATHEMATICS"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB15", "NATURAL SCIENCES", - "Research Subject Categories::NATURAL SCIENCES") + "NATURAL SCIENCES") ))) .andExpect(jsonPath("$.page.totalElements", is(12))) .andExpect(jsonPath("$.page.totalPages", is(3))) @@ -213,16 +213,16 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.vocabularyEntryDetails", Matchers.containsInAnyOrder( VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB16", "TECHNOLOGY", - "Research Subject Categories::TECHNOLOGY"), + "TECHNOLOGY"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB17", "FORESTRY, AGRICULTURAL SCIENCES and LANDSCAPE PLANNING", - "Research Subject Categories::FORESTRY, AGRICULTURAL SCIENCES and LANDSCAPE PLANNING"), + "FORESTRY, AGRICULTURAL SCIENCES and LANDSCAPE PLANNING"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB18", "MEDICINE", - "Research Subject Categories::MEDICINE"), + "MEDICINE"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB19", "ODONTOLOGY", - "Research Subject Categories::ODONTOLOGY"), + "ODONTOLOGY"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB21", "PHARMACY", - "Research Subject Categories::PHARMACY") + "PHARMACY") ))) .andExpect(jsonPath("$.page.totalElements", is(12))) .andExpect(jsonPath("$.page.totalPages", is(3))) @@ -236,9 +236,9 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.vocabularyEntryDetails", Matchers.containsInAnyOrder( VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB22", "VETERINARY MEDICINE", - "Research Subject Categories::VETERINARY MEDICINE"), + "VETERINARY MEDICINE"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB23", "INTERDISCIPLINARY RESEARCH AREAS", - "Research Subject Categories::INTERDISCIPLINARY RESEARCH AREAS") + "INTERDISCIPLINARY RESEARCH AREAS") ))) .andExpect(jsonPath("$.page.totalElements", is(12))) .andExpect(jsonPath("$.page.totalPages", is(3))) @@ -254,10 +254,10 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest } @Test - public void searchTopUnauthorizedTest() throws Exception { + public void searchTopAnonymousTest() throws Exception { getClient().perform(get("/api/submission/vocabularyEntryDetails/search/top") - .param("vocabulary", "srsc:SCB16")) - .andExpect(status().isUnauthorized()); + .param("vocabulary", "srsc")) + .andExpect(status().isOk()); } @Test @@ -271,9 +271,9 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest .andExpect(jsonPath("$._embedded.children", Matchers.containsInAnyOrder( VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB1401", "Algebra, geometry and mathematical analysis", - "Research Subject Categories::MATHEMATICS::Algebra, geometry and mathematical analysis"), + "MATHEMATICS::Algebra, geometry and mathematical analysis"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB1402", "Applied mathematics", - "Research Subject Categories::MATHEMATICS::Applied mathematics") + "MATHEMATICS::Applied mathematics") ))) .andExpect(jsonPath("$.page.totalElements", is(3))) .andExpect(jsonPath("$.page.totalPages", is(2))) @@ -286,7 +286,7 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.children", Matchers.contains( VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB1409", "Other mathematics", - "Research Subject Categories::MATHEMATICS::Other mathematics") + "MATHEMATICS::Other mathematics") ))) .andExpect(jsonPath("$.page.totalElements", is(3))) .andExpect(jsonPath("$.page.totalPages", is(2))) @@ -300,13 +300,13 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.children", Matchers.containsInAnyOrder( VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR140202", "Numerical analysis", - "Research Subject Categories::MATHEMATICS::Applied mathematics::Numerical analysis"), + "MATHEMATICS::Applied mathematics::Numerical analysis"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR140203", "Mathematical statistics", - "Research Subject Categories::MATHEMATICS::Applied mathematics::Mathematical statistics"), + "MATHEMATICS::Applied mathematics::Mathematical statistics"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR140204", "Optimization, systems theory", - "Research Subject Categories::MATHEMATICS::Applied mathematics::Optimization, systems theory"), + "MATHEMATICS::Applied mathematics::Optimization, systems theory"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR140205", "Theoretical computer science", - "Research Subject Categories::MATHEMATICS::Applied mathematics::Theoretical computer science") + "MATHEMATICS::Applied mathematics::Theoretical computer science") ))) .andExpect(jsonPath("$.page.totalElements", Matchers.is(4))); } @@ -328,10 +328,10 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest } @Test - public void srscSearchTopUnauthorizedTest() throws Exception { + public void srscSearchTopAnonymousTest() throws Exception { getClient().perform(get("/api/submission/vocabularyEntryDetails/search/top") .param("vocabulary", "srsc")) - .andExpect(status().isUnauthorized()); + .andExpect(status().isOk()); } @Test @@ -341,7 +341,7 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest .andExpect(status().isOk()) .andExpect(jsonPath("$", is( VocabularyEntryDetailsMatcher.matchAuthorityEntry( - "srsc:SCB18", "MEDICINE","Research Subject Categories::MEDICINE") + "srsc:SCB18", "MEDICINE","MEDICINE") ))); } @@ -353,9 +353,9 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest } @Test - public void findParentByChildUnauthorizedTest() throws Exception { + public void findParentByChildAnonymousTest() throws Exception { getClient().perform(get("/api/submission/vocabularyEntryDetails/srsc:SCB180/parent")) - .andExpect(status().isUnauthorized()); + .andExpect(status().isOk()); } @Test @@ -375,11 +375,11 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest .andExpect(jsonPath("$._embedded.parent", VocabularyEntryDetailsMatcher.matchAuthorityEntry( "srsc:SCB11", "HUMANITIES and RELIGION", - "Research Subject Categories::HUMANITIES and RELIGION"))) + "HUMANITIES and RELIGION"))) .andExpect(jsonPath("$._embedded.children._embedded.children", matchAllSrscSC110Children())) .andExpect(jsonPath("$._embedded.children._embedded.children[*].otherInformation.parent", Matchers.everyItem( - is("Research Subject Categories::HUMANITIES and RELIGION::Religion/Theology")))); + is("HUMANITIES and RELIGION::Religion/Theology")))); getClient(tokenAdmin).perform( get("/api/submission/vocabularyEntryDetails/srsc:SCB110").param("embed", "children")) @@ -387,7 +387,7 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest .andExpect(jsonPath("$._embedded.children._embedded.children", matchAllSrscSC110Children())) .andExpect(jsonPath("$._embedded.children._embedded.children[*].otherInformation.parent", Matchers.everyItem( - is("Research Subject Categories::HUMANITIES and RELIGION::Religion/Theology")))); + is("HUMANITIES and RELIGION::Religion/Theology")))); getClient(tokenAdmin).perform( get("/api/submission/vocabularyEntryDetails/srsc:SCB110").param("embed", "parent")) @@ -395,47 +395,47 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest .andExpect(jsonPath("$._embedded.parent", VocabularyEntryDetailsMatcher.matchAuthorityEntry( "srsc:SCB11", "HUMANITIES and RELIGION", - "Research Subject Categories::HUMANITIES and RELIGION"))); + "HUMANITIES and RELIGION"))); } private Matcher> matchAllSrscSC110Children() { return Matchers.containsInAnyOrder( VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR110102", "History of religion", - "Research Subject Categories::HUMANITIES and RELIGION::Religion/Theology::History of religion"), + "HUMANITIES and RELIGION::Religion/Theology::History of religion"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR110103", "Church studies", - "Research Subject Categories::HUMANITIES and RELIGION::Religion/Theology::Church studies"), + "HUMANITIES and RELIGION::Religion/Theology::Church studies"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR110104", "Missionary studies", - "Research Subject Categories::HUMANITIES and RELIGION::Religion/Theology::Missionary studies"), + "HUMANITIES and RELIGION::Religion/Theology::Missionary studies"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR110105", "Systematic theology", - "Research Subject Categories::HUMANITIES and RELIGION::Religion/Theology::Systematic theology"), + "HUMANITIES and RELIGION::Religion/Theology::Systematic theology"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR110106", "Islamology", - "Research Subject Categories::HUMANITIES and RELIGION::Religion/Theology::Islamology"), + "HUMANITIES and RELIGION::Religion/Theology::Islamology"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR110107", "Faith and reason", - "Research Subject Categories::HUMANITIES and RELIGION::Religion/Theology::Faith and reason"), + "HUMANITIES and RELIGION::Religion/Theology::Faith and reason"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR110108", "Sociology of religion", - "Research Subject Categories::HUMANITIES and RELIGION::Religion/Theology::Sociology of religion"), + "HUMANITIES and RELIGION::Religion/Theology::Sociology of religion"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR110109", "Psychology of religion", - "Research Subject Categories::HUMANITIES and RELIGION::Religion/Theology::Psychology of religion"), + "HUMANITIES and RELIGION::Religion/Theology::Psychology of religion"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR110110", "Philosophy of religion", - "Research Subject Categories::HUMANITIES and RELIGION::Religion/Theology::Philosophy of religion"), + "HUMANITIES and RELIGION::Religion/Theology::Philosophy of religion"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR110111", "New Testament exegesis", - "Research Subject Categories::HUMANITIES and RELIGION::Religion/Theology::New Testament exegesis"), + "HUMANITIES and RELIGION::Religion/Theology::New Testament exegesis"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR110112", "Old Testament exegesis", - "Research Subject Categories::HUMANITIES and RELIGION::Religion/Theology::Old Testament exegesis"), + "HUMANITIES and RELIGION::Religion/Theology::Old Testament exegesis"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR110113", "Dogmatics with symbolics", - "Research Subject Categories::HUMANITIES and RELIGION::Religion/Theology::Dogmatics with symbolics") + "HUMANITIES and RELIGION::Religion/Theology::Dogmatics with symbolics") ); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java index 5ea8cdb231..f9b31fb06d 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java @@ -151,8 +151,7 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes @Test public void findOneSRSC_Test() throws Exception { - String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(get("/api/submission/vocabularies/srsc")) + getClient().perform(get("/api/submission/vocabularies/srsc")) .andExpect(status().isOk()) .andExpect(jsonPath("$", is( VocabularyMatcher.matchProperties("srsc", "srsc", false, true) @@ -161,8 +160,7 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes @Test public void findOneCommonTypesTest() throws Exception { - String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(get("/api/submission/vocabularies/common_types")) + getClient().perform(get("/api/submission/vocabularies/common_types")) .andExpect(status().isOk()) .andExpect(jsonPath("$", is( VocabularyMatcher.matchProperties("common_types", "common_types", true, false) @@ -179,9 +177,9 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.entries", Matchers.containsInAnyOrder( VocabularyMatcher.matchVocabularyEntry("Research Subject Categories", - "Research Subject Categories", "vocabularyEntry"), + "", "vocabularyEntry"), VocabularyMatcher.matchVocabularyEntry("Family research", - "Research Subject Categories::SOCIAL SCIENCES::Social sciences::Social work::Family research", + "SOCIAL SCIENCES::Social sciences::Social work::Family research", "vocabularyEntry")))) .andExpect(jsonPath("$.page.totalElements", Matchers.is(26))) .andExpect(jsonPath("$.page.totalPages", Matchers.is(13))) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowActionRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowActionRestRepositoryIT.java index de687ebd9d..ec963fd2f3 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowActionRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowActionRestRepositoryIT.java @@ -7,7 +7,9 @@ */ package org.dspace.app.rest; +import static org.dspace.xmlworkflow.state.actions.processingaction.ScoreReviewAction.SUBMIT_SCORE; import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; @@ -17,9 +19,18 @@ import org.dspace.app.rest.matcher.WorkflowActionMatcher; import org.dspace.app.rest.model.WorkflowActionRest; import org.dspace.app.rest.repository.WorkflowActionRestRepository; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.builder.GroupBuilder; +import org.dspace.eperson.Group; +import org.dspace.eperson.factory.EPersonServiceFactory; +import org.dspace.eperson.service.GroupService; +import org.dspace.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.xmlworkflow.factory.XmlWorkflowFactory; import org.dspace.xmlworkflow.factory.XmlWorkflowServiceFactory; import org.dspace.xmlworkflow.state.actions.WorkflowActionConfig; +import org.dspace.xmlworkflow.state.actions.processingaction.ScoreReviewActionAdvancedInfo; +import org.dspace.xmlworkflow.state.actions.processingaction.SelectReviewerAction; +import org.dspace.xmlworkflow.state.actions.processingaction.SelectReviewerActionAdvancedInfo; import org.hamcrest.Matchers; import org.junit.Test; @@ -31,6 +42,8 @@ import org.junit.Test; public class WorkflowActionRestRepositoryIT extends AbstractControllerIntegrationTest { private XmlWorkflowFactory xmlWorkflowFactory = XmlWorkflowServiceFactory.getInstance().getWorkflowFactory(); + private ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); + private GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); private static final String WORKFLOW_ACTIONS_ENDPOINT = "/api/" + WorkflowActionRest.CATEGORY + "/" + WorkflowActionRest.NAME_PLURAL; @@ -82,6 +95,7 @@ public class WorkflowActionRestRepositoryIT extends AbstractControllerIntegratio .andExpect(status().isOk()) // has options .andExpect(jsonPath("$.options", not(empty()))) + .andExpect(jsonPath("$.advanced", is(false))) //Matches expected corresponding rest action values .andExpect(jsonPath("$", Matchers.is( WorkflowActionMatcher.matchWorkflowActionEntry(existentWorkflow) @@ -99,6 +113,7 @@ public class WorkflowActionRestRepositoryIT extends AbstractControllerIntegratio .andExpect(status().isOk()) // has no options .andExpect(jsonPath("$.options", empty())) + .andExpect(jsonPath("$.advanced", is(false))) //Matches expected corresponding rest action values .andExpect(jsonPath("$", Matchers.is( WorkflowActionMatcher.matchWorkflowActionEntry(existentWorkflowNoOptions) @@ -125,4 +140,68 @@ public class WorkflowActionRestRepositoryIT extends AbstractControllerIntegratio //We expect a 401 Unauthorized .andExpect(status().isUnauthorized()); } + + @Test + public void getWorkflowActionByName_ExistentWithOptions_ratingreviewaction() throws Exception { + String token = getAuthToken(eperson.getEmail(), password); + String nameActionWithOptions = "scorereviewaction"; + WorkflowActionConfig existentWorkflow = xmlWorkflowFactory.getActionByName(nameActionWithOptions); + + // create ScoreReviewActionAdvancedInfo to compare with output + ScoreReviewActionAdvancedInfo scoreReviewActionAdvancedInfo = new ScoreReviewActionAdvancedInfo(); + scoreReviewActionAdvancedInfo.setDescriptionRequired(true); + scoreReviewActionAdvancedInfo.setMaxValue(5); + scoreReviewActionAdvancedInfo.setType(SUBMIT_SCORE); + scoreReviewActionAdvancedInfo.generateId(SUBMIT_SCORE); + + //When we call this facets endpoint + getClient(token).perform(get(WORKFLOW_ACTIONS_ENDPOINT + "/" + nameActionWithOptions)) + //We expect a 200 is ok status + .andExpect(status().isOk()) + // has options + .andExpect(jsonPath("$.options", not(empty()))) + .andExpect(jsonPath("$.advancedOptions", not(empty()))) + .andExpect(jsonPath("$.advanced", is(true))) + .andExpect(jsonPath("$.advancedInfo", Matchers.contains( + WorkflowActionMatcher.matchScoreReviewActionAdvancedInfo(scoreReviewActionAdvancedInfo)))) + //Matches expected corresponding rest action values + .andExpect(jsonPath("$", Matchers.is( + WorkflowActionMatcher.matchWorkflowActionEntry(existentWorkflow) + ))); + } + + @Test + public void getWorkflowActionByName_ExistentWithOptions_selectrevieweraction() throws Exception { + String token = getAuthToken(eperson.getEmail(), password); + String nameActionWithOptions = "selectrevieweraction"; + // create reviewers group + SelectReviewerAction.resetGroup(); + context.turnOffAuthorisationSystem(); + Group group = GroupBuilder.createGroup(context).withName("ReviewersUUIDConfig").build(); + configurationService.setProperty("action.selectrevieweraction.group", group.getID()); + context.restoreAuthSystemState(); + + // create SelectReviewerActionAdvancedInfo to compare with output + SelectReviewerActionAdvancedInfo selectReviewerActionAdvancedInfo = new SelectReviewerActionAdvancedInfo(); + selectReviewerActionAdvancedInfo.setGroup(group.getID().toString()); + selectReviewerActionAdvancedInfo.setType("submit_select_reviewer"); + selectReviewerActionAdvancedInfo.generateId("submit_select_reviewer"); + + WorkflowActionConfig existentWorkflow = xmlWorkflowFactory.getActionByName(nameActionWithOptions); + //When we call this facets endpoint + getClient(token).perform(get(WORKFLOW_ACTIONS_ENDPOINT + "/" + nameActionWithOptions)) + //We expect a 200 is ok status + .andExpect(status().isOk()) + // has options + .andExpect(jsonPath("$.options", not(empty()))) + .andExpect(jsonPath("$.advancedOptions", not(empty()))) + .andExpect(jsonPath("$.advanced", is(true))) + .andExpect(jsonPath("$.advancedInfo", Matchers.contains( + WorkflowActionMatcher.matchSelectReviewerActionAdvancedInfo(selectReviewerActionAdvancedInfo)))) + //Matches expected corresponding rest action values + .andExpect(jsonPath("$", Matchers.is( + WorkflowActionMatcher.matchWorkflowActionEntry(existentWorkflow) + ))); + } + } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowItemRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowItemRestRepositoryIT.java index 025a5f15a9..c43821d4a0 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowItemRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowItemRestRepositoryIT.java @@ -2122,5 +2122,4 @@ public class WorkflowItemRestRepositoryIT extends AbstractControllerIntegrationT WorkflowItemBuilder.deleteWorkflowItem(idRef.get()); } } - } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java index 4a9f03ffff..6c97526425 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java @@ -11,9 +11,12 @@ import static com.jayway.jsonpath.JsonPath.read; import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; import static org.dspace.app.rest.matcher.MetadataMatcher.matchMetadata; import static org.dspace.app.rest.matcher.MetadataMatcher.matchMetadataDoesNotExist; +import static org.dspace.app.rest.matcher.SupervisionOrderMatcher.matchSuperVisionOrder; import static org.dspace.authorize.ResourcePolicy.TYPE_CUSTOM; import static org.hamcrest.Matchers.allOf; 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.hasSize; import static org.hamcrest.Matchers.is; @@ -70,6 +73,7 @@ import org.dspace.builder.ItemBuilder; import org.dspace.builder.RelationshipBuilder; import org.dspace.builder.RelationshipTypeBuilder; import org.dspace.builder.ResourcePolicyBuilder; +import org.dspace.builder.SupervisionOrderBuilder; import org.dspace.builder.WorkspaceItemBuilder; import org.dspace.content.Bitstream; import org.dspace.content.Bundle; @@ -89,6 +93,7 @@ import org.dspace.eperson.Group; import org.dspace.eperson.factory.EPersonServiceFactory; import org.dspace.eperson.service.GroupService; import org.dspace.services.ConfigurationService; +import org.dspace.supervision.SupervisionOrder; import org.hamcrest.Matchers; import org.junit.Before; import org.junit.Test; @@ -8291,4 +8296,274 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration assertTrue(date.equals(date2)); } + @Test + public void supervisionOrdersLinksTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = + CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + WorkspaceItem witem = + WorkspaceItemBuilder.createWorkspaceItem(context, col1) + .withTitle("Workspace Item 1") + .withIssueDate("2022-12-12") + .withAuthor("Eskander, Mohamed") + .withSubject("ExtraEntry") + .grantLicense() + .build(); + + Group group = + GroupBuilder.createGroup(context) + .withName("group A") + .addMember(admin) + .build(); + + SupervisionOrder supervisionOrderOne = SupervisionOrderBuilder + .createSupervisionOrder(context, witem.getItem(), group) + .build(); + + //disable file upload mandatory + configurationService.setProperty("webui.submit.upload.required", false); + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + + getClient(adminToken) + .perform(get("/api/submission/workspaceitems/" + witem.getID())).andExpect(status().isOk()) + .andExpect(jsonPath("$", + Matchers.is(WorkspaceItemMatcher.matchItemWithTitleAndDateIssuedAndSubject(witem, + "Workspace Item 1", "2022-12-12", "ExtraEntry")))) + .andExpect(jsonPath("$._links.supervisionOrders.href", containsString( + "/api/submission/workspaceitems/" + witem.getID() + "/supervisionOrders") + )); + } + + @Test + public void supervisionOrdersEndpointTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = + CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + WorkspaceItem witemOne = + WorkspaceItemBuilder.createWorkspaceItem(context, col1) + .withTitle("Test item -- supervision orders") + .withIssueDate("2022-12-12") + .withAuthor("Eskander, Mohamed") + .grantLicense() + .build(); + + WorkspaceItem witemTwo = + WorkspaceItemBuilder.createWorkspaceItem(context, col1) + .withTitle("Test item -- no supervision orders") + .withIssueDate("2022-12-12") + .withAuthor("Eskander") + .grantLicense() + .build(); + + Group groupA = + GroupBuilder.createGroup(context) + .withName("group A") + .addMember(admin) + .build(); + + Group groupB = + GroupBuilder.createGroup(context) + .withName("group B") + .addMember(eperson) + .build(); + + SupervisionOrder supervisionOrderOne = SupervisionOrderBuilder + .createSupervisionOrder(context, witemOne.getItem(), groupA) + .build(); + + SupervisionOrder supervisionOrderTwo = SupervisionOrderBuilder + .createSupervisionOrder(context, witemOne.getItem(), groupB) + .build(); + + //disable file upload mandatory + configurationService.setProperty("webui.submit.upload.required", false); + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + String authToken = getAuthToken(eperson.getEmail(), password); + + // Item's supervision orders endpoint of itemOne by not admin + getClient(authToken) + .perform(get("/api/submission/workspaceitems/" + witemOne.getID() + "/supervisionOrders")) + .andExpect(status().isForbidden()); + + // Item's supervision orders endpoint of itemOne by admin + getClient(adminToken) + .perform(get("/api/submission/workspaceitems/" + witemOne.getID() + "/supervisionOrders")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(2))) + .andExpect(jsonPath("$._embedded.supervisionOrders", containsInAnyOrder( + matchSuperVisionOrder(supervisionOrderOne), + matchSuperVisionOrder(supervisionOrderTwo) + ))) + .andExpect(jsonPath("$._links.self.href", containsString( + "/api/submission/workspaceitems/" + witemOne.getID() + "/supervisionOrders") + )); + + // Item's supervision orders endpoint of itemTwo by admin + getClient(adminToken) + .perform(get("/api/submission/workspaceitems/" + witemTwo.getID() + "/supervisionOrders")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(0))) + .andExpect(jsonPath("$._embedded.supervisionOrders", empty())) + .andExpect(jsonPath("$._links.self.href", containsString( + "/api/submission/workspaceitems/" + witemTwo.getID() + "/supervisionOrders") + )); + } + + @Test + public void patchBySupervisorTest() throws Exception { + context.turnOffAuthorisationSystem(); + + EPerson userA = + EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withEmail("userA@test.com") + .withPassword(password) + .build(); + + EPerson userB = + EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withEmail("userB@test.com") + .withPassword(password) + .build(); + + EPerson userC = + EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withEmail("userC@test.com") + .withPassword(password) + .build(); + + parentCommunity = + CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection publications = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Publications") + .withEntityType("Publication") + .build(); + + Group groupA = + GroupBuilder.createGroup(context) + .withName("group A") + .addMember(userA) + .build(); + + Group groupB = + GroupBuilder.createGroup(context) + .withName("group B") + .addMember(userB) + .build(); + + WorkspaceItem witem = + WorkspaceItemBuilder.createWorkspaceItem(context, publications) + .withTitle("Workspace Item 1") + .withIssueDate("2017-10-17") + .withSubject("ExtraEntry") + .grantLicense() + .build(); + + //disable file upload mandatory + configurationService.setProperty("webui.submit.upload.required", false); + context.restoreAuthSystemState(); + + getClient(getAuthToken(admin.getEmail(), password)) + .perform(post("/api/core/supervisionorders/") + .param("uuid", witem.getItem().getID().toString()) + .param("group", groupA.getID().toString()) + .param("type", "EDITOR") + .contentType(contentType)) + .andExpect(status().isCreated()); + + getClient(getAuthToken(admin.getEmail(), password)) + .perform(post("/api/core/supervisionorders/") + .param("uuid", witem.getItem().getID().toString()) + .param("group", groupB.getID().toString()) + .param("type", "OBSERVER") + .contentType(contentType)) + .andExpect(status().isCreated()); + + String authTokenA = getAuthToken(userA.getEmail(), password); + String authTokenB = getAuthToken(userB.getEmail(), password); + String authTokenC = getAuthToken(userC.getEmail(), password); + + getClient(authTokenC).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isForbidden()); + + // a simple patch to update an existent metadata + List updateTitle = new ArrayList<>(); + Map value = new HashMap<>(); + value.put("value", "New Title"); + updateTitle.add(new ReplaceOperation("/sections/traditionalpageone/dc.title/0", value)); + String patchBody = getPatchContent(updateTitle); + + getClient(authTokenB).perform(patch("/api/submission/workspaceitems/" + witem.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isForbidden()); + + getClient(authTokenA).perform(patch("/api/submission/workspaceitems/" + witem.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.errors").doesNotExist()) + .andExpect(jsonPath("$", + // check the new title and untouched values + Matchers.is(WorkspaceItemMatcher + .matchItemWithTitleAndDateIssuedAndSubject( + witem, + "New Title", "2017-10-17", + "ExtraEntry")))); + + getClient(authTokenA).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.errors").doesNotExist()) + .andExpect(jsonPath("$", + Matchers.is( + WorkspaceItemMatcher + .matchItemWithTitleAndDateIssuedAndSubject( + witem, + "New Title", "2017-10-17", + "ExtraEntry") + ))); + + getClient(authTokenB).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.errors").doesNotExist()) + .andExpect(jsonPath("$", + Matchers.is( + WorkspaceItemMatcher + .matchItemWithTitleAndDateIssuedAndSubject( + witem, + "New Title", "2017-10-17", + "ExtraEntry") + ))); + } + } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanSubscribeFeatureIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanSubscribeFeatureIT.java new file mode 100644 index 0000000000..7eb0960566 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanSubscribeFeatureIT.java @@ -0,0 +1,310 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the 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; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.io.IOException; +import java.sql.SQLException; +import java.util.List; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.dspace.app.rest.authorization.impl.CanSubscribeFeature; +import org.dspace.app.rest.converter.CollectionConverter; +import org.dspace.app.rest.converter.CommunityConverter; +import org.dspace.app.rest.converter.ItemConverter; +import org.dspace.app.rest.matcher.AuthorizationMatcher; +import org.dspace.app.rest.model.CollectionRest; +import org.dspace.app.rest.model.CommunityRest; +import org.dspace.app.rest.model.ItemRest; +import org.dspace.app.rest.projection.DefaultProjection; +import org.dspace.app.rest.projection.Projection; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.authorize.AuthorizeException; +import org.dspace.authorize.ResourcePolicy; +import org.dspace.authorize.service.ResourcePolicyService; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.EPersonBuilder; +import org.dspace.builder.GroupBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.builder.ResourcePolicyBuilder; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.DSpaceObject; +import org.dspace.content.Item; +import org.dspace.core.Constants; +import org.dspace.discovery.SearchServiceException; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.Group; +import org.hamcrest.Matchers; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Test of Subscribe Dso Feature implementation. + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.com) + */ +public class CanSubscribeFeatureIT extends AbstractControllerIntegrationTest { + + private static final Logger log = LogManager.getLogger(CanSubscribeFeatureIT.class); + + @Autowired + private ItemConverter itemConverter; + @Autowired + private CollectionConverter collectionConverter; + @Autowired + private CommunityConverter communityConverter; + @Autowired + private AuthorizationFeatureService authorizationFeatureService; + @Autowired + private ResourcePolicyService resourcePolicyService; + + private Community communityAuthorized; + private Collection collectionAuthorized; + private AuthorizationFeature canSubscribeFeature; + + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Community") + .build(); + communityAuthorized = CommunityBuilder.createCommunity(context) + .withName("communityA") + .build(); + collectionAuthorized = CollectionBuilder.createCollection(context, communityAuthorized) + .withName("Collection A") + .build(); + context.restoreAuthSystemState(); + canSubscribeFeature = authorizationFeatureService.find(CanSubscribeFeature.NAME); + } + + @Test + public void canSubscribeCommunityAndCollectionTest() throws Exception { + context.turnOffAuthorisationSystem(); + CommunityRest comRest = communityConverter.convert(parentCommunity, DefaultProjection.DEFAULT); + CollectionRest colRest = collectionConverter.convert(collectionAuthorized, DefaultProjection.DEFAULT); + + // define authorizations that we know must exists + Authorization epersonToCommunity = new Authorization(eperson, canSubscribeFeature, comRest); + Authorization adminToCommunity = new Authorization(admin, canSubscribeFeature, comRest); + Authorization epersonToCollection = new Authorization(eperson, canSubscribeFeature, colRest); + Authorization adminToCollection = new Authorization(admin, canSubscribeFeature, colRest); + + // define authorization that we know not exists + Authorization anonymousToCommunity = new Authorization(null, canSubscribeFeature, comRest); + Authorization anonymousToCollection = new Authorization(null, canSubscribeFeature, colRest); + context.restoreAuthSystemState(); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + String tokenEPerson = getAuthToken(eperson.getEmail(), password); + + getClient(tokenEPerson).perform(get("/api/authz/authorizations/" + epersonToCommunity.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is( + AuthorizationMatcher.matchAuthorization(epersonToCommunity)))); + + getClient(tokenAdmin).perform(get("/api/authz/authorizations/" + adminToCommunity.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is( + AuthorizationMatcher.matchAuthorization(adminToCommunity)))); + + getClient(tokenEPerson).perform(get("/api/authz/authorizations/" + epersonToCollection.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is( + AuthorizationMatcher.matchAuthorization(epersonToCollection)))); + + getClient(tokenAdmin).perform(get("/api/authz/authorizations/" + adminToCollection.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is( + AuthorizationMatcher.matchAuthorization(adminToCollection)))); + + getClient().perform(get("/api/authz/authorizations/" + anonymousToCommunity.getID())) + .andExpect(status().isNotFound()); + + getClient().perform(get("/api/authz/authorizations/" + anonymousToCollection.getID())) + .andExpect(status().isNotFound()); + } + + @Test + public void canNotSubscribeItemTest() throws Exception { + context.turnOffAuthorisationSystem(); + EPerson ePersonNotSubscribePermission = EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withPassword(password) + .withEmail("test@email.it") + .build(); + // the user to be tested is not part of the group with read permission + Group groupWithReadPermission = GroupBuilder.createGroup(context) + .withName("Group A") + .addMember(eperson) + .build(); + Item item = ItemBuilder.createItem(context, collectionAuthorized) + .withTitle("Test item") + .build(); + + cleanUpPermissions(resourcePolicyService.find(context, item)); + setPermissions(item, groupWithReadPermission, Constants.READ); + + ItemRest itemRest = itemConverter.convert(item, Projection.DEFAULT); + + // define authorization that we know not exists + Authorization anonymousToItem = new Authorization(null, canSubscribeFeature, itemRest); + Authorization epersonToItem = new Authorization(eperson, canSubscribeFeature, itemRest); + Authorization adminToItem = new Authorization(admin, canSubscribeFeature, itemRest); + Authorization ePersonNotSubscribePermissionToItem = new Authorization(ePersonNotSubscribePermission, + canSubscribeFeature, itemRest); + + context.restoreAuthSystemState(); + + String token1 = getAuthToken(eperson.getEmail(), password); + String token2 = getAuthToken(admin.getEmail(), password); + String token3 = getAuthToken(ePersonNotSubscribePermission.getEmail(), password); + + getClient(token1).perform(get("/api/authz/authorizations/" + epersonToItem.getID())) + .andExpect(status().isNotFound()); + + getClient(token2).perform(get("/api/authz/authorizations/" + adminToItem.getID())) + .andExpect(status().isNotFound()); + + getClient(token3).perform(get("/api/authz/authorizations/" + ePersonNotSubscribePermissionToItem.getID())) + .andExpect(status().isNotFound()); + + getClient().perform(get("/api/authz/authorizations/" + anonymousToItem.getID())) + .andExpect(status().isNotFound()); + } + + @Test + public void canNotSubscribeCollectionTest() throws Exception { + context.turnOffAuthorisationSystem(); + + EPerson ePersonNotSubscribePermission = EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withPassword(password) + .withEmail("test@email.it") + .build(); + + // the user to be tested is not part of the group with read permission + Group groupWithReadPermission = GroupBuilder.createGroup(context) + .withName("Group A") + .addMember(eperson) + .build(); + + cleanUpPermissions(resourcePolicyService.find(context, collectionAuthorized)); + setPermissions(collectionAuthorized, groupWithReadPermission, Constants.READ); + + CollectionRest collectionRest = collectionConverter.convert(collectionAuthorized, Projection.DEFAULT); + + // define authorizations that we know must exists + Authorization epersonToCollection = new Authorization(eperson, canSubscribeFeature, collectionRest); + Authorization adminToCollection = new Authorization(admin, canSubscribeFeature, collectionRest); + + // define authorization that we know not exists + Authorization ePersonNotSubscribePermissionToColl = new Authorization(ePersonNotSubscribePermission, + canSubscribeFeature, collectionRest); + + context.restoreAuthSystemState(); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + String tokenEPerson = getAuthToken(eperson.getEmail(), password); + String token = getAuthToken(ePersonNotSubscribePermission.getEmail(), password); + + getClient(tokenEPerson).perform(get("/api/authz/authorizations/" + epersonToCollection.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is( + AuthorizationMatcher.matchAuthorization(epersonToCollection)))); + + getClient(tokenAdmin).perform(get("/api/authz/authorizations/" + adminToCollection.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is( + AuthorizationMatcher.matchAuthorization(adminToCollection)))); + + getClient(token).perform(get("/api/authz/authorizations/" + ePersonNotSubscribePermissionToColl.getID())) + .andExpect(status().isNotFound()); + } + + @Test + public void canNotSubscribeCommunityTest() throws Exception { + context.turnOffAuthorisationSystem(); + + EPerson ePersonNotSubscribePermission = EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withPassword(password) + .withEmail("test@email.it") + .build(); + + // the user to be tested is not part of the group with read permission + Group groupWithReadPermission = GroupBuilder.createGroup(context) + .withName("Group A") + .addMember(eperson) + .build(); + + cleanUpPermissions(resourcePolicyService.find(context, communityAuthorized)); + setPermissions(communityAuthorized, groupWithReadPermission, Constants.READ); + + CommunityRest communityRest = communityConverter.convert(communityAuthorized, Projection.DEFAULT); + + // define authorizations that we know must exists + Authorization epersonToComm = new Authorization(eperson, canSubscribeFeature, communityRest); + Authorization adminToComm = new Authorization(admin, canSubscribeFeature, communityRest); + + // define authorization that we know not exists + Authorization ePersonNotSubscribePermissionToComm = new Authorization(ePersonNotSubscribePermission, + canSubscribeFeature, communityRest); + + context.restoreAuthSystemState(); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + String tokenEPerson = getAuthToken(eperson.getEmail(), password); + String token = getAuthToken(ePersonNotSubscribePermission.getEmail(), password); + + getClient(tokenEPerson).perform(get("/api/authz/authorizations/" + epersonToComm.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is( + AuthorizationMatcher.matchAuthorization(epersonToComm)))); + + getClient(tokenAdmin).perform(get("/api/authz/authorizations/" + adminToComm.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is( + AuthorizationMatcher.matchAuthorization(adminToComm)))); + + getClient(token).perform(get("/api/authz/authorizations/" + ePersonNotSubscribePermissionToComm.getID())) + .andExpect(status().isNotFound()); + } + + private void setPermissions(DSpaceObject dSpaceObject, Group group, Integer permissions) { + try { + ResourcePolicyBuilder.createResourcePolicy(context) + .withDspaceObject(dSpaceObject) + .withAction(permissions) + .withGroup(group) + .build(); + } catch (SQLException | AuthorizeException sqlException) { + log.error(sqlException.getMessage()); + } + } + + private void cleanUpPermissions(List resourcePolicies) { + try { + for (ResourcePolicy resourcePolicy : resourcePolicies) { + ResourcePolicyBuilder.delete(resourcePolicy.getID()); + } + } catch (SQLException | SearchServiceException | IOException sqlException) { + log.error(sqlException.getMessage()); + } + } + +} \ No newline at end of file diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/EditItemFeatureIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/EditItemFeatureIT.java new file mode 100644 index 0000000000..4bdc7743b5 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/EditItemFeatureIT.java @@ -0,0 +1,279 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the 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; + +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.is; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.util.concurrent.Callable; + +import org.dspace.app.rest.authorization.impl.EditItemFeature; +import org.dspace.app.rest.converter.ItemConverter; +import org.dspace.app.rest.converter.SiteConverter; +import org.dspace.app.rest.model.ItemRest; +import org.dspace.app.rest.model.SiteRest; +import org.dspace.app.rest.projection.DefaultProjection; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.app.rest.utils.Utils; +import org.dspace.authorize.ResourcePolicy; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.GroupBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.builder.ResourcePolicyBuilder; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.Item; +import org.dspace.content.Site; +import org.dspace.content.service.SiteService; +import org.dspace.core.Constants; +import org.dspace.eperson.Group; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.web.servlet.ResultActions; + +public class EditItemFeatureIT extends AbstractControllerIntegrationTest { + @Autowired + private AuthorizationFeatureService authorizationFeatureService; + @Autowired + private SiteService siteService; + @Autowired + private ItemConverter itemConverter; + @Autowired + private SiteConverter siteConverter; + @Autowired + private Utils utils; + + private Group group; + private String siteUri; + private String epersonToken; + private AuthorizationFeature editItemFeature; + private Community communityA; + + private Collection collectionA1; + private Collection collectionA2; + + private Item itemA1X; + private Item itemA2X; + + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + + withSuppressedAuthorization(() -> { + communityA = CommunityBuilder.createCommunity(context).withName("Community A").build(); + collectionA1 = CollectionBuilder.createCollection(context, communityA).withName("Collection A1").build(); + collectionA2 = CollectionBuilder.createCollection(context, communityA).withName("Collection A2").build(); + itemA1X = ItemBuilder.createItem(context, collectionA1).withTitle("Item A1X").build(); + itemA2X = ItemBuilder.createItem(context, collectionA2).withTitle("Item A2X").build(); + group = GroupBuilder.createGroup(context) + .withName("group") + .addMember(eperson) + .build(); + return null; + }); + editItemFeature = authorizationFeatureService.find(EditItemFeature.NAME); + + Site site = siteService.findSite(context); + SiteRest siteRest = siteConverter.convert(site, DefaultProjection.DEFAULT); + siteUri = utils.linkToSingleResource(siteRest, "self").getHref(); + } + + @Test + public void testNoRights() throws Exception { + expectZeroResults(requestSitewideEditItemFeature()); + } + + @Test + public void testDirectEPersonWritePolicy() throws Exception { + ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context) + .withUser(eperson) + .withDspaceObject(itemA1X) + .withAction(Constants.WRITE) + .build(); + expectSomeResults(requestSitewideEditItemFeature()); + expectSomeResults(requestEditItemFeature(itemA1X)); + expectZeroResults(requestEditItemFeature(itemA2X)); + } + + @Test + public void testDirectGroupWritePolicy() throws Exception { + ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context) + .withGroup(group) + .withDspaceObject(itemA1X) + .withAction(Constants.WRITE) + .build(); + expectSomeResults(requestSitewideEditItemFeature()); + expectSomeResults(requestEditItemFeature(itemA1X)); + expectZeroResults(requestEditItemFeature(itemA2X)); + } + + @Test + public void testDirectEPersonAdminPolicy() throws Exception { + ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context) + .withUser(eperson) + .withDspaceObject(itemA1X) + .withAction(Constants.ADMIN) + .build(); + expectSomeResults(requestSitewideEditItemFeature()); + expectSomeResults(requestEditItemFeature(itemA1X)); + expectZeroResults(requestEditItemFeature(itemA2X)); + } + + @Test + public void testDirectGroupAdminPolicy() throws Exception { + ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context) + .withGroup(group) + .withDspaceObject(itemA1X) + .withAction(Constants.ADMIN) + .build(); + expectSomeResults(requestSitewideEditItemFeature()); + expectSomeResults(requestEditItemFeature(itemA1X)); + expectZeroResults(requestEditItemFeature(itemA2X)); + } + + @Test + public void testNonemptyCollectionAdmin() throws Exception { + Item item = withSuppressedAuthorization(() -> { + Collection col = CollectionBuilder + .createCollection(context, communityA) + .withName("nonempty collection") + .withAdminGroup(eperson) + .build(); + return ItemBuilder + .createItem(context, col) + .withTitle("item in nonempty collection") + .build(); + }); + expectSomeResults(requestSitewideEditItemFeature()); + expectSomeResults(requestEditItemFeature(item)); + expectZeroResults(requestEditItemFeature(itemA1X)); + expectZeroResults(requestEditItemFeature(itemA2X)); + } + + @Test + public void testEmptyCollectionAdmin() throws Exception { + withSuppressedAuthorization(() -> { + Collection col = CollectionBuilder + .createCollection(context, communityA) + .withName("nonempty collection") + .withAdminGroup(eperson) + .build(); + return null; + }); + expectZeroResults(requestSitewideEditItemFeature()); + } + + @Test + public void testCommunityWithEmptyCollectionAdmin() throws Exception { + withSuppressedAuthorization(() -> { + Community comm = CommunityBuilder + .createCommunity(context) + .withName("This community contains a collection") + .withAdminGroup(eperson) + .build(); + Collection coll = CollectionBuilder + .createCollection(context, comm) + .withName("This collection contains no items") + .build(); + return null; + }); + expectZeroResults(requestSitewideEditItemFeature()); + } + + @Test + public void testCommunityWithNonemptyCollectionAdmin() throws Exception { + Item item = withSuppressedAuthorization(() -> { + Community comm = CommunityBuilder + .createCommunity(context) + .withName("This community contains a collection") + .withAdminGroup(eperson) + .build(); + Collection coll = CollectionBuilder + .createCollection(context, comm) + .withName("This collection contains an item") + .build(); + return ItemBuilder + .createItem(context, coll) + .withTitle("This is an item") + .build(); + }); + expectSomeResults(requestSitewideEditItemFeature()); + expectSomeResults(requestEditItemFeature(item)); + } + + @Test + public void testNestedCommunitiesWithNonemptyCollectionAdmin() throws Exception { + Item item = withSuppressedAuthorization(() -> { + Community parent = CommunityBuilder + .createCommunity(context) + .withName("parent community") + .withAdminGroup(eperson) + .build(); + Community child = CommunityBuilder + .createSubCommunity(context, parent) + .withName("child community") + .withAdminGroup(eperson) + .build(); + Collection coll = CollectionBuilder + .createCollection(context, child) + .withName("This collection contains an item") + .build(); + return ItemBuilder + .createItem(context, coll) + .withTitle("This is an item") + .build(); + }); + expectSomeResults(requestSitewideEditItemFeature()); + expectSomeResults(requestEditItemFeature(item)); + } + + private ResultActions requestSitewideEditItemFeature() throws Exception { + return requestEditItemFeature(siteUri); + } + + private ResultActions requestEditItemFeature(Item item) throws Exception { + return requestEditItemFeature(getItemUri(item)); + } + private ResultActions requestEditItemFeature(String uri) throws Exception { + epersonToken = getAuthToken(eperson.getEmail(), password); + return getClient(epersonToken).perform(get("/api/authz/authorizations/search/object?") + .param("uri", uri) + .param("feature", editItemFeature.getName()) + .param("embed", "feature")); + } + + private ResultActions expectSomeResults(ResultActions actions) throws Exception { + return actions + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", greaterThan(0))); + } + + private ResultActions expectZeroResults(ResultActions actions) throws Exception { + return actions + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(0))); + } + + private T withSuppressedAuthorization(Callable fn) throws Exception { + context.turnOffAuthorisationSystem(); + T result = fn.call(); + context.restoreAuthSystemState(); + return result; + } + + private String getItemUri(Item item) { + ItemRest itemRest = itemConverter.convert(item, DefaultProjection.DEFAULT); + return utils.linkToSingleResource(itemRest, "self").getHref(); + } +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/SubmitFeatureIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/SubmitFeatureIT.java new file mode 100644 index 0000000000..bba45ecd22 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/SubmitFeatureIT.java @@ -0,0 +1,328 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the 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; + +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.is; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.util.concurrent.Callable; + +import org.dspace.app.rest.authorization.impl.SubmitFeature; +import org.dspace.app.rest.converter.CollectionConverter; +import org.dspace.app.rest.converter.SiteConverter; +import org.dspace.app.rest.model.CollectionRest; +import org.dspace.app.rest.model.SiteRest; +import org.dspace.app.rest.projection.DefaultProjection; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.app.rest.utils.Utils; +import org.dspace.authorize.ResourcePolicy; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.GroupBuilder; +import org.dspace.builder.ResourcePolicyBuilder; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.Site; +import org.dspace.content.service.SiteService; +import org.dspace.core.Constants; +import org.dspace.eperson.Group; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.web.servlet.ResultActions; + +public class SubmitFeatureIT extends AbstractControllerIntegrationTest { + @Autowired + private AuthorizationFeatureService authorizationFeatureService; + @Autowired + private SiteService siteService; + @Autowired + private CollectionConverter collectionConverter; + @Autowired + private SiteConverter siteConverter; + @Autowired + private Utils utils; + + private Group group; + private String siteUri; + private String epersonToken; + private AuthorizationFeature submitFeature; + private Community communityA; + + private Collection collectionA1; + private Collection collectionA2; + + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + + withSuppressedAuthorization(() -> { + communityA = CommunityBuilder.createCommunity(context).withName("Community A").build(); + collectionA1 = CollectionBuilder.createCollection(context, communityA).withName("Collection A1").build(); + collectionA2 = CollectionBuilder.createCollection(context, communityA).withName("Collection A2").build(); + group = GroupBuilder.createGroup(context) + .withName("group") + .addMember(eperson) + .build(); + return null; + }); + submitFeature = authorizationFeatureService.find(SubmitFeature.NAME); + + Site site = siteService.findSite(context); + SiteRest siteRest = siteConverter.convert(site, DefaultProjection.DEFAULT); + siteUri = utils.linkToSingleResource(siteRest, "self").getHref(); + } + + @Test + public void testNoRights() throws Exception { + expectZeroResults(requestSitewideSubmitFeature()); + } + + @Test + public void testDirectEPersonAddPolicy() throws Exception { + ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context) + .withUser(eperson) + .withDspaceObject(collectionA1) + .withAction(Constants.ADD) + .build(); + expectSomeResults(requestSitewideSubmitFeature()); + } + + @Test + public void testDirectGroupAddPolicy() throws Exception { + ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context) + .withGroup(group) + .withDspaceObject(collectionA1) + .withAction(Constants.ADD) + .build(); + expectSomeResults(requestSitewideSubmitFeature()); + } + + @Test + public void testDirectEPersonAdminPolicy() throws Exception { + ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context) + .withUser(eperson) + .withDspaceObject(collectionA1) + .withAction(Constants.ADMIN) + .build(); + expectSomeResults(requestSitewideSubmitFeature()); + } + + @Test + public void testDirectGroupAdminPolicy() throws Exception { + ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context) + .withGroup(group) + .withDspaceObject(collectionA1) + .withAction(Constants.ADMIN) + .build(); + expectSomeResults(requestSitewideSubmitFeature()); + } + + @Test + public void testCollectionAdmin() throws Exception { + withSuppressedAuthorization(() -> { + Collection col = CollectionBuilder + .createCollection(context, communityA) + .withName("this is another test collection") + .withAdminGroup(eperson) + .build(); + return null; + }); + expectSomeResults(requestSitewideSubmitFeature()); + } + + @Test + public void testCommunityWithoutCollectionsAdmin() throws Exception { + withSuppressedAuthorization(() -> { + Community comm = CommunityBuilder + .createCommunity(context) + .withName("This community contains no collections") + .withAdminGroup(eperson) + .build(); + return null; + }); + expectZeroResults(requestSitewideSubmitFeature()); + } + + @Test + public void testCommunityWithCollectionsAdmin() throws Exception { + withSuppressedAuthorization(() -> { + Community comm = CommunityBuilder + .createCommunity(context) + .withName("This community contains a collection") + .withAdminGroup(eperson) + .build(); + Collection coll = CollectionBuilder + .createCollection(context, comm) + .withName("Contained collection") + .build(); + return null; + }); + expectSomeResults(requestSitewideSubmitFeature()); + } + + @Test + public void testCommunityWithSubCommunityWithCollectionsAdmin() throws Exception { + withSuppressedAuthorization(() -> { + Community parent = CommunityBuilder + .createCommunity(context) + .withName("This community contains no collections") + .withAdminGroup(eperson) + .build(); + Community child = CommunityBuilder + .createSubCommunity(context, parent) + .withName("This community contains a collection") + .build(); + Collection coll = CollectionBuilder + .createCollection(context, child) + .withName("Contained collection") + .build(); + return null; + }); + expectSomeResults(requestSitewideSubmitFeature()); + } + + @Test + public void testNoRightsOnCollection() throws Exception { + expectZeroResults(requestSubmitFeature(getCollectionUri(collectionA1))); + expectZeroResults(requestSubmitFeature(getCollectionUri(collectionA2))); + } + + @Test + public void testDirectEPersonAddPolicyOnCollection() throws Exception { + ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context) + .withUser(eperson) + .withDspaceObject(collectionA1) + .withAction(Constants.ADD) + .build(); + expectSomeResults(requestSubmitFeature(getCollectionUri(collectionA1))); + expectZeroResults(requestSubmitFeature(getCollectionUri(collectionA2))); + } + + @Test + public void testDirectGroupAddPolicyOnCollection() throws Exception { + ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context) + .withGroup(group) + .withDspaceObject(collectionA1) + .withAction(Constants.ADD) + .build(); + expectSomeResults(requestSubmitFeature(getCollectionUri(collectionA1))); + expectZeroResults(requestSubmitFeature(getCollectionUri(collectionA2))); + } + + @Test + public void testDirectEPersonAdminPolicyOnCollection() throws Exception { + ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context) + .withUser(eperson) + .withDspaceObject(collectionA1) + .withAction(Constants.ADMIN) + .build(); + expectSomeResults(requestSubmitFeature(getCollectionUri(collectionA1))); + expectZeroResults(requestSubmitFeature(getCollectionUri(collectionA2))); + } + + @Test + public void testDirectGroupAdminPolicyOnCollection() throws Exception { + ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context) + .withGroup(group) + .withDspaceObject(collectionA1) + .withAction(Constants.ADMIN) + .build(); + expectSomeResults(requestSubmitFeature(getCollectionUri(collectionA1))); + expectZeroResults(requestSubmitFeature(getCollectionUri(collectionA2))); + } + + @Test + public void testCollectionAdminOnCollection() throws Exception { + Collection col = withSuppressedAuthorization(() -> { + return CollectionBuilder + .createCollection(context, communityA) + .withName("this is another test collection") + .withAdminGroup(eperson) + .build(); + }); + expectSomeResults(requestSubmitFeature(getCollectionUri(col))); + expectZeroResults(requestSubmitFeature(getCollectionUri(collectionA1))); + } + + @Test + public void testCommunityWithCollectionsAdminOnCollection() throws Exception { + Collection coll = withSuppressedAuthorization(() -> { + Community comm = CommunityBuilder + .createCommunity(context) + .withName("This community contains a collection") + .withAdminGroup(eperson) + .build(); + return CollectionBuilder + .createCollection(context, comm) + .withName("Contained collection") + .build(); + }); + expectSomeResults(requestSubmitFeature(getCollectionUri(coll))); + } + + @Test + public void testCommunityWithSubCommunityWithCollectionsAdminOnCollection() throws Exception { + Collection coll = withSuppressedAuthorization(() -> { + Community parent = CommunityBuilder + .createCommunity(context) + .withName("This community contains no collections") + .withAdminGroup(eperson) + .build(); + Community child = CommunityBuilder + .createSubCommunity(context, parent) + .withName("This community contains a collection") + .build(); + return CollectionBuilder + .createCollection(context, child) + .withName("Contained collection") + .build(); + }); + expectSomeResults(requestSubmitFeature(getCollectionUri(coll))); + } + + private ResultActions requestSitewideSubmitFeature() throws Exception { + return requestSubmitFeature(siteUri); + } + + private ResultActions requestSubmitFeature(String uri) throws Exception { + epersonToken = getAuthToken(eperson.getEmail(), password); + return getClient(epersonToken).perform(get("/api/authz/authorizations/search/object?") + .param("uri", uri) + .param("feature", submitFeature.getName()) + .param("embed", "feature")); + } + + private ResultActions expectSomeResults(ResultActions actions) throws Exception { + return actions + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", greaterThan(0))); + } + + private ResultActions expectZeroResults(ResultActions actions) throws Exception { + return actions + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(0))); + } + + private T withSuppressedAuthorization(Callable fn) throws Exception { + context.turnOffAuthorisationSystem(); + T result = fn.call(); + context.restoreAuthSystemState(); + return result; + } + + private String getCollectionUri(Collection collection) { + CollectionRest collectionRest = collectionConverter.convert(collection, DefaultProjection.DEFAULT); + return utils.linkToSingleResource(collectionRest, "self").getHref(); + } +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/iiif/IIIFControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/iiif/IIIFControllerIT.java index 08ed91500d..b4d1f785d4 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/iiif/IIIFControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/iiif/IIIFControllerIT.java @@ -22,12 +22,14 @@ import org.apache.commons.codec.CharEncoding; import org.apache.commons.io.IOUtils; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.builder.BitstreamBuilder; +import org.dspace.builder.BundleBuilder; import org.dspace.builder.CollectionBuilder; import org.dspace.builder.CommunityBuilder; import org.dspace.builder.EPersonBuilder; import org.dspace.builder.GroupBuilder; import org.dspace.builder.ItemBuilder; import org.dspace.content.Bitstream; +import org.dspace.content.Bundle; import org.dspace.content.Collection; import org.dspace.content.Item; import org.dspace.content.service.ItemService; @@ -85,7 +87,7 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest { } @Test - public void findOneIIIFSearchableEntityTypeWithGlobalConfigIT() throws Exception { + public void findOneIIIFSearchableItemWithDefaultDimensionsIT() throws Exception { context.turnOffAuthorisationSystem(); parentCommunity = CommunityBuilder.createCommunity(context) .withName("Parent Community") @@ -138,7 +140,8 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest { .andExpect(jsonPath("$.sequences[0].canvases[0].@id", Matchers.containsString("/iiif/" + publicItem1.getID() + "/canvas/c0"))) .andExpect(jsonPath("$.sequences[0].canvases[0].label", is("Page 1"))) - .andExpect(jsonPath("$.sequences[0].canvases[0].width", is(2200))) + .andExpect(jsonPath("$.sequences[0].canvases[0].width", is(64))) + .andExpect(jsonPath("$.sequences[0].canvases[0].height", is(64))) .andExpect(jsonPath("$.sequences[0].canvases[0].images[0].resource.service.@id", Matchers.endsWith(bitstream1.getID().toString()))) .andExpect(jsonPath("$.sequences[0].canvases[0].metadata[0].label", is("File name"))) @@ -1290,4 +1293,45 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest { .andExpect(jsonPath("$.metadata[0].value", is("Public item (revised)"))); } + @Test + public void setDefaultCanvasDimensionCustomBundle() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1") + .build(); + + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Public item 1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald").withAuthor("Doe, John") + .enableIIIF() + .build(); + + + Bundle targetBundle = BundleBuilder.createBundle(context, publicItem1) + .withName(IIIFBundle) + .build(); + + String bitstreamContent = "ThisIsSomeDummyText"; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + Bitstream bitstream1 = BitstreamBuilder + .createBitstream(context, targetBundle, is) + .withName("Bitstream1.jpg") + .withMimeType("image/jpeg") + .build(); + } + context.restoreAuthSystemState(); + + // canvas dimensions using bitstream in the custom bundle (no bitstreams in ORIGINAL) + getClient().perform(get("/iiif/" + publicItem1.getID() + "/manifest")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sequences[0].canvases[0].width", is(64))) + .andExpect(jsonPath("$.sequences[0].canvases[0].height", is(64))); + } + } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ClaimedTaskMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ClaimedTaskMatcher.java index 0d2ba67f16..4ab812f82c 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ClaimedTaskMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ClaimedTaskMatcher.java @@ -28,8 +28,8 @@ public class ClaimedTaskMatcher { /** * Check if the returned json expose all the required links and properties * - * @param ptask - * the pool task + * @param cTask + * the claimed task * @param step * the step name * @return diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ExternalSourceMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ExternalSourceMatcher.java index ce0cda0b58..f304540eff 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ExternalSourceMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ExternalSourceMatcher.java @@ -10,8 +10,12 @@ package org.dspace.app.rest.matcher; import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; import static org.dspace.app.rest.test.AbstractControllerIntegrationTest.REST_SERVER_URL; import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.is; +import java.util.ArrayList; +import java.util.List; + import org.dspace.external.provider.ExternalDataProvider; import org.hamcrest.Matcher; @@ -20,6 +24,28 @@ public class ExternalSourceMatcher { private ExternalSourceMatcher() { } + /** + * Matcher which checks if all external source providers are listed (in exact order), up to the maximum number + * @param providers List of providers to check against + * @param max maximum number to check + * @return Matcher for this list of providers + */ + public static Matcher>> matchAllExternalSources( + List providers, int max) { + List matchers = new ArrayList<>(); + int count = 0; + for (ExternalDataProvider provider: providers) { + count++; + if (count > max) { + break; + } + matchers.add(matchExternalSource(provider)); + } + + // Make sure all providers exist in this exact order + return contains(matchers.toArray(new Matcher[0])); + } + public static Matcher matchExternalSource(ExternalDataProvider provider) { return matchExternalSource(provider.getSourceIdentifier(), provider.getSourceIdentifier(), false); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/FacetEntryMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/FacetEntryMatcher.java index 5e3c477506..82bedf4a92 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/FacetEntryMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/FacetEntryMatcher.java @@ -67,6 +67,17 @@ public class FacetEntryMatcher { ); } + public static Matcher supervisedByFacet(boolean hasNext) { + return allOf( + hasJsonPath("$.name", is("supervisedBy")), + hasJsonPath("$.facetType", is("authority")), + hasJsonPath("$.facetLimit", any(Integer.class)), + hasJsonPath("$._links.self.href", containsString("api/discover/facets/supervisedBy")), + hasJsonPath("$._links", matchNextLink(hasNext, "api/discover/facets/supervisedBy")) + + ); + } + public static Matcher dateIssuedFacet(boolean hasNext) { return allOf( hasJsonPath("$.name", is("dateIssued")), diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/FacetValueMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/FacetValueMatcher.java index a68356da53..330d31263b 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/FacetValueMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/FacetValueMatcher.java @@ -31,6 +31,13 @@ public class FacetValueMatcher { ); } + public static Matcher entryFacetWithoutSelfLink(String label) { + return allOf( + hasJsonPath("$.label", is(label)), + hasJsonPath("$.type", is("discover")) + ); + } + public static Matcher entryAuthorWithAuthority(String label, String authority, int count) { return allOf( hasJsonPath("$.authorityKey", is(authority)), @@ -48,15 +55,20 @@ public class FacetValueMatcher { hasJsonPath("$.type", is("discover")), hasJsonPath("$.count", is(count)), hasJsonPath("$._links.search.href", containsString("api/discover/search/objects")), - hasJsonPath("$._links.search.href", containsString("f.subject=" + label + ",equals")) + hasJsonPath("$._links.search.href", containsString( + "f.subject=" + urlPathSegmentEscaper().escape(label) + ",equals")) ); } - public static Matcher entrySubject(String label, String authority, int count) { + public static Matcher entrySubjectWithAuthority(String label, String authority, int count) { return allOf( hasJsonPath("$.authorityKey", is(authority)), - entrySubject(label, count) + hasJsonPath("$.count", is(count)), + hasJsonPath("$.label", is(label)), + hasJsonPath("$.type", is("discover")), + hasJsonPath("$._links.search.href", containsString("api/discover/search/objects")), + hasJsonPath("$._links.search.href", containsString("f.subject=" + authority + ",authority")) ); } @@ -90,4 +102,16 @@ public class FacetValueMatcher { hasJsonPath("$._links.search.href", containsString(",equals")) ); } + + public static Matcher entrySupervisedBy(String label, String authority, int count) { + return allOf( + hasJsonPath("$.authorityKey", is(authority)), + hasJsonPath("$.count", is(count)), + hasJsonPath("$.label", is(label)), + hasJsonPath("$.type", is("discover")), + hasJsonPath("$._links.search.href", containsString("api/discover/search/objects")), + hasJsonPath("$._links.search.href", containsString("f.supervisedBy=" + authority + ",authority")) + ); + } + } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ItemMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ItemMatcher.java index 371ad6b4b4..64905f90ea 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ItemMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ItemMatcher.java @@ -52,6 +52,7 @@ public class ItemMatcher { return matchEmbeds( "accessStatus", "bundles[]", + "identifiers", "mappedCollections[]", "owningCollection", "version", @@ -68,6 +69,7 @@ public class ItemMatcher { return HalMatcher.matchLinks(REST_SERVER_URL + "core/items/" + uuid, "accessStatus", "bundles", + "identifiers", "mappedCollections", "owningCollection", "relationships", diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/PoolTaskMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/PoolTaskMatcher.java index aeed70e527..8d43acbb0f 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/PoolTaskMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/PoolTaskMatcher.java @@ -28,7 +28,7 @@ public class PoolTaskMatcher { /** * Check if the returned json expose all the required links and properties * - * @param ptask + * @param pTask * the pool task * @param step * the step name diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/SubscriptionMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/SubscriptionMatcher.java new file mode 100644 index 0000000000..88349c73a3 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/SubscriptionMatcher.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.rest.matcher; + +import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; +import static org.dspace.app.rest.test.AbstractControllerIntegrationTest.REST_SERVER_URL; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.is; + +import java.util.stream.Collectors; + +import org.dspace.eperson.Subscription; +import org.hamcrest.Matcher; +import org.hamcrest.Matchers; + +/** + * Provide convenient org.hamcrest.Matcher to verify a SubscriptionRest json response + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.it) + */ +public class SubscriptionMatcher { + + private SubscriptionMatcher() {} + + public static Matcher matchSubscription(Subscription subscription) { + return allOf( + hasJsonPath("$.id", is(subscription.getID())), + hasJsonPath("$.type", is("subscription")), + hasJsonPath("$.subscriptionType", is(subscription.getSubscriptionType())), + hasJsonPath("$.subscriptionParameterList", Matchers.containsInAnyOrder( + subscription.getSubscriptionParameterList().stream() + .map(x -> SubscriptionMatcher.matchSubscriptionParameter(x.getName(), x.getValue())) + .collect(Collectors.toList()) + )), + //Check links + matchLinks(subscription.getID()) + ); + } + + public static Matcher matchSubscriptionParameter(String name, String value) { + return allOf( + hasJsonPath("$.name", is(name)), + hasJsonPath("$.value", is(value)) + ); + } + + /** + * Gets a matcher for all expected links. + */ + public static Matcher matchLinks(Integer id) { + return HalMatcher.matchLinks(REST_SERVER_URL + "core/subscriptions/" + id, + "resource", + "eperson", + "self" + ); + } + +} \ No newline at end of file diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/SupervisionOrderMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/SupervisionOrderMatcher.java new file mode 100644 index 0000000000..1ba147879d --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/SupervisionOrderMatcher.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.app.rest.matcher; + +import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; +import static org.dspace.app.rest.matcher.GroupMatcher.matchGroupEntry; +import static org.dspace.app.rest.matcher.ItemMatcher.matchItemProperties; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.is; + +import org.dspace.eperson.Group; +import org.dspace.supervision.SupervisionOrder; +import org.hamcrest.Matcher; + +/** + * Utility class to construct a Matcher for an SupervisionOrder object + * + * @author Mohamed Eskander (mohamed.eskander at 4science dot it) + */ +public class SupervisionOrderMatcher { + + private SupervisionOrderMatcher() { + } + + public static Matcher matchSuperVisionOrder(SupervisionOrder supervisionOrder) { + Group group = supervisionOrder.getGroup(); + return allOf( + hasJsonPath("$.id", is(supervisionOrder.getID())), + hasJsonPath("$._embedded.item", matchItemProperties(supervisionOrder.getItem())), + hasJsonPath("$._embedded.group", matchGroupEntry(group.getID(), group.getName())) + ); + } + +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/WorkflowActionMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/WorkflowActionMatcher.java index 69f9c501aa..e727eb8acc 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/WorkflowActionMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/WorkflowActionMatcher.java @@ -14,7 +14,10 @@ import static org.hamcrest.Matchers.is; import org.dspace.app.rest.model.WorkflowActionRest; import org.dspace.xmlworkflow.state.actions.WorkflowActionConfig; +import org.dspace.xmlworkflow.state.actions.processingaction.ScoreReviewActionAdvancedInfo; +import org.dspace.xmlworkflow.state.actions.processingaction.SelectReviewerActionAdvancedInfo; import org.hamcrest.Matcher; +import org.hamcrest.Matchers; /** * @author Maria Verdonck (Atmire) on 06/01/2020 @@ -32,7 +35,35 @@ public class WorkflowActionMatcher { return allOf( hasJsonPath("$.id", is(workflowAction.getId())), hasJsonPath("$.options", is(workflowAction.getOptions())), + hasJsonPath("$.advanced", is(workflowAction.isAdvanced())), hasJsonPath("$._links.self.href", containsString(WORKFLOW_ACTIONS_ENDPOINT + workflowAction.getId())) ); } + + /** + * Matcher to check the contents of the advancedInfo for "ratingreviewaction" + * @param scoreReviewActionAdvancedInfo identical ScoreReviewActionAdvancedInfo object + */ + public static Matcher matchScoreReviewActionAdvancedInfo( + ScoreReviewActionAdvancedInfo scoreReviewActionAdvancedInfo) { + return Matchers.allOf( + hasJsonPath("$.descriptionRequired", is(scoreReviewActionAdvancedInfo.isDescriptionRequired())), + hasJsonPath("$.maxValue", is(scoreReviewActionAdvancedInfo.getMaxValue())), + hasJsonPath("$.type", is(scoreReviewActionAdvancedInfo.getType())), + hasJsonPath("$.id", is(scoreReviewActionAdvancedInfo.getId())) + ); + } + + /** + * Matcher to check the contents of the advancedInfo for "selectrevieweraction" + * @param selectReviewerActionAdvancedInfo identical SelectReviewerActionAdvancedInfo object + */ + public static Matcher matchSelectReviewerActionAdvancedInfo( + SelectReviewerActionAdvancedInfo selectReviewerActionAdvancedInfo) { + return Matchers.allOf( + hasJsonPath("$.group", is(selectReviewerActionAdvancedInfo.getGroup())), + hasJsonPath("$.type", is(selectReviewerActionAdvancedInfo.getType())), + hasJsonPath("$.id", is(selectReviewerActionAdvancedInfo.getId())) + ); + } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/CommunityCollectionLinkRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/CommunityCollectionLinkRepositoryIT.java new file mode 100644 index 0000000000..24a94a4d4b --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/CommunityCollectionLinkRepositoryIT.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; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.dspace.app.rest.matcher.CollectionMatcher; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.hamcrest.Matchers; +import org.junit.Before; +import org.junit.Test; + +/** + * Test class for {@link CommunityCollectionLinkRepository} + */ +public class CommunityCollectionLinkRepositoryIT extends AbstractControllerIntegrationTest { + + Community parentCommunity; + Collection collection1; + Collection collection2; + Collection collection3; + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .build(); + collection1 = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + collection2 = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 2") + .build(); + collection3 = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 3") + .build(); + context.commit(); + context.restoreAuthSystemState(); + } + + @Test + public void getCollections_sortTitleASC() throws Exception { + String adminToken = getAuthToken(admin.getEmail(), password); + + getClient(adminToken).perform(get("/api/core/communities/" + parentCommunity.getID() + "/collections") + .param("sort", "dc.title,ASC")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.collections", Matchers.contains( + CollectionMatcher.matchCollection(collection1), + CollectionMatcher.matchCollection(collection2), + CollectionMatcher.matchCollection(collection3) + ))); + } + + @Test + public void getCollections_sortTitleDESC() throws Exception { + String adminToken = getAuthToken(admin.getEmail(), password); + + getClient(adminToken).perform(get("/api/core/communities/" + parentCommunity.getID() + "/collections") + .param("sort", "dc.title,DESC")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.collections", Matchers.contains( + CollectionMatcher.matchCollection(collection3), + CollectionMatcher.matchCollection(collection2), + CollectionMatcher.matchCollection(collection1) + ))); + } + +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/CommunitySubcommunityLinkRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/CommunitySubcommunityLinkRepositoryIT.java new file mode 100644 index 0000000000..aa3b1c0721 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/CommunitySubcommunityLinkRepositoryIT.java @@ -0,0 +1,80 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the 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 org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.dspace.app.rest.matcher.CommunityMatcher; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.builder.CommunityBuilder; +import org.dspace.content.Community; +import org.hamcrest.Matchers; +import org.junit.Before; +import org.junit.Test; + +/** + * Test class for {@link CommunitySubcommunityLinkRepository} + */ +public class CommunitySubcommunityLinkRepositoryIT extends AbstractControllerIntegrationTest { + + Community parentCommunity; + Community subCommunity1; + Community subCommunity2; + Community subCommunity3; + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .build(); + subCommunity1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub community 1") + .build(); + subCommunity2 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub community 2") + .build(); + subCommunity3 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub community 3") + .build(); + context.commit(); + context.restoreAuthSystemState(); + } + + @Test + public void getSubCommunities_sortTitleASC() throws Exception { + String adminToken = getAuthToken(admin.getEmail(), password); + + getClient(adminToken).perform(get("/api/core/communities/" + parentCommunity.getID() + "/subcommunities") + .param("sort", "dc.title,ASC")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.subcommunities", Matchers.contains( + CommunityMatcher.matchCommunity(subCommunity1), + CommunityMatcher.matchCommunity(subCommunity2), + CommunityMatcher.matchCommunity(subCommunity3) + ))); + } + + @Test + public void getSubCommunities_sortTitleDESC() throws Exception { + String adminToken = getAuthToken(admin.getEmail(), password); + + getClient(adminToken).perform(get("/api/core/communities/" + parentCommunity.getID() + "/subcommunities") + .param("sort", "dc.title,DESC")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.subcommunities", Matchers.contains( + CommunityMatcher.matchCommunity(subCommunity3), + CommunityMatcher.matchCommunity(subCommunity2), + CommunityMatcher.matchCommunity(subCommunity1) + ))); + } + +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/iiif/MockIIIFApiQueryServiceImpl.java b/dspace-server-webapp/src/test/java/org/dspace/iiif/MockIIIFApiQueryServiceImpl.java new file mode 100644 index 0000000000..a240e76f97 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/iiif/MockIIIFApiQueryServiceImpl.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.iiif; + +import org.dspace.content.Bitstream; + +/** + * Mock for the IIIFApiQueryService. + * @author Michael Spalti (mspalti at willamette.edu) + */ +public class MockIIIFApiQueryServiceImpl extends IIIFApiQueryServiceImpl { + public int[] getImageDimensions(Bitstream bitstream) { + return new int[]{64, 64}; + } +} diff --git a/dspace-server-webapp/src/test/resources/org/dspace/app/rest/dataCite-test.json b/dspace-server-webapp/src/test/resources/org/dspace/app/rest/dataCite-test.json new file mode 100644 index 0000000000..8ede6f29a0 --- /dev/null +++ b/dspace-server-webapp/src/test/resources/org/dspace/app/rest/dataCite-test.json @@ -0,0 +1 @@ +{"data":{"id":"10.48550/arxiv.2207.04779","type":"dois","attributes":{"doi":"10.48550/arxiv.2207.04779","prefix":"10.48550","suffix":"arxiv.2207.04779","identifiers":[{"identifier":"2207.04779","identifierType":"arXiv"}],"alternateIdentifiers":[{"alternateIdentifierType":"arXiv","alternateIdentifier":"2207.04779"}],"creators":[{"name":"Bayer, Jonas","nameType":"Personal","givenName":"Jonas","familyName":"Bayer","affiliation":[],"nameIdentifiers":[]},{"name":"Benzmüller, Christoph","nameType":"Personal","givenName":"Christoph","familyName":"Benzmüller","affiliation":[],"nameIdentifiers":[]},{"name":"Buzzard, Kevin","nameType":"Personal","givenName":"Kevin","familyName":"Buzzard","affiliation":[],"nameIdentifiers":[]},{"name":"David, Marco","nameType":"Personal","givenName":"Marco","familyName":"David","affiliation":[],"nameIdentifiers":[]},{"name":"Lamport, Leslie","nameType":"Personal","givenName":"Leslie","familyName":"Lamport","affiliation":[],"nameIdentifiers":[]},{"name":"Matiyasevich, Yuri","nameType":"Personal","givenName":"Yuri","familyName":"Matiyasevich","affiliation":[],"nameIdentifiers":[]},{"name":"Paulson, Lawrence","nameType":"Personal","givenName":"Lawrence","familyName":"Paulson","affiliation":[],"nameIdentifiers":[]},{"name":"Schleicher, Dierk","nameType":"Personal","givenName":"Dierk","familyName":"Schleicher","affiliation":[],"nameIdentifiers":[]},{"name":"Stock, Benedikt","nameType":"Personal","givenName":"Benedikt","familyName":"Stock","affiliation":[],"nameIdentifiers":[]},{"name":"Zelmanov, Efim","nameType":"Personal","givenName":"Efim","familyName":"Zelmanov","affiliation":[],"nameIdentifiers":[]}],"titles":[{"title":"Mathematical Proof Between Generations"}],"publisher":"arXiv","container":{},"publicationYear":2022,"subjects":[{"lang":"en","subject":"History and Overview (math.HO)","subjectScheme":"arXiv"},{"lang":"en","subject":"Logic in Computer Science (cs.LO)","subjectScheme":"arXiv"},{"subject":"FOS: Mathematics","subjectScheme":"Fields of Science and Technology (FOS)"},{"subject":"FOS: Mathematics","schemeUri":"http://www.oecd.org/science/inno/38235147.pdf","subjectScheme":"Fields of Science and Technology (FOS)"},{"subject":"FOS: Computer and information sciences","subjectScheme":"Fields of Science and Technology (FOS)"},{"subject":"FOS: Computer and information sciences","schemeUri":"http://www.oecd.org/science/inno/38235147.pdf","subjectScheme":"Fields of Science and Technology (FOS)"}],"contributors":[],"dates":[{"date":"2022-07-08T14:42:33Z","dateType":"Submitted","dateInformation":"v1"},{"date":"2022-07-13T00:14:24Z","dateType":"Updated","dateInformation":"v1"},{"date":"2022-07","dateType":"Available","dateInformation":"v1"},{"date":"2022","dateType":"Issued"}],"language":null,"types":{"ris":"GEN","bibtex":"misc","citeproc":"article","schemaOrg":"CreativeWork","resourceType":"Article","resourceTypeGeneral":"Preprint"},"relatedIdentifiers":[],"relatedItems":[],"sizes":[],"formats":[],"version":"1","rightsList":[{"rights":"arXiv.org perpetual, non-exclusive license","rightsUri":"http://arxiv.org/licenses/nonexclusive-distrib/1.0/"}],"descriptions":[{"description":"A proof is one of the most important concepts of mathematics. However, there is a striking difference between how a proof is defined in theory and how it is used in practice. This puts the unique status of mathematics as exact science into peril. Now may be the time to reconcile theory and practice, i.e. precision and intuition, through the advent of computer proof assistants. For the most time this has been a topic for experts in specialized communities. However, mathematical proofs have become increasingly sophisticated, stretching the boundaries of what is humanly comprehensible, so that leading mathematicians have asked for formal verification of their proofs. At the same time, major theorems in mathematics have recently been computer-verified by people from outside of these communities, even by beginning students. This article investigates the gap between the different definitions of a proof and possibilities to build bridges. It is written as a polemic or a collage by different members of the communities in mathematics and computer science at different stages of their careers, challenging well-known preconceptions and exploring new perspectives.","descriptionType":"Abstract"},{"description":"17 pages, 1 figure","descriptionType":"Other"}],"geoLocations":[],"fundingReferences":[],"xml":"PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHJlc291cmNlIHhtbG5zPSJodHRwOi8vZGF0YWNpdGUub3JnL3NjaGVtYS9rZXJuZWwtNCIgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgeHNpOnNjaGVtYUxvY2F0aW9uPSJodHRwOi8vZGF0YWNpdGUub3JnL3NjaGVtYS9rZXJuZWwtNCBodHRwOi8vc2NoZW1hLmRhdGFjaXRlLm9yZy9tZXRhL2tlcm5lbC00LjMvbWV0YWRhdGEueHNkIj4KICA8aWRlbnRpZmllciBpZGVudGlmaWVyVHlwZT0iRE9JIj4xMC40ODU1MC9BUlhJVi4yMjA3LjA0Nzc5PC9pZGVudGlmaWVyPgogIDxhbHRlcm5hdGVJZGVudGlmaWVycz4KICAgIDxhbHRlcm5hdGVJZGVudGlmaWVyIGFsdGVybmF0ZUlkZW50aWZpZXJUeXBlPSJhclhpdiI+MjIwNy4wNDc3OTwvYWx0ZXJuYXRlSWRlbnRpZmllcj4KICA8L2FsdGVybmF0ZUlkZW50aWZpZXJzPgogIDxjcmVhdG9ycz4KICAgIDxjcmVhdG9yPgogICAgICA8Y3JlYXRvck5hbWUgbmFtZVR5cGU9IlBlcnNvbmFsIj5CYXllciwgSm9uYXM8L2NyZWF0b3JOYW1lPgogICAgICA8Z2l2ZW5OYW1lPkpvbmFzPC9naXZlbk5hbWU+CiAgICAgIDxmYW1pbHlOYW1lPkJheWVyPC9mYW1pbHlOYW1lPgogICAgPC9jcmVhdG9yPgogICAgPGNyZWF0b3I+CiAgICAgIDxjcmVhdG9yTmFtZSBuYW1lVHlwZT0iUGVyc29uYWwiPkJlbnptw7xsbGVyLCBDaHJpc3RvcGg8L2NyZWF0b3JOYW1lPgogICAgICA8Z2l2ZW5OYW1lPkNocmlzdG9waDwvZ2l2ZW5OYW1lPgogICAgICA8ZmFtaWx5TmFtZT5CZW56bcO8bGxlcjwvZmFtaWx5TmFtZT4KICAgIDwvY3JlYXRvcj4KICAgIDxjcmVhdG9yPgogICAgICA8Y3JlYXRvck5hbWUgbmFtZVR5cGU9IlBlcnNvbmFsIj5CdXp6YXJkLCBLZXZpbjwvY3JlYXRvck5hbWU+CiAgICAgIDxnaXZlbk5hbWU+S2V2aW48L2dpdmVuTmFtZT4KICAgICAgPGZhbWlseU5hbWU+QnV6emFyZDwvZmFtaWx5TmFtZT4KICAgIDwvY3JlYXRvcj4KICAgIDxjcmVhdG9yPgogICAgICA8Y3JlYXRvck5hbWUgbmFtZVR5cGU9IlBlcnNvbmFsIj5EYXZpZCwgTWFyY288L2NyZWF0b3JOYW1lPgogICAgICA8Z2l2ZW5OYW1lPk1hcmNvPC9naXZlbk5hbWU+CiAgICAgIDxmYW1pbHlOYW1lPkRhdmlkPC9mYW1pbHlOYW1lPgogICAgPC9jcmVhdG9yPgogICAgPGNyZWF0b3I+CiAgICAgIDxjcmVhdG9yTmFtZSBuYW1lVHlwZT0iUGVyc29uYWwiPkxhbXBvcnQsIExlc2xpZTwvY3JlYXRvck5hbWU+CiAgICAgIDxnaXZlbk5hbWU+TGVzbGllPC9naXZlbk5hbWU+CiAgICAgIDxmYW1pbHlOYW1lPkxhbXBvcnQ8L2ZhbWlseU5hbWU+CiAgICA8L2NyZWF0b3I+CiAgICA8Y3JlYXRvcj4KICAgICAgPGNyZWF0b3JOYW1lIG5hbWVUeXBlPSJQZXJzb25hbCI+TWF0aXlhc2V2aWNoLCBZdXJpPC9jcmVhdG9yTmFtZT4KICAgICAgPGdpdmVuTmFtZT5ZdXJpPC9naXZlbk5hbWU+CiAgICAgIDxmYW1pbHlOYW1lPk1hdGl5YXNldmljaDwvZmFtaWx5TmFtZT4KICAgIDwvY3JlYXRvcj4KICAgIDxjcmVhdG9yPgogICAgICA8Y3JlYXRvck5hbWUgbmFtZVR5cGU9IlBlcnNvbmFsIj5QYXVsc29uLCBMYXdyZW5jZTwvY3JlYXRvck5hbWU+CiAgICAgIDxnaXZlbk5hbWU+TGF3cmVuY2U8L2dpdmVuTmFtZT4KICAgICAgPGZhbWlseU5hbWU+UGF1bHNvbjwvZmFtaWx5TmFtZT4KICAgIDwvY3JlYXRvcj4KICAgIDxjcmVhdG9yPgogICAgICA8Y3JlYXRvck5hbWUgbmFtZVR5cGU9IlBlcnNvbmFsIj5TY2hsZWljaGVyLCBEaWVyazwvY3JlYXRvck5hbWU+CiAgICAgIDxnaXZlbk5hbWU+RGllcms8L2dpdmVuTmFtZT4KICAgICAgPGZhbWlseU5hbWU+U2NobGVpY2hlcjwvZmFtaWx5TmFtZT4KICAgIDwvY3JlYXRvcj4KICAgIDxjcmVhdG9yPgogICAgICA8Y3JlYXRvck5hbWUgbmFtZVR5cGU9IlBlcnNvbmFsIj5TdG9jaywgQmVuZWRpa3Q8L2NyZWF0b3JOYW1lPgogICAgICA8Z2l2ZW5OYW1lPkJlbmVkaWt0PC9naXZlbk5hbWU+CiAgICAgIDxmYW1pbHlOYW1lPlN0b2NrPC9mYW1pbHlOYW1lPgogICAgPC9jcmVhdG9yPgogICAgPGNyZWF0b3I+CiAgICAgIDxjcmVhdG9yTmFtZSBuYW1lVHlwZT0iUGVyc29uYWwiPlplbG1hbm92LCBFZmltPC9jcmVhdG9yTmFtZT4KICAgICAgPGdpdmVuTmFtZT5FZmltPC9naXZlbk5hbWU+CiAgICAgIDxmYW1pbHlOYW1lPlplbG1hbm92PC9mYW1pbHlOYW1lPgogICAgPC9jcmVhdG9yPgogIDwvY3JlYXRvcnM+CiAgPHRpdGxlcz4KICAgIDx0aXRsZT5NYXRoZW1hdGljYWwgUHJvb2YgQmV0d2VlbiBHZW5lcmF0aW9uczwvdGl0bGU+CiAgPC90aXRsZXM+CiAgPHB1Ymxpc2hlcj5hclhpdjwvcHVibGlzaGVyPgogIDxwdWJsaWNhdGlvblllYXI+MjAyMjwvcHVibGljYXRpb25ZZWFyPgogIDxzdWJqZWN0cz4KICAgIDxzdWJqZWN0IHhtbDpsYW5nPSJlbiIgc3ViamVjdFNjaGVtZT0iYXJYaXYiPkhpc3RvcnkgYW5kIE92ZXJ2aWV3IChtYXRoLkhPKTwvc3ViamVjdD4KICAgIDxzdWJqZWN0IHhtbDpsYW5nPSJlbiIgc3ViamVjdFNjaGVtZT0iYXJYaXYiPkxvZ2ljIGluIENvbXB1dGVyIFNjaWVuY2UgKGNzLkxPKTwvc3ViamVjdD4KICAgIDxzdWJqZWN0IHN1YmplY3RTY2hlbWU9IkZpZWxkcyBvZiBTY2llbmNlIGFuZCBUZWNobm9sb2d5IChGT1MpIj5GT1M6IE1hdGhlbWF0aWNzPC9zdWJqZWN0PgogICAgPHN1YmplY3Qgc3ViamVjdFNjaGVtZT0iRmllbGRzIG9mIFNjaWVuY2UgYW5kIFRlY2hub2xvZ3kgKEZPUykiPkZPUzogQ29tcHV0ZXIgYW5kIGluZm9ybWF0aW9uIHNjaWVuY2VzPC9zdWJqZWN0PgogIDwvc3ViamVjdHM+CiAgPGRhdGVzPgogICAgPGRhdGUgZGF0ZVR5cGU9IlN1Ym1pdHRlZCIgZGF0ZUluZm9ybWF0aW9uPSJ2MSI+MjAyMi0wNy0wOFQxNDo0MjozM1o8L2RhdGU+CiAgICA8ZGF0ZSBkYXRlVHlwZT0iVXBkYXRlZCIgZGF0ZUluZm9ybWF0aW9uPSJ2MSI+MjAyMi0wNy0xM1QwMDoxNDoyNFo8L2RhdGU+CiAgICA8ZGF0ZSBkYXRlVHlwZT0iQXZhaWxhYmxlIiBkYXRlSW5mb3JtYXRpb249InYxIj4yMDIyLTA3PC9kYXRlPgogIDwvZGF0ZXM+CiAgPHJlc291cmNlVHlwZSByZXNvdXJjZVR5cGVHZW5lcmFsPSJQcmVwcmludCI+QXJ0aWNsZTwvcmVzb3VyY2VUeXBlPgogIDx2ZXJzaW9uPjE8L3ZlcnNpb24+CiAgPHJpZ2h0c0xpc3Q+CiAgICA8cmlnaHRzIHJpZ2h0c1VSST0iaHR0cDovL2FyeGl2Lm9yZy9saWNlbnNlcy9ub25leGNsdXNpdmUtZGlzdHJpYi8xLjAvIj5hclhpdi5vcmcgcGVycGV0dWFsLCBub24tZXhjbHVzaXZlIGxpY2Vuc2U8L3JpZ2h0cz4KICA8L3JpZ2h0c0xpc3Q+CiAgPGRlc2NyaXB0aW9ucz4KICAgIDxkZXNjcmlwdGlvbiBkZXNjcmlwdGlvblR5cGU9IkFic3RyYWN0Ij5BIHByb29mIGlzIG9uZSBvZiB0aGUgbW9zdCBpbXBvcnRhbnQgY29uY2VwdHMgb2YgbWF0aGVtYXRpY3MuIEhvd2V2ZXIsIHRoZXJlIGlzIGEgc3RyaWtpbmcgZGlmZmVyZW5jZSBiZXR3ZWVuIGhvdyBhIHByb29mIGlzIGRlZmluZWQgaW4gdGhlb3J5IGFuZCBob3cgaXQgaXMgdXNlZCBpbiBwcmFjdGljZS4gVGhpcyBwdXRzIHRoZSB1bmlxdWUgc3RhdHVzIG9mIG1hdGhlbWF0aWNzIGFzIGV4YWN0IHNjaWVuY2UgaW50byBwZXJpbC4gTm93IG1heSBiZSB0aGUgdGltZSB0byByZWNvbmNpbGUgdGhlb3J5IGFuZCBwcmFjdGljZSwgaS5lLiBwcmVjaXNpb24gYW5kIGludHVpdGlvbiwgdGhyb3VnaCB0aGUgYWR2ZW50IG9mIGNvbXB1dGVyIHByb29mIGFzc2lzdGFudHMuIEZvciB0aGUgbW9zdCB0aW1lIHRoaXMgaGFzIGJlZW4gYSB0b3BpYyBmb3IgZXhwZXJ0cyBpbiBzcGVjaWFsaXplZCBjb21tdW5pdGllcy4gSG93ZXZlciwgbWF0aGVtYXRpY2FsIHByb29mcyBoYXZlIGJlY29tZSBpbmNyZWFzaW5nbHkgc29waGlzdGljYXRlZCwgc3RyZXRjaGluZyB0aGUgYm91bmRhcmllcyBvZiB3aGF0IGlzIGh1bWFubHkgY29tcHJlaGVuc2libGUsIHNvIHRoYXQgbGVhZGluZyBtYXRoZW1hdGljaWFucyBoYXZlIGFza2VkIGZvciBmb3JtYWwgdmVyaWZpY2F0aW9uIG9mIHRoZWlyIHByb29mcy4gQXQgdGhlIHNhbWUgdGltZSwgbWFqb3IgdGhlb3JlbXMgaW4gbWF0aGVtYXRpY3MgaGF2ZSByZWNlbnRseSBiZWVuIGNvbXB1dGVyLXZlcmlmaWVkIGJ5IHBlb3BsZSBmcm9tIG91dHNpZGUgb2YgdGhlc2UgY29tbXVuaXRpZXMsIGV2ZW4gYnkgYmVnaW5uaW5nIHN0dWRlbnRzLiBUaGlzIGFydGljbGUgaW52ZXN0aWdhdGVzIHRoZSBnYXAgYmV0d2VlbiB0aGUgZGlmZmVyZW50IGRlZmluaXRpb25zIG9mIGEgcHJvb2YgYW5kIHBvc3NpYmlsaXRpZXMgdG8gYnVpbGQgYnJpZGdlcy4gSXQgaXMgd3JpdHRlbiBhcyBhIHBvbGVtaWMgb3IgYSBjb2xsYWdlIGJ5IGRpZmZlcmVudCBtZW1iZXJzIG9mIHRoZSBjb21tdW5pdGllcyBpbiBtYXRoZW1hdGljcyBhbmQgY29tcHV0ZXIgc2NpZW5jZSBhdCBkaWZmZXJlbnQgc3RhZ2VzIG9mIHRoZWlyIGNhcmVlcnMsIGNoYWxsZW5naW5nIHdlbGwta25vd24gcHJlY29uY2VwdGlvbnMgYW5kIGV4cGxvcmluZyBuZXcgcGVyc3BlY3RpdmVzLjwvZGVzY3JpcHRpb24+CiAgICA8ZGVzY3JpcHRpb24gZGVzY3JpcHRpb25UeXBlPSJPdGhlciI+MTcgcGFnZXMsIDEgZmlndXJlPC9kZXNjcmlwdGlvbj4KICA8L2Rlc2NyaXB0aW9ucz4KPC9yZXNvdXJjZT4=","url":"https://arxiv.org/abs/2207.04779","contentUrl":null,"metadataVersion":1,"schemaVersion":"http://datacite.org/schema/kernel-4","source":"mds","isActive":true,"state":"findable","reason":null,"viewCount":0,"viewsOverTime":[],"downloadCount":0,"downloadsOverTime":[],"referenceCount":0,"citationCount":0,"citationsOverTime":[],"partCount":0,"partOfCount":0,"versionCount":0,"versionOfCount":0,"created":"2022-07-12T01:41:56.000Z","registered":"2022-07-12T01:41:57.000Z","published":"2022","updated":"2022-07-13T01:24:20.000Z"},"relationships":{"client":{"data":{"id":"arxiv.content","type":"clients"}},"provider":{"data":{"id":"arxiv","type":"providers"}},"media":{"data":{"id":"10.48550/arxiv.2207.04779","type":"media"}},"references":{"data":[]},"citations":{"data":[]},"parts":{"data":[]},"partOf":{"data":[]},"versions":{"data":[]},"versionOf":{"data":[]}}}} diff --git a/dspace-services/pom.xml b/dspace-services/pom.xml index 778b5412c1..f3112b049b 100644 --- a/dspace-services/pom.xml +++ b/dspace-services/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - 7.4 + 7.6-SNAPSHOT diff --git a/dspace-services/src/main/java/org/dspace/services/RequestService.java b/dspace-services/src/main/java/org/dspace/services/RequestService.java index 0c0c5fad45..d28a04ecf6 100644 --- a/dspace-services/src/main/java/org/dspace/services/RequestService.java +++ b/dspace-services/src/main/java/org/dspace/services/RequestService.java @@ -111,7 +111,7 @@ public interface RequestService { /** * Set the ID of the current authenticated user * - * @return the id of the user associated with the current thread OR null if there is no user + * @param epersonId the id of the user associated with the current thread OR null if there is no user */ public void setCurrentUserId(UUID epersonId); diff --git a/dspace-sword/pom.xml b/dspace-sword/pom.xml index f5ce69ac85..fd68337bbc 100644 --- a/dspace-sword/pom.xml +++ b/dspace-sword/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.4 + 7.6-SNAPSHOT .. @@ -24,25 +24,6 @@ ${basedir}/.. - - - oracle-support - - - db.name - oracle - - - - - com.oracle - ojdbc6 - - - - - - diff --git a/dspace-swordv2/pom.xml b/dspace-swordv2/pom.xml index 5f99fb78fb..9badeb2fe8 100644 --- a/dspace-swordv2/pom.xml +++ b/dspace-swordv2/pom.xml @@ -13,7 +13,7 @@ org.dspace dspace-parent - 7.4 + 7.6-SNAPSHOT .. @@ -22,38 +22,6 @@ ${basedir}/.. - - - oracle-support - - - db.name - oracle - - - - - com.oracle - ojdbc6 - - - - - postgres-support - - - !db.name - - - - - org.postgresql - postgresql - - - - - javax.servlet diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 11c53cb6a7..59fee8b411 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -75,23 +75,15 @@ solr.multicorePrefix = # solr.client.timeToLive = 600 ##### Database settings ##### -# DSpace only supports two database types: PostgreSQL or Oracle -# PostgreSQL is highly recommended. -# Oracle support is DEPRECATED. See https://github.com/DSpace/DSpace/issues/8214 +# DSpace ONLY supports PostgreSQL at this time. # URL for connecting to database -# * Postgres template: jdbc:postgresql://localhost:5432/dspace -# * Oracle template (DEPRECATED): jdbc:oracle:thin:@//localhost:1521/xe db.url = jdbc:postgresql://localhost:5432/dspace -# JDBC Driver -# * For Postgres: org.postgresql.Driver -# * For Oracle (DEPRECATED): oracle.jdbc.OracleDriver +# JDBC Driver for PostgreSQL db.driver = org.postgresql.Driver -# Database Dialect (for Hibernate) -# * For Postgres: org.hibernate.dialect.PostgreSQL94Dialect -# * For Oracle (DEPRECATED): org.hibernate.dialect.Oracle10gDialect +# PostgreSQL Database Dialect (for Hibernate) db.dialect = org.hibernate.dialect.PostgreSQL94Dialect # Database username and password @@ -99,9 +91,7 @@ db.username = dspace db.password = dspace # Database Schema name -# * For Postgres, this is often "public" (default schema) -# * For Oracle (DEPRECATED), schema is equivalent to the username of your database account, -# so this may be set to ${db.username} in most scenarios. +# For PostgreSQL, this is often "public" (default schema) db.schema = public ## Database Connection pool parameters @@ -339,7 +329,7 @@ handle.dir = ${dspace.dir}/handle-server # Whether to enable the DSpace listhandles resolver that lists all available # handles for this DSpace installation. # Defaults to "false" which means is possible to obtain the list of handles -# of this DSpace installation, whenever the `handle.remote-resolver.enabled = true`. +# of this DSpace installation, whenever the `handle.remote-resolver.enabled = true`. # handle.hide.listhandles = false ##### Authorization system configuration - Delegate ADMIN ##### @@ -493,9 +483,9 @@ filter.org.dspace.app.mediafilter.TikaTextExtractionFilter.inputFormats = OpenDo filter.org.dspace.app.mediafilter.TikaTextExtractionFilter.inputFormats = OpenDocument Text filter.org.dspace.app.mediafilter.TikaTextExtractionFilter.inputFormats = RTF filter.org.dspace.app.mediafilter.TikaTextExtractionFilter.inputFormats = Text -filter.org.dspace.app.mediafilter.JPEGFilter.inputFormats = BMP, GIF, JPEG, image/png -filter.org.dspace.app.mediafilter.BrandedPreviewJPEGFilter.inputFormats = BMP, GIF, JPEG, image/png -filter.org.dspace.app.mediafilter.ImageMagickImageThumbnailFilter.inputFormats = BMP, GIF, image/png, JPG, TIFF, JPEG, JPEG 2000 +filter.org.dspace.app.mediafilter.JPEGFilter.inputFormats = BMP, GIF, JPEG, PNG +filter.org.dspace.app.mediafilter.BrandedPreviewJPEGFilter.inputFormats = BMP, GIF, JPEG, PNG +filter.org.dspace.app.mediafilter.ImageMagickImageThumbnailFilter.inputFormats = BMP, GIF, PNG, JPG, TIFF, JPEG, JPEG 2000 filter.org.dspace.app.mediafilter.ImageMagickPdfThumbnailFilter.inputFormats = Adobe PDF filter.org.dspace.app.mediafilter.PDFBoxThumbnail.inputFormats = Adobe PDF @@ -548,6 +538,12 @@ filter.org.dspace.app.mediafilter.PDFBoxThumbnail.inputFormats = Adobe PDF # org.dspace.app.mediafilter.ImageMagickThumbnailFilter.cmyk_profile = /usr/share/ghostscript/9.18/iccprofiles/default_cmyk.icc # org.dspace.app.mediafilter.ImageMagickThumbnailFilter.srgb_profile = /usr/share/ghostscript/9.18/iccprofiles/default_rgb.icc +# Optional: override ImageMagick's default density of 72 when creating PDF thum- +# bnails. Greatly increases quality of resulting thumbnails, at the expense of +# slightly longer execution times and higher memory usage. Any integer over 72 +# will help, but recommend 144 for a "2x" supersample. +# org.dspace.app.mediafilter.ImageMagickThumbnailFilter.density = 144 + #### Crosswalk and Packager Plugin Settings #### # Crosswalks are used to translate external metadata formats into DSpace's internal format (DIM) # Packagers are used to ingest/export 'packages' (both content files and metadata) @@ -838,7 +834,7 @@ plugin.single.org.dspace.embargo.EmbargoSetter = org.dspace.embargo.DefaultEmbar plugin.single.org.dspace.embargo.EmbargoLifter = org.dspace.embargo.DefaultEmbargoLifter # 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. @@ -875,7 +871,7 @@ org.dspace.app.itemexport.life.span.hours = 48 # The maximum size in Megabytes the export should be. This is enforced before the # compression. Each bitstream's size in each item being exported is added up, if their -# cummulative sizes are more than this entry the export is not kicked off +# cumulative sizes are more than this entry the export is not kicked off org.dspace.app.itemexport.max.size = 200 ### Batch Item import settings ### @@ -888,10 +884,6 @@ org.dspace.app.batchitemimport.work.dir = ${dspace.dir}/imports # default = false, (disabled) #org.dspace.content.Collection.findAuthorizedPerformanceOptimize = true -# For backwards compatibility, the subscription emails by default include any modified items -# uncomment the following entry for only new items to be emailed -# eperson.subscription.onlynew = true - # Identifier providers. # Following are configuration values for the EZID DOI provider, with appropriate @@ -1005,11 +997,12 @@ cc.license.classfilter = recombo, mark # http://api.creativecommons.org/rest/1.5/support/jurisdictions # Commented out means the license is unported. # (e.g. nz = New Zealand, uk = England and Wales, jp = Japan) +# or set value none for user-selected jurisdiction cc.license.jurisdiction = us # Locale for CC dialogs # A locale in the form language or language-country. -# If no default locale is defined the CC default locale will be used +# If no default locale is defined the current supported locale will be used cc.license.locale = en @@ -1222,8 +1215,6 @@ plugin.named.org.dspace.sort.OrderFormatDelegate= \ # starting with the value of the link clicked on. # # The default below defines the authors to link to other publications by that author -# -# TODO: UNSUPPORTED in DSpace 7.0 webui.browse.link.1 = author:dc.contributor.* #### Display browse frequencies @@ -1533,10 +1524,10 @@ log.report.dir = ${dspace.dir}/log # The below example will run this task daily, every 5 minutes # google.analytics.cron = 0 0/5 * * * ? -# Defines a Measurement Protocol API Secret to be used to track interactions which occur outside of the user's browser. +# Defines a Measurement Protocol API Secret to be used to track interactions which occur outside of the user's browser. # For example , this is required to track downloads of bitstreams. This setting is only used by Google Analytics 4. # For more details see https://developers.google.com/analytics/devguides/collection/protocol/ga4 -# google.analytics.api-secret = +# google.analytics.api-secret = #################################################################### #---------------------------------------------------------------# @@ -1550,6 +1541,10 @@ log.report.dir = ${dspace.dir}/log request.item.type = all # Should all Request Copy emails go to the helpdesk instead of the item submitter? request.item.helpdesk.override = false +# Should a rejection of a copy request send an email back to the requester? +# Defaults to "true", which means a rejection email is sent back. +# Setting it to "false" results in a silent rejection. +request.item.reject.email = true #------------------------------------------------------------------# #------------------SUBMISSION CONFIGURATION------------------------# @@ -1652,6 +1647,7 @@ include = ${module_dir}/discovery.cfg include = ${module_dir}/doi-curation.cfg include = ${module_dir}/google-analytics.cfg include = ${module_dir}/healthcheck.cfg +include = ${module_dir}/identifiers.cfg include = ${module_dir}/irus-statistics.cfg include = ${module_dir}/oai.cfg include = ${module_dir}/openaire-client.cfg diff --git a/dspace/config/emails/healthcheck b/dspace/config/emails/healthcheck index bee4d4dec2..bd2ae0be52 100644 --- a/dspace/config/emails/healthcheck +++ b/dspace/config/emails/healthcheck @@ -3,7 +3,7 @@ ## Parameters: {0} is the output of healthcheck command ## See org.dspace.core.Email for information on the format of this file. ## -#set($subject = 'Repository healthcheck') +#set($subject = "${config.get('dspace.name')}: Repository healthcheck") The healthcheck finished with the following output: ${params[0]} diff --git a/dspace/config/emails/subscription b/dspace/config/emails/subscription deleted file mode 100644 index 5141192b57..0000000000 --- a/dspace/config/emails/subscription +++ /dev/null @@ -1,12 +0,0 @@ -## E-mail sent to DSpace users when new items appear in collections they are -## subscribed to -## -## Parameters: {0} is the details of the new collections and items -## See org.dspace.core.Email for information on the format of this file. -## -#set($subject = "${config.get('dspace.name')} Subscription") -New items are available in the collections you have subscribed to: - -${params[0]} - -The ${config.get('dspace.name')} Team diff --git a/dspace/config/emails/subscriptions_content b/dspace/config/emails/subscriptions_content new file mode 100644 index 0000000000..a330c59537 --- /dev/null +++ b/dspace/config/emails/subscriptions_content @@ -0,0 +1,16 @@ +## E-mail sent to designated address about updates on subscribed items +## +## Parameters: {0} Collections updates +## {1} Communities updates +#set($subject = "${config.get('dspace.name')} Subscription") + +This email is sent from ${config.get('dspace.name')} based on the chosen subscription preferences. + +Communities +----------- +List of changed items : ${params[0]} + +Collections +----------- +List of changed items : ${params[1]} + diff --git a/dspace/config/hibernate.cfg.xml b/dspace/config/hibernate.cfg.xml index 085ed0bd6e..3cc0623c34 100644 --- a/dspace/config/hibernate.cfg.xml +++ b/dspace/config/hibernate.cfg.xml @@ -54,6 +54,7 @@ + @@ -66,7 +67,7 @@ - + @@ -93,5 +94,7 @@ + + diff --git a/dspace/config/item-submission.xml b/dspace/config/item-submission.xml index 299a5fdfce..2ab26dcf57 100644 --- a/dspace/config/item-submission.xml +++ b/dspace/config/item-submission.xml @@ -209,6 +209,13 @@ org.dspace.app.rest.submit.step.SherpaPolicyStep sherpaPolicy + + + + submit.progressbar.identifiers + org.dspace.app.rest.submit.step.ShowIdentifiersStep + identifiers + @@ -233,7 +240,9 @@ - + + diff --git a/dspace/config/launcher.xml b/dspace/config/launcher.xml index be8564a0e6..13d8e0bef8 100644 --- a/dspace/config/launcher.xml +++ b/dspace/config/launcher.xml @@ -289,13 +289,6 @@ org.dspace.administer.StructBuilder - - sub-daily - Send daily subscription notices - - org.dspace.eperson.SubscribeCLITool - - test-email Test the DSpace email server settings are OK diff --git a/dspace/config/local.cfg.EXAMPLE b/dspace/config/local.cfg.EXAMPLE index cf13a47d76..7176ed275a 100644 --- a/dspace/config/local.cfg.EXAMPLE +++ b/dspace/config/local.cfg.EXAMPLE @@ -71,23 +71,15 @@ dspace.name = DSpace at My University ########################## # DATABASE CONFIGURATION # ########################## -# DSpace only supports two database types: PostgreSQL or Oracle -# PostgreSQL is highly recommended. -# Oracle support is DEPRECATED. See https://github.com/DSpace/DSpace/issues/8214 +# DSpace ONLY supports PostgreSQL at this time. # URL for connecting to database -# * Postgres template: jdbc:postgresql://localhost:5432/dspace -# * Oracle template (DEPRECATED): jdbc:oracle:thin:@//localhost:1521/xe db.url = jdbc:postgresql://localhost:5432/dspace -# JDBC Driver -# * For Postgres: org.postgresql.Driver -# * For Oracle (DEPRECATED): oracle.jdbc.OracleDriver +# JDBC Driver for PostgreSQL db.driver = org.postgresql.Driver -# Database Dialect (for Hibernate) -# * For Postgres: org.hibernate.dialect.PostgreSQL94Dialect -# * For Oracle (DEPRECATED): org.hibernate.dialect.Oracle10gDialect +# PostgreSQL Database Dialect (for Hibernate) db.dialect = org.hibernate.dialect.PostgreSQL94Dialect # Database username and password @@ -95,9 +87,7 @@ db.username = dspace db.password = dspace # Database Schema name -# * For Postgres, this is often "public" (default schema) -# * For Oracle (DEPRECATED), schema is equivalent to the username of your database account, -# so this may be set to ${db.username} in most scenarios. +# For PostgreSQL, this is often "public" (default schema) db.schema = public ## Connection pool parameters diff --git a/dspace/config/modules/authentication-password.cfg b/dspace/config/modules/authentication-password.cfg index 078da7f8d1..9029e76e26 100644 --- a/dspace/config/modules/authentication-password.cfg +++ b/dspace/config/modules/authentication-password.cfg @@ -6,6 +6,9 @@ #---------------------------------------------------------------# # +# self-registration can be disabled completely by setting the user.registration property to false +# user.registration = false + # Only emails ending in the following domains are allowed to self-register # Example - example.com domain : @example.com # Example - MIT domain and all .ac.uk domains: @mit.edu, .ac.uk diff --git a/dspace/config/modules/bulkedit.cfg b/dspace/config/modules/bulkedit.cfg index 9e6ec59372..6174af53a0 100644 --- a/dspace/config/modules/bulkedit.cfg +++ b/dspace/config/modules/bulkedit.cfg @@ -32,3 +32,11 @@ # By default, only 'dspace.agreements.end-user' can be deleted in bulk, as doing so allows # an administrator to force all users to re-review the End User Agreement on their next login. bulkedit.allow-bulk-deletion = dspace.agreements.end-user + +### metadata import script ### +# Set the number after which the changes should be committed while running the script +# After too much consecutive records everything starts to slow down because too many things are being loaded into memory +# If we commit these to the database these are cleared out of our memory and we don't lose as much performance +# By default this is set to 100 +bulkedit.change.commit.count = 100 + diff --git a/dspace/config/modules/external-providers.cfg b/dspace/config/modules/external-providers.cfg index 1ae7d5a12b..b7c0e120db 100644 --- a/dspace/config/modules/external-providers.cfg +++ b/dspace/config/modules/external-providers.cfg @@ -85,4 +85,10 @@ scopus.search-api.viewMode = wos.apiKey = wos.url = https://wos-api.clarivate.com/api/wos/id/ wos.url.search = https://wos-api.clarivate.com/api/wos/?databaseId=WOS&lang=en&usrQuery= -################################################################## \ No newline at end of file +################################################################# +#------------------------- DataCite ----------------------------# +#---------------------------------------------------------------# + +datacite.url = https://api.datacite.org/dois/ +datacite.timeout = 180000 +################################################################# diff --git a/dspace/config/modules/identifiers.cfg b/dspace/config/modules/identifiers.cfg new file mode 100644 index 0000000000..63a9cda30f --- /dev/null +++ b/dspace/config/modules/identifiers.cfg @@ -0,0 +1,51 @@ +#----------------------------------------------------------------------# +#---------------------IDENTIFIER CONFIGURATIONS------------------------# +#----------------------------------------------------------------------# +# These configs are used for additional identifier configuration such # +# as the Show Identifiers step which can "pre-mint" DOIs and Handles # +#----------------------------------------------------------------------# + +# Should configured identifiers (eg handle and DOI) be minted for (future) registration at workspace item creation? +# A handle created at this stage will act just like a regular handle created at archive time. +# A DOI created at this stage will be in a 'PENDING' status while in workspace and workflow. +# At the time of item install, the DOI filter (if any) will be applied and if the item matches the filter, the DOI +# status will be updated to TO_BE_REGISTERED. An administrator can also manually progress the DOI status, overriding +# any filters, in the item status page. +# This option doesn't require the Show Identifiers submission step to be visible. +# Default: false +#identifiers.submission.register = true + +# This configuration property can be set to a filter name to determine if a PENDING DOI for an item +# should be queued for registration. If the filter doesn't match, the DOI will stay in PENDING or MINTED status +# so that the identifier itself persists in case it is considered for registration in the future. +# See doi-filter and other example filters in item-filters.xml. +# Default (always_true_filter) +#identifiers.submission.filter.install = doi-filter + +# This optional configuration property can be set to a filter name, in case there are some initial rules to apply +# when first deciding whether a DOI should be be created for a new workspace item with a PENDING status. +# This filter is only applied if identifiers.submission.register is true. +# This filter is updated as submission data is saved. +# Default: (always_true_filter) +#identifiers.submission.filter.workspace = always_true_filter + +# If true, the workspace filter will be applied as submission data is saved. If the filter no longer +# matches the item, the DOI will be shifted into a MINTED status and not displayed in the submission section. +# If false, then once a DOI has been created with PENDING status it will remain that way until final item install +# Default: true +#identifiers.submission.strip_pending_during_submission = true + +# This configuration property can be set to a filter name to determine if an item processed by RegisterDOI curation +# task should be eligible for a DOI +#identifiers.submission.filter.curation = always_true_filter + +# Show Register DOI button in item status page? +# Default: false +# This configuration property is exposed over rest. For dspace-angular to work, +# this property must always have a true or false value. Do not comment it out! +identifiers.item-status.register-doi = false + +# Which identifier types to show in submission step? +# Default: handle, doi (currently the only supported identifier 'types') +#identifiers.submission.display = handle +#identifiers.submission.display = doi diff --git a/dspace/config/modules/rest.cfg b/dspace/config/modules/rest.cfg index e5959cb7bd..6421258c57 100644 --- a/dspace/config/modules/rest.cfg +++ b/dspace/config/modules/rest.cfg @@ -45,6 +45,9 @@ rest.properties.exposed = submit.type-bind.field rest.properties.exposed = google.recaptcha.key.site rest.properties.exposed = google.recaptcha.version rest.properties.exposed = google.recaptcha.mode +rest.properties.exposed = cc.license.jurisdiction +rest.properties.exposed = identifiers.item-status.register-doi +rest.properties.exposed = authentication-password.domain.valid #---------------------------------------------------------------# # These configs are used by the deprecated REST (v4-6) module # diff --git a/dspace/config/modules/workflow.cfg b/dspace/config/modules/workflow.cfg index 8d11df03d5..c77050d719 100644 --- a/dspace/config/modules/workflow.cfg +++ b/dspace/config/modules/workflow.cfg @@ -16,4 +16,9 @@ workflow.reviewer.file-edit=false # Notify reviewers about tasks returned to the pool -#workflow.notify.returned.tasks = true \ No newline at end of file +#workflow.notify.returned.tasks = true + +# Reviewer group for the select reviewer workflow (can be UUID or group name) +# This determines the group from which reviewers can be chosen +# If this is not set, the review manager can choose reviewers from all e-people instead of this selected group +action.selectrevieweraction.group = Reviewers diff --git a/dspace/config/registries/bitstream-formats.xml b/dspace/config/registries/bitstream-formats.xml index 586931b376..3515773fd7 100644 --- a/dspace/config/registries/bitstream-formats.xml +++ b/dspace/config/registries/bitstream-formats.xml @@ -115,6 +115,15 @@ csv + + text/vtt + WebVTT + Web Video Text Tracks Format + 1 + false + vtt + + application/msword Microsoft Word @@ -187,6 +196,7 @@ false jpeg jpg + jfif @@ -200,7 +210,7 @@ image/png - image/png + PNG Portable Network Graphics 1 false @@ -217,6 +227,15 @@ tif + + image/jp2 + JPEG2000 + JPEG 2000 Image File Format + 1 + false + jp2 + + audio/x-aiff AIFF @@ -790,4 +809,22 @@ mp3 + + image/webp + WebP + WebP is a modern image format that provides superior lossless and lossy compression for images on the web. + 1 + false + webp + + + + image/avif + AVIF + AV1 Image File Format (AVIF) is an open, royalty-free image file format specification for storing images or image sequences compressed with AV1 in the HEIF container format. + 1 + false + avif + + diff --git a/dspace/config/registries/workflow-types.xml b/dspace/config/registries/workflow-types.xml index 3f26849bf2..a6417e3894 100644 --- a/dspace/config/registries/workflow-types.xml +++ b/dspace/config/registries/workflow-types.xml @@ -17,7 +17,13 @@ workflow score - Metadata field used for the score review + Metadata field used for the score review rating + + + + workflow + review + Metadata field used for the score review description diff --git a/dspace/config/spring/api/arxiv-integration.xml b/dspace/config/spring/api/arxiv-integration.xml index e963e73a20..59594b08fa 100644 --- a/dspace/config/spring/api/arxiv-integration.xml +++ b/dspace/config/spring/api/arxiv-integration.xml @@ -56,10 +56,12 @@
- + + + diff --git a/dspace/config/spring/api/core-dao-services.xml b/dspace/config/spring/api/core-dao-services.xml index ae4b5e6e3b..bc62a71c03 100644 --- a/dspace/config/spring/api/core-dao-services.xml +++ b/dspace/config/spring/api/core-dao-services.xml @@ -34,11 +34,14 @@ + + + @@ -64,5 +67,7 @@ + + diff --git a/dspace/config/spring/api/core-factory-services.xml b/dspace/config/spring/api/core-factory-services.xml index 20e5297b6c..cef906adc8 100644 --- a/dspace/config/spring/api/core-factory-services.xml +++ b/dspace/config/spring/api/core-factory-services.xml @@ -55,4 +55,6 @@ + + diff --git a/dspace/config/spring/api/core-services.xml b/dspace/config/spring/api/core-services.xml index a124ec830f..212237585e 100644 --- a/dspace/config/spring/api/core-services.xml +++ b/dspace/config/spring/api/core-services.xml @@ -48,7 +48,6 @@ - @@ -60,6 +59,8 @@ + + @@ -101,7 +102,6 @@ - @@ -148,5 +148,7 @@ + + diff --git a/dspace/config/spring/api/datacite-integration.xml b/dspace/config/spring/api/datacite-integration.xml new file mode 100644 index 0000000000..236ec0a3bd --- /dev/null +++ b/dspace/config/spring/api/datacite-integration.xml @@ -0,0 +1,62 @@ + + + + + + + + Defines which metadatum is mapped on which metadatum. Note that while the key must be unique it + only matters here for postprocessing of the value. The mapped MetadatumContributor has full control over + what metadatafield is generated. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dspace/config/spring/api/discovery.xml b/dspace/config/spring/api/discovery.xml index 57f4c07aee..611e77b27b 100644 --- a/dspace/config/spring/api/discovery.xml +++ b/dspace/config/spring/api/discovery.xml @@ -31,6 +31,7 @@ + @@ -40,6 +41,9 @@ + + + @@ -61,10 +65,14 @@ + + + + @@ -247,6 +255,18 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + search.resourcetype:WorkspaceItem AND supervised:true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + search.resourcetype:WorkspaceItem OR search.resourcetype:XmlWorkflowItem + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2792,6 +2960,18 @@ + + + + + + + placeholder.placeholder.placeholder + + + + @@ -2866,4 +3046,21 @@ + + + + + + + + + + + + + + + + + diff --git a/dspace/config/spring/api/external-services.xml b/dspace/config/spring/api/external-services.xml index 2a9f601b52..6d7d50c39f 100644 --- a/dspace/config/spring/api/external-services.xml +++ b/dspace/config/spring/api/external-services.xml @@ -212,4 +212,15 @@ - \ No newline at end of file + + + + + + + Publication + none + + + + diff --git a/dspace/config/spring/api/identifier-service.xml b/dspace/config/spring/api/identifier-service.xml index e9f08003bd..0c58cc1de9 100644 --- a/dspace/config/spring/api/identifier-service.xml +++ b/dspace/config/spring/api/identifier-service.xml @@ -13,15 +13,13 @@ scope="singleton"/> - - - + - - + + + + + + - - - + @@ -226,16 +241,12 @@ - - - + - - - + - @@ -28,13 +29,12 @@ id="org.dspace.app.requestitem.RequestItemMetadataStrategy" autowire-candidate="true"> - + id="org.dspace.app.requestitem.RequestItemHelpdeskStrategy" + autowire-candidate="true"/> - - + id='org.dspace.app.requestitem.CollectionAdministratorsRequestItemStrategy' + autowire-candidate="true"> + + Send request emails to administrators of an Item's owning + Collection. + + - - - --> diff --git a/dspace/config/spring/api/scripts.xml b/dspace/config/spring/api/scripts.xml index f76cbe88c6..19f558dab6 100644 --- a/dspace/config/spring/api/scripts.xml +++ b/dspace/config/spring/api/scripts.xml @@ -80,4 +80,10 @@ + + + + + + diff --git a/dspace/config/spring/api/subscriptions_email_configuration.xml b/dspace/config/spring/api/subscriptions_email_configuration.xml new file mode 100644 index 0000000000..2d52dfc270 --- /dev/null +++ b/dspace/config/spring/api/subscriptions_email_configuration.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + dc.title + + + + + + + diff --git a/dspace/config/spring/api/workflow-actions.xml b/dspace/config/spring/api/workflow-actions.xml index c1252f4b17..d01f1b6b4c 100644 --- a/dspace/config/spring/api/workflow-actions.xml +++ b/dspace/config/spring/api/workflow-actions.xml @@ -13,9 +13,12 @@ - + + + + - + @@ -61,6 +64,12 @@ + + + + + + diff --git a/dspace/config/spring/api/workflow.xml b/dspace/config/spring/api/workflow.xml index 004ff1d757..448e10fd46 100644 --- a/dspace/config/spring/api/workflow.xml +++ b/dspace/config/spring/api/workflow.xml @@ -151,6 +151,7 @@ + diff --git a/dspace/config/submission-forms.xml b/dspace/config/submission-forms.xml index 8aee946d84..39a4778356 100644 --- a/dspace/config/submission-forms.xml +++ b/dspace/config/submission-forms.xml @@ -910,9 +910,9 @@ - dc - dcterms - references + dcterms + references + false onebox diff --git a/dspace/modules/additions/pom.xml b/dspace/modules/additions/pom.xml index 74a90b027e..dd98bf0cbd 100644 --- a/dspace/modules/additions/pom.xml +++ b/dspace/modules/additions/pom.xml @@ -17,7 +17,7 @@ org.dspace modules - 7.4 + 7.6-SNAPSHOT .. @@ -61,22 +61,6 @@ - - oracle-support - - - db.name - oracle - - - - - com.oracle - ojdbc6 - - - - unit-test-environment @@ -266,13 +250,6 @@ solr-core ${solr.client.version} test - - - - org.apache.commons - commons-text - - org.apache.lucene diff --git a/dspace/modules/pom.xml b/dspace/modules/pom.xml index e36035cd8e..b60246ba6c 100644 --- a/dspace/modules/pom.xml +++ b/dspace/modules/pom.xml @@ -11,7 +11,7 @@ org.dspace dspace-parent - 7.4 + 7.6-SNAPSHOT ../../pom.xml diff --git a/dspace/modules/rest/pom.xml b/dspace/modules/rest/pom.xml index 2ff004d945..115393b7db 100644 --- a/dspace/modules/rest/pom.xml +++ b/dspace/modules/rest/pom.xml @@ -13,7 +13,7 @@ org.dspace modules - 7.4 + 7.6-SNAPSHOT .. @@ -90,24 +90,6 @@ - - - oracle-support - - - db.name - oracle - - - - - com.oracle - ojdbc6 - - - - - diff --git a/dspace/modules/server/pom.xml b/dspace/modules/server/pom.xml index 84117dcdfe..41ddb94be5 100644 --- a/dspace/modules/server/pom.xml +++ b/dspace/modules/server/pom.xml @@ -13,7 +13,7 @@ just adding new jar in the classloader modules org.dspace - 7.4 + 7.6-SNAPSHOT .. @@ -244,22 +244,6 @@ just adding new jar in the classloader - - - oracle-support - - - db.name - oracle - - - - - com.oracle - ojdbc6 - - - @@ -336,13 +320,6 @@ just adding new jar in the classloader solr-core ${solr.client.version} test - - - - org.apache.commons - commons-text - - org.apache.lucene diff --git a/dspace/pom.xml b/dspace/pom.xml index f0d7e3bbe8..7916648e47 100644 --- a/dspace/pom.xml +++ b/dspace/pom.xml @@ -16,7 +16,7 @@ org.dspace dspace-parent - 7.4 + 7.6-SNAPSHOT ../pom.xml diff --git a/dspace/solr/authority/conf/solrconfig.xml b/dspace/solr/authority/conf/solrconfig.xml index 373cbb661c..21f917ebf8 100644 --- a/dspace/solr/authority/conf/solrconfig.xml +++ b/dspace/solr/authority/conf/solrconfig.xml @@ -79,6 +79,7 @@ 200 false 2 + 1000 diff --git a/dspace/solr/oai/conf/solrconfig.xml b/dspace/solr/oai/conf/solrconfig.xml index 02789fa3c2..ce8d9ebe20 100644 --- a/dspace/solr/oai/conf/solrconfig.xml +++ b/dspace/solr/oai/conf/solrconfig.xml @@ -88,6 +88,7 @@ 200 false 2 + 1000 diff --git a/dspace/solr/search/conf/schema.xml b/dspace/solr/search/conf/schema.xml index f9c8eb9760..caa646ba1b 100644 --- a/dspace/solr/search/conf/schema.xml +++ b/dspace/solr/search/conf/schema.xml @@ -264,6 +264,9 @@ + + + @@ -299,6 +302,9 @@ + + diff --git a/dspace/solr/search/conf/solrconfig.xml b/dspace/solr/search/conf/solrconfig.xml index e1174dc4a9..97b1d1ddbb 100644 --- a/dspace/solr/search/conf/solrconfig.xml +++ b/dspace/solr/search/conf/solrconfig.xml @@ -99,6 +99,7 @@ 200 false 2 + 1000 diff --git a/dspace/solr/statistics/conf/solrconfig.xml b/dspace/solr/statistics/conf/solrconfig.xml index 9a6c161462..2b1cff4537 100644 --- a/dspace/solr/statistics/conf/solrconfig.xml +++ b/dspace/solr/statistics/conf/solrconfig.xml @@ -88,6 +88,7 @@ 200 false 2 + 1000 diff --git a/dspace/src/main/docker-compose/README.md b/dspace/src/main/docker-compose/README.md index a83a466bdb..35a6e60554 100644 --- a/dspace/src/main/docker-compose/README.md +++ b/dspace/src/main/docker-compose/README.md @@ -1,4 +1,4 @@ -# Docker Compose Resources +# Docker Compose files for DSpace Backend *** :warning: **THESE IMAGES ARE NOT PRODUCTION READY** The below Docker Compose images/resources were built for development/testing only. Therefore, they may not be fully secured or up-to-date, and should not be used in production. @@ -6,27 +6,51 @@ If you wish to run DSpace on Docker in production, we recommend building your own Docker images. You are welcome to borrow ideas/concepts from the below images in doing so. But, the below images should not be used "as is" in any production scenario. *** -## root directory Resources -- docker-compose.yml - - Docker compose file to orchestrate DSpace 7 REST components -- docker-compose-cli - - Docker compose file to run DSpace CLI tasks within a running DSpace instance in Docker -## dspace/src/main/docker-compose resources +## Overview +The scripts in this directory can be used to start the DSpace REST API (backend) in Docker. +Optionally, the DSpace User Interface (frontend) may also be started in Docker. + +For additional options/settings in starting the User Interface (frontend) in Docker, see the Docker Compose +documentation for the frontend: https://github.com/DSpace/dspace-angular/blob/main/docker/README.md + +## Primary Docker Compose Scripts (in root directory) +The root directory of this project contains the primary Dockerfiles & Docker Compose scripts +which are used to start the backend. + +- docker-compose.yml + - Docker compose file to orchestrate DSpace REST API (backend) components. + - Uses the `Dockerfile` in the same directory. +- docker-compose-cli.yml + - Docker compose file to run DSpace CLI (Command Line Interface) tasks within a running DSpace instance in Docker. See instructions below. + - Uses the `Dockerfile.cli` in the same directory. + +Documentation for all Dockerfiles used by these compose scripts can be found in the ["docker" folder README](../docker/README.md) + +## Additional Docker Compose tools (in ./dspace/src/main/docker-compose) - cli.assetstore.yml - Docker compose file that will download and install a default assetstore. + - The default assetstore is the configurable entities test dataset. Useful for [testing/demos of Entities](#Ingest Option 2 Ingest Entities Test Data). - cli.ingest.yml - - Docker compose file that will run an AIP ingest into DSpace 7. + - Docker compose file that will run an AIP ingest into DSpace 7. Useful for testing/demos with basic Items. - db.entities.yml - - Docker compose file that pre-populate a database instance using a SQL dump. The default dataset is the configurable entities test dataset. -- local.cfg - - Sets the environment used across containers run with docker-compose + - Docker compose file that pre-populate a database instance using a downloaded SQL dump. + - The default dataset is the configurable entities test dataset. Useful for [testing/demos of Entities](#Ingest Option 2 Ingest Entities Test Data). +- db.restore.yml + - Docker compose file that pre-populate a database instance using a *local* SQL dump (hardcoded to `./pgdump.sql`) + - Useful for restoring data from a local backup, or [Upgrading PostgreSQL in Docker](#Upgrading PostgreSQL in Docker) - docker-compose-angular.yml - - Docker compose file that will start a published DSpace angular container that interacts with the branch. + - Docker compose file that will start a published DSpace User Interface container that interacts with the branch. - docker-compose-shibboleth.yml - Docker compose file that will start a *test/demo* Shibboleth SP container (in Apache) that proxies requests to the DSpace container - ONLY useful for testing/development. NOT production ready. +- docker-compose-iiif.yml + - Docker compose file that will start a *test/demo* Cantaloupe image server container required for enabling IIIF support. + - ONLY useful for testing/development. NOT production ready. + +Documentation for all Dockerfiles used by these compose scripts can be found in the ["docker" folder README](../docker/README.md) + ## To refresh / pull DSpace images from Dockerhub ``` @@ -55,6 +79,12 @@ docker-compose -p d7 up -d docker-compose -p d7 -f docker-compose.yml -f dspace/src/main/docker-compose/docker-compose-angular.yml up -d ``` +## Run DSpace REST and DSpace Angular from local branches + +*Allows you to run the backend from the "DSpace/DSpace" codebase while also running the frontend from the "DSpace/dspace-angular" codebase.* + +See documentation in [DSpace User Interface Docker instructions](https://github.com/DSpace/dspace-angular/blob/main/docker/README.md#run-dspace-rest-and-dspace-angular-from-local-branches). + ## Run DSpace 7 REST with a IIIF Image Server from your branch *Only useful for testing IIIF support in a development environment* @@ -67,7 +97,6 @@ docker-compose -p d7 -f docker-compose.yml -f dspace/src/main/docker-compose/doc ``` ## Run DSpace 7 REST and Shibboleth SP (in Apache) from your branch - *Only useful for testing Shibboleth in a development environment* This Shibboleth container uses https://samltest.id/ as an IdP (see `../docker/dspace-shibboleth/`). @@ -143,21 +172,11 @@ The remainder of these instructions assume you are using ngrok (though other pro DSPACE_HOSTNAME=[subdomain].ngrok.io docker-compose -p d7 -f docker-compose.yml -f dspace/src/main/docker-compose/docker-compose-angular.yml -f dspace/src/main/docker-compose/docker-compose-shibboleth.yml up -d ``` -## Run DSpace 7 REST and Angular from local branches +## Sample Test Data -_The system will be started in 2 steps. Each step shares the same docker network._ +### Ingesting test content from AIP files -From DSpace/DSpace -``` -docker-compose -p d7 up -d -``` - -From DSpace/DSpace-angular (build as needed) -``` -docker-compose -p d7 -f docker/docker-compose.yml up -d -``` - -## Ingest Option 1: Ingesting test content from AIP files into a running DSpace 7 instance +*Allows you to ingest a set of AIPs into your DSpace instance for testing/demo purposes.* These AIPs represent basic Communities, Collections and Items. Prerequisites - Start DSpace 7 using one of the options listed above @@ -173,8 +192,14 @@ Download a Zip file of AIP content and ingest test data docker-compose -p d7 -f docker-compose-cli.yml -f dspace/src/main/docker-compose/cli.ingest.yml run --rm dspace-cli ``` -## Ingest Option 2: Ingest Entities Test Data -_Remove your d7 volumes if you already ingested content into your docker volumes_ +### Ingest Entities Test Data + +*Allows you to load Configurable Entities test data for testing/demo purposes.* + +Prerequisites +- Start DSpace 7 using one of the options listed above +- Build the DSpace CLI image if needed. See the instructions above. +- _Remove your d7 volumes if you already ingested content into your docker volumes_ Start DSpace REST with a postgres database dump downloaded from the internet. ``` @@ -212,3 +237,85 @@ Similarly, you can see the value of any DSpace configuration (in local.cfg or ds # Output the value of `dspace.ui.url` from running Docker instance docker-compose -p d7 -f docker-compose-cli.yml run --rm dspace-cli dsprop -p dspace.ui.url ``` + +NOTE: It is also possible to run CLI scripts directly on the "dspace" container (where the backend runs) +This can be useful if you want to pass environment variables which override DSpace configs. +``` +# Run the "./dspace database clean" command from the "dspace" container +# Before doing so, it sets "db.cleanDisabled=false". +# WARNING: This will delete all your data. It's just an example of how to do so. +docker-compose -p d7 exec -e "db__P__cleanDisabled=false" dspace /dspace/bin/dspace database clean +``` + +## Upgrading PostgreSQL in Docker + +Occasionally, we update our `dspace-postgres-*` images to use a new version of PostgreSQL. +Simply using the new image will likely throw errors as the pgdata (postgres data) directory is incompatible +with the new version of PostgreSQL. These errors look like: +``` +FATAL: database files are incompatible with server +DETAIL: The data directory was initialized by PostgreSQL version 11, which is not compatible with this version 13.10 +``` + +Here's how to fix those issues by migrating your old Postgres data to the new version of Postgres + +1. First, you must start up the older PostgreSQL image (to dump your existing data to a `*.sql` file) + ``` + # This command assumes you are using the process described above to start all your containers + docker-compose -p d7 up -d + ``` + * If you've already accidentally updated to the new PostgreSQL image, you have a few options: + * Pull down an older version of the image from Dockerhub (using a tag) + * Or, temporarily rebuild your local image with the old version of Postgres. For example: + ``` + # This command will rebuild using PostgreSQL v11 & tag it locally as "dspace-7_x" + docker build --build-arg POSTGRES_VERSION=11 -t dspace/dspace-postgres-pgcrypto:dspace-7_x ./dspace/src/main/docker/dspace-postgres-pgcrypto/ + # Then restart container with that image + docker-compose -p d7 up -d + ``` +2. Dump your entire "dspace" database out of the old "dspacedb" container to a local file named `pgdump.sql` + ``` + # NOTE: WE HIGHLY RECOMMEND LOGGING INTO THE CONTAINER and doing the pg_dump within the container. + # If you attempt to run pg_dump from your local machine via docker "exec" (or similar), sometimes + # UTF-8 characters can be corrupted in the export file. This may result in data loss. + + # First login to the "dspacedb" container + docker exec -it dspacedb /bin/bash + + # Dump the "dspace" database to a file named "/tmp/pgdump.sql" within the container + pg_dump -U dspace dspace > /tmp/pgdump.sql + + # Exit the container + exit + + # Download (copy) that /tmp/pgdump.sql backup file from container to your local machine + docker cp dspacedb:/tmp/pgdump.sql . + ``` +3. Now, stop all existing containers. This shuts down the old version of PostgreSQL + ``` + # This command assumes you are using the process described above to start/stop all your containers + docker-compose -p d7 down + ``` +4. Delete the `pgdata` volume. WARNING: This deletes all your old PostgreSQL data. Make sure you have that `pgdump.sql` file FIRST! + ``` + # Assumes you are using `-p d7` which prefixes all volumes with `d7_` + docker volume rm d7_pgdata + ``` +5. Now, pull down the latest PostgreSQL image with the NEW version of PostgreSQL. + ``` + docker-compose -f docker-compose.yml -f docker-compose-cli.yml pull + ``` +6. Start everything up using our `db.restore.yml` script. This script will recreate the database +using the local `./pgdump.sql` file. IMPORTANT: If you renamed that "pgdump.sql" file or stored it elsewhere, +then you MUST change the name/directory in the `db.restore.yml` script. + ``` + # Restore database from "./pgdump.sql" (this path is hardcoded in db.restore.yml) + docker-compose -p d7 -f docker-compose.yml -f dspace/src/main/docker-compose/db.restore.yml up -d + ``` +7. Finally, reindex all database contents into Solr (just to be sure Solr indexes are current). + ``` + # Run "./dspace index-discovery -b" using our CLI image + docker-compose -p d7 -f docker-compose-cli.yml run --rm dspace-cli index-discovery -b + ``` +At this point in time, all your old database data should be migrated to the new Postgres +and running at http://localhost:8080/server/ \ No newline at end of file diff --git a/dspace/src/main/docker-compose/db.entities.yml b/dspace/src/main/docker-compose/db.entities.yml index 8d86f7bb83..32c54a5d0b 100644 --- a/dspace/src/main/docker-compose/db.entities.yml +++ b/dspace/src/main/docker-compose/db.entities.yml @@ -10,7 +10,7 @@ version: "3.7" services: dspacedb: - image: dspace/dspace-postgres-pgcrypto:loadsql + image: dspace/dspace-postgres-pgcrypto:dspace-7_x-loadsql environment: # This SQL is available from https://github.com/DSpace-Labs/AIP-Files/releases/tag/demo-entities-data - LOADSQL=https://github.com/DSpace-Labs/AIP-Files/releases/download/demo-entities-data/dspace7-entities-data.sql diff --git a/dspace/src/main/docker-compose/db.restore.yml b/dspace/src/main/docker-compose/db.restore.yml new file mode 100644 index 0000000000..fc2f30b9d8 --- /dev/null +++ b/dspace/src/main/docker-compose/db.restore.yml @@ -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/ +# + +version: "3.7" + +# +# Overrides the default "dspacedb" container behavior to load a local SQL file into PostgreSQL. +# +# This can be used to restore a "dspacedb" container from a pg_dump, or during upgrade to a new version of PostgreSQL. +services: + dspacedb: + image: dspace/dspace-postgres-pgcrypto:dspace-7_x-loadsql + environment: + # Location where the dump SQL file will be available on the running container + - LOCALSQL=/tmp/pgdump.sql + volumes: + # Volume which shares a local SQL file at "./pgdump.sql" to the running container + # IF YOUR LOCAL FILE HAS A DIFFERENT NAME (or is in a different location), then change the "./pgdump.sql" + # portion of this line. + - ./pgdump.sql:/tmp/pgdump.sql + diff --git a/dspace/src/main/docker/README.md b/dspace/src/main/docker/README.md index 6c9da0190c..ac1b4cb923 100644 --- a/dspace/src/main/docker/README.md +++ b/dspace/src/main/docker/README.md @@ -1,4 +1,4 @@ -# Docker images supporting DSpace +# Docker images supporting DSpace Backend *** :warning: **THESE IMAGES ARE NOT PRODUCTION READY** The below Docker Compose images/resources were built for development/testing only. Therefore, they may not be fully secured or up-to-date, and should not be used in production. @@ -6,9 +6,15 @@ If you wish to run DSpace on Docker in production, we recommend building your own Docker images. You are welcome to borrow ideas/concepts from the below images in doing so. But, the below images should not be used "as is" in any production scenario. *** -## Dockerfile.dependencies +## Overview +The Dockerfiles in this directory (and subdirectories) are used by our [Docker Compose scripts](../docker-compose/README.md). + +## Dockerfile.dependencies (in root folder) This Dockerfile is used to pre-cache Maven dependency downloads that will be used in subsequent DSpace docker builds. +Caching these Maven dependencies provides a speed increase to all later builds by ensuring the dependencies +are only downloaded once. + ``` docker build -t dspace/dspace-dependencies:dspace-7_x -f Dockerfile.dependencies . ``` @@ -22,12 +28,13 @@ Admins to our DockerHub repo can manually publish with the following command. docker push dspace/dspace-dependencies:dspace-7_x ``` -## Dockerfile.test +## Dockerfile.test (in root folder) -This Dockerfile builds a DSpace 7 Tomcat image (for testing/development). -This image deploys two DSpace webapps: +This Dockerfile builds a DSpace 7 backend image (for testing/development). +This image deploys two DSpace webapps to Tomcat running in Docker: 1. The DSpace 7 REST API (at `http://localhost:8080/server`) -2. The legacy (v6) REST API (at `http://localhost:8080//rest`), deployed without requiring HTTPS access. +2. The legacy (v6) REST API (at `http://localhost:8080/rest`), deployed without requiring HTTPS access. +This image also sets up debugging in Tomcat for development. ``` docker build -t dspace/dspace:dspace-7_x-test -f Dockerfile.test . @@ -42,12 +49,12 @@ Admins to our DockerHub repo can manually publish with the following command. docker push dspace/dspace:dspace-7_x-test ``` -## Dockerfile +## Dockerfile (in root folder) -This Dockerfile builds a DSpace 7 tomcat image. -This image deploys two DSpace webapps: +This Dockerfile builds a DSpace 7 backend image. +This image deploys one DSpace webapp to Tomcat running in Docker: 1. The DSpace 7 REST API (at `http://localhost:8080/server`) -2. The legacy (v6) REST API (at `http://localhost:8080//rest`), deployed *requiring* HTTPS access. + ``` docker build -t dspace/dspace:dspace-7_x -f Dockerfile . ``` @@ -61,9 +68,9 @@ Admins to our DockerHub repo can publish with the following command. docker push dspace/dspace:dspace-7_x ``` -## Dockefile.cli +## Dockerfile.cli (in root folder) -This Dockerfile builds a DSpace 7 CLI image, which can be used to run commandline tools via Docker. +This Dockerfile builds a DSpace 7 CLI (command line interface) image, which can be used to run DSpace's commandline tools via Docker. ``` docker build -t dspace/dspace-cli:dspace-7_x -f Dockerfile.cli . ``` @@ -77,46 +84,60 @@ Admins to our DockerHub repo can publish with the following command. docker push dspace/dspace-cli:dspace-7_x ``` -## dspace/src/main/docker/dspace-postgres-pgcrypto/Dockerfile +## ./dspace-postgres-pgcrypto/Dockerfile This is a PostgreSQL Docker image containing the `pgcrypto` extension required by DSpace 6+. +This image is built *automatically* after each commit is made to the `main` branch. + +How to build manually: ``` cd dspace/src/main/docker/dspace-postgres-pgcrypto -docker build -t dspace/dspace-postgres-pgcrypto . +docker build -t dspace/dspace-postgres-pgcrypto:dspace-7_x . ``` -**This image is built manually.** It should be rebuilt as needed. +It is also possible to change the version of PostgreSQL or the PostgreSQL user's password during the build: +``` +cd dspace/src/main/docker/dspace-postgres-pgcrypto +docker build -t dspace/dspace-postgres-pgcrypto:dspace-7_x --build-arg POSTGRES_VERSION=11 --build-arg POSTGRES_PASSWORD=mypass . +``` A copy of this file exists in the DSpace 6 branch. A specialized version of this file exists for DSpace 4 in DSpace-Docker-Images. -Admins to our DockerHub repo can publish with the following command. +Admins to our DockerHub repo can (manually) publish with the following command. ``` -docker push dspace/dspace-postgres-pgcrypto +docker push dspace/dspace-postgres-pgcrypto:dspace-7_x ``` -## dspace/src/main/docker/dspace-postgres-pgcrypto-curl/Dockerfile +## ./dspace-postgres-pgcrypto-curl/Dockerfile This is a PostgreSQL Docker image containing the `pgcrypto` extension required by DSpace 6+. This image also contains `curl`. The image is pre-configured to load a Postgres database dump on initialization. + +This image is built *automatically* after each commit is made to the `main` branch. + +How to build manually: ``` cd dspace/src/main/docker/dspace-postgres-pgcrypto-curl -docker build -t dspace/dspace-postgres-pgcrypto:loadsql . +docker build -t dspace/dspace-postgres-pgcrypto:dspace-7_x-loadsql . ``` -**This image is built manually.** It should be rebuilt as needed. +Similar to `dspace-postgres-pgcrypto` above, you can also modify the version of PostgreSQL or the PostgreSQL user's password. +See examples above. A copy of this file exists in the DSpace 6 branch. -Admins to our DockerHub repo can publish with the following command. +Admins to our DockerHub repo can (manually) publish with the following command. ``` -docker push dspace/dspace-postgres-pgcrypto:loadsql +docker push dspace/dspace-postgres-pgcrypto:dspace-7_x-loadsql ``` -## dspace/src/main/docker/dspace-shibboleth/Dockerfile +## ./dspace-shibboleth/Dockerfile This is a test / demo image which provides an Apache HTTPD proxy (in front of Tomcat) -with mod_shib & Shibboleth installed. It is primarily for usage for -testing DSpace's Shibboleth integration. It uses https://samltest.id/ as the Shibboleth IDP +with `mod_shib` & Shibboleth installed based on the +[DSpace Shibboleth configuration instructions](https://wiki.lyrasis.org/display/DSDOC7x/Authentication+Plugins#AuthenticationPlugins-ShibbolethAuthentication). +It is primarily for usage for testing DSpace's Shibboleth integration. +It uses https://samltest.id/ as the Shibboleth IDP **This image is built manually.** It should be rebuilt as needed. @@ -130,10 +151,28 @@ docker run -i -t -d -p 80:80 -p 443:443 dspace/dspace-shibboleth This image can also be rebuilt using the `../docker-compose/docker-compose-shibboleth.yml` script. +## ./dspace-solr/Dockerfile -## test/ folder +This Dockerfile builds a Solr image with DSpace Solr configsets included. It +can be pulled / built following the [docker compose resources](../docker-compose/README.md) +documentation. Or, to just build and/or run Solr: + +```bash +docker-compose build dspacesolr +docker-compose -p d7 up -d dspacesolr +``` + +If you're making iterative changes to the DSpace Solr configsets you'll need to rebuild / +restart the `dspacesolr` container for the changes to be deployed. From DSpace root: + +```bash +docker-compose -p d7 up --detach --build dspacesolr +``` + +## ./test/ folder These resources are bundled into the `dspace/dspace:dspace-*-test` image at build time. +See the `Dockerfile.test` section above for more information about the test image. ## Debugging Docker builds diff --git a/dspace/src/main/docker/dspace-postgres-pgcrypto-curl/Dockerfile b/dspace/src/main/docker/dspace-postgres-pgcrypto-curl/Dockerfile index 0e85dd33ce..b2131a7402 100644 --- a/dspace/src/main/docker/dspace-postgres-pgcrypto-curl/Dockerfile +++ b/dspace/src/main/docker/dspace-postgres-pgcrypto-curl/Dockerfile @@ -6,14 +6,21 @@ # http://www.dspace.org/license/ # -# This will be deployed as dspace/dspace-postgres-pgcrpyto:loadsql -FROM postgres:11 +# To build for example use: +# docker build --build-arg POSTGRES_VERSION=13 --build-arg POSTGRES_PASSWORD=mypass ./dspace/src/main/docker/dspace-postgres-pgcrypto-curl/ +# This will be published as dspace/dspace-postgres-pgcrypto:$DSPACE_VERSION-loadsql + +ARG POSTGRES_VERSION=13 +ARG POSTGRES_PASSWORD=dspace + +FROM postgres:${POSTGRES_VERSION} ENV POSTGRES_DB dspace ENV POSTGRES_USER dspace -ENV POSTGRES_PASSWORD dspace +ENV POSTGRES_PASSWORD ${POSTGRES_PASSWORD} + +# Install curl which is necessary to load SQL file +RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/* # Load a SQL dump. Set LOADSQL to a URL for the sql dump file. -RUN apt-get update && apt-get install -y curl - COPY install-pgcrypto.sh /docker-entrypoint-initdb.d/ diff --git a/dspace/src/main/docker/dspace-postgres-pgcrypto-curl/install-pgcrypto.sh b/dspace/src/main/docker/dspace-postgres-pgcrypto-curl/install-pgcrypto.sh index 054d3dede5..3f8e95e104 100644 --- a/dspace/src/main/docker/dspace-postgres-pgcrypto-curl/install-pgcrypto.sh +++ b/dspace/src/main/docker/dspace-postgres-pgcrypto-curl/install-pgcrypto.sh @@ -11,15 +11,33 @@ set -e CHECKFILE=/pgdata/ingest.hasrun.flag +# If $LOADSQL environment variable set, use 'curl' to download that SQL and run it in PostgreSQL +# This can be used to initialize a database based on test data available on the web. if [ ! -f $CHECKFILE -a ! -z ${LOADSQL} ] then - curl ${LOADSQL} -L -s --output /tmp/dspace.sql - psql -U $POSTGRES_USER < /tmp/dspace.sql + # Download SQL file to /tmp/dspace-db-init.sql + curl ${LOADSQL} -L -s --output /tmp/dspace-db-init.sql + # Load into PostgreSQL + psql -U $POSTGRES_USER < /tmp/dspace-db-init.sql + # Remove downloaded file + rm /tmp/dspace-db-init.sql touch $CHECKFILE exit fi +# If $LOCALSQL environment variable set, then simply run it in PostgreSQL +# This can be used to restore data from a pg_dump or similar. +if [ ! -f $CHECKFILE -a ! -z ${LOCALSQL} ] +then + # Load into PostgreSQL + psql -U $POSTGRES_USER < ${LOCALSQL} + + touch $CHECKFILE + exit +fi + +# Then, setup pgcrypto on this database psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" <<-EOSQL -- Create a new schema in this database named "extensions" (or whatever you want to name it) CREATE SCHEMA extensions; diff --git a/dspace/src/main/docker/dspace-postgres-pgcrypto/Dockerfile b/dspace/src/main/docker/dspace-postgres-pgcrypto/Dockerfile index 84b7569a2b..7dde1a6bfd 100644 --- a/dspace/src/main/docker/dspace-postgres-pgcrypto/Dockerfile +++ b/dspace/src/main/docker/dspace-postgres-pgcrypto/Dockerfile @@ -6,13 +6,18 @@ # http://www.dspace.org/license/ # -# This will be deployed as dspace/dspace-postgres-pgcrpyto:latest -FROM postgres:11 +# To build for example use: +# docker build --build-arg POSTGRES_VERSION=13 --build-arg POSTGRES_PASSWORD=mypass ./dspace/src/main/docker/dspace-postgres-pgcrypto/ +# This will be published as dspace/dspace-postgres-pgcrypto:$DSPACE_VERSION + +ARG POSTGRES_VERSION=13 +ARG POSTGRES_PASSWORD=dspace + +FROM postgres:${POSTGRES_VERSION} ENV POSTGRES_DB dspace ENV POSTGRES_USER dspace -ENV POSTGRES_PASSWORD dspace - -RUN apt-get update +ENV POSTGRES_PASSWORD ${POSTGRES_PASSWORD} +# Copy over script which will initialize database and install pgcrypto extension COPY install-pgcrypto.sh /docker-entrypoint-initdb.d/ diff --git a/dspace/src/main/docker/dspace-solr/Dockerfile b/dspace/src/main/docker/dspace-solr/Dockerfile new file mode 100644 index 0000000000..9fe9adf944 --- /dev/null +++ b/dspace/src/main/docker/dspace-solr/Dockerfile @@ -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/ +# + +# To build use root as context for (easier) access to solr cfgs +# docker build --build-arg SOLR_VERSION=8.11 -f ./dspace/src/main/docker/dspace-solr/Dockerfile . +# This will be published as dspace/dspace-solr:$DSPACE_VERSION + +ARG SOLR_VERSION=8.11 + +FROM solr:${SOLR_VERSION}-slim + +ENV AUTHORITY_CONFIGSET_PATH=/opt/solr/server/solr/configsets/authority/conf \ + OAI_CONFIGSET_PATH=/opt/solr/server/solr/configsets/oai/conf \ + SEARCH_CONFIGSET_PATH=/opt/solr/server/solr/configsets/search/conf \ + STATISTICS_CONFIGSET_PATH=/opt/solr/server/solr/configsets/statistics/conf + +USER root + +RUN mkdir -p $AUTHORITY_CONFIGSET_PATH && \ + mkdir -p $OAI_CONFIGSET_PATH && \ + mkdir -p $SEARCH_CONFIGSET_PATH && \ + mkdir -p $STATISTICS_CONFIGSET_PATH + +COPY dspace/solr/authority/conf/* $AUTHORITY_CONFIGSET_PATH/ +COPY dspace/solr/oai/conf/* $OAI_CONFIGSET_PATH/ +COPY dspace/solr/search/conf/* $SEARCH_CONFIGSET_PATH/ +COPY dspace/solr/statistics/conf/* $STATISTICS_CONFIGSET_PATH/ + +RUN chown -R solr:solr /opt/solr/server/solr/configsets + +USER solr diff --git a/pom.xml b/pom.xml index da2ada0e4d..ef6227e6ff 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ org.dspace dspace-parent pom - 7.4 + 7.6-SNAPSHOT DSpace Parent Project DSpace open source software is a turnkey institutional repository application. @@ -19,29 +19,29 @@ 11 - 5.3.20 - 2.6.8 - 5.6.5 - 5.6.5.Final - 6.0.23.Final - 42.4.1 - 8.11.1 + 5.3.27 + 2.7.12 + 5.7.8 + 5.6.15.Final + 6.2.5.Final + 42.6.0 + 8.11.2 3.4.0 2.10.0 - 2.12.6 - 2.12.6.1 + 2.13.4 + 2.13.4.2 1.3.2 2.3.1 2.3.1 1.1.0 - 9.4.48.v20220622 - 2.17.1 - 2.0.24 - 1.18.0 - 1.7.25 + 9.4.51.v20230217 + 2.20.0 + 2.0.28 + 1.19.0 + 1.7.36 2.3.0 1.70 @@ -872,14 +872,14 @@ org.dspace dspace-rest - 7.4 + 7.6-SNAPSHOT jar classes org.dspace dspace-rest - 7.4 + 7.6-SNAPSHOT war @@ -1030,69 +1030,69 @@ org.dspace dspace-api - 7.4 + 7.6-SNAPSHOT org.dspace dspace-api test-jar - 7.4 + 7.6-SNAPSHOT test org.dspace.modules additions - 7.4 + 7.6-SNAPSHOT org.dspace dspace-sword - 7.4 + 7.6-SNAPSHOT org.dspace dspace-swordv2 - 7.4 + 7.6-SNAPSHOT org.dspace dspace-oai - 7.4 + 7.6-SNAPSHOT org.dspace dspace-services - 7.4 + 7.6-SNAPSHOT org.dspace dspace-server-webapp test-jar - 7.4 + 7.6-SNAPSHOT test org.dspace dspace-rdf - 7.4 + 7.6-SNAPSHOT org.dspace dspace-iiif - 7.4 + 7.6-SNAPSHOT org.dspace dspace-server-webapp - 7.4 + 7.6-SNAPSHOT jar classes org.dspace dspace-server-webapp - 7.4 + 7.6-SNAPSHOT war @@ -1479,12 +1479,12 @@ org.apache.commons commons-dbcp2 - 2.8.0 + 2.9.0 commons-fileupload commons-fileupload - 1.3.3 + 1.5 commons-io @@ -1506,7 +1506,12 @@ org.apache.commons commons-pool2 - 2.9.0 + 2.11.1 + + + org.apache.commons + commons-text + 1.10.0 commons-validator @@ -1612,11 +1617,6 @@ icu4j 62.1 - - com.oracle - ojdbc6 - 11.2.0.4.0 - org.dspace @@ -1927,7 +1927,7 @@ scm:git:git@github.com:DSpace/DSpace.git scm:git:git@github.com:DSpace/DSpace.git git@github.com:DSpace/DSpace.git - dspace-7.4 + HEAD