diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index b11e3cd531cf..5b3f4336e6a2 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 67c2d496af92..dcb98747ba1e 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 @@ -80,11 +83,11 @@ 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 - name: Upload coverage to Codecov.io - uses: codecov/codecov-action@v2 + uses: codecov/codecov-action@v3 diff --git a/.github/workflows/codescan.yml b/.github/workflows/codescan.yml new file mode 100644 index 000000000000..7580b4ba3dc3 --- /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 7f58a49f9e9e..64e12f01aac0 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 diff --git a/.github/workflows/issue_opened.yml b/.github/workflows/issue_opened.yml index 3ccdd22a0ddd..5d7c1c30f7d3 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.3.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 dcbab18f1b57..d71d244c2b02 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@v2 # 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 000000000000..45a6af9ce5a3 --- /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.test b/Dockerfile.test index 5e70c5e539ca..4e9b2b5b4343 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 92d0b71a70af..b96ea77648a6 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 39ab43db2778..618a01456180 100644 --- a/README.md +++ b/README.md @@ -25,24 +25,17 @@ Past releases and future releases are documented in the [RoadMap page](https://w Documentation is available on our [Documentation Wiki](https://wiki.lyrasis.org/display/DSPACECRIS/Technical+and+User+documentation) please check also the documentation from the parent DSpace project as basic features and principle are common and only described in the [DSpace documentation](https://wiki.lyrasis.org/display/DSDOC/). -The latest DSpace Installation instructions are available at: -https://wiki.lyrasis.org/display/DSDOC7x/Installing+DSpace +The latest DSpace Installation instructions are available [here](https://wiki.lyrasis.org/display/DSDOC7x/Installing+DSpace). Some extra step to initialize the DSpace-CRIS with proper default can be found in our documentation (see above). -some extra step to initialize te DSpace-CRIS with proper default can be found in our documentation (see above) +Please be aware that, as a Java web application, DSpace-CRIS requires a database (PostgreSQL or Oracle), a servlet container (usually Tomcat) and a SOLR instance in order to function. -Please be aware that, as a Java web application, DSpace-CRIS requires a database (PostgreSQL) -and a servlet container (usually Tomcat) in order to function. More information about these and all other prerequisites can be found in the Installation instructions above. ## Running DSpace-CRIS 7 in Docker -NOTE: At this time, we do not have production-ready Docker images for DSpace-CRIS. +**NOTE**: At this time, we do not have production-ready Docker images for DSpace-CRIS. That said, we do have quick-start Docker Compose scripts for development or testing purposes. -Please be aware that, as a Java web application, DSpace-CRIS requires a database (PostgreSQL or Oracle), a servlet container (usually Tomcat) and a SOLR instance in order to function. - -More information about these and all other prerequisites can be found in the Installation instructions above. - ## Getting Help DSpace-CRIS has a [dedicated slack channel](https://dspace-org.slack.com/messages/dspace-cris/) in the DSpace.org workspace. diff --git a/checkstyle.xml b/checkstyle.xml index 815edaec7bf0..e0fa808d83cb 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 f790257bdb81..6008b873ae5f 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: diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index e28d1d369f72..c70f5a2a6370 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -12,7 +12,7 @@ org.dspace dspace-parent - cris-2023.01.00-SNAPSHOT + cris-2023.01.01-SNAPSHOT .. @@ -589,13 +589,6 @@ solr-core test ${solr.client.version} - - - - org.apache.commons - commons-text - - org.apache.lucene @@ -828,10 +821,11 @@ test - + org.apache.bcel bcel - 6.4.0 + 6.6.0 + test @@ -927,11 +921,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 000000000000..a200cab8781f --- /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 000000000000..f56cbdcce9e9 --- /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 000000000000..9ddf6c97d111 --- /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 000000000000..b26b64758355 --- /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 000000000000..13a0e0af236a --- /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 000000000000..cf231308849d --- /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/BulkImport.java b/dspace-api/src/main/java/org/dspace/app/bulkedit/BulkImport.java index fbb904f8084e..491039cff835 100644 --- a/dspace-api/src/main/java/org/dspace/app/bulkedit/BulkImport.java +++ b/dspace-api/src/main/java/org/dspace/app/bulkedit/BulkImport.java @@ -1158,9 +1158,6 @@ private Item updateItem(EntityRow entityRow, Item item) handler.logInfo("Row " + entityRow.getRow() + " - Item updated successfully - ID: " + item.getID()); switch (entityRow.getAction()) { - case UPDATE: - itemService.update(context, item); - break; case UPDATE_WORKFLOW: startWorkflow(entityRow, item); break; @@ -1168,6 +1165,7 @@ private Item updateItem(EntityRow entityRow, Item item) installItem(entityRow, item); break; default: + itemService.update(context, item); break; } 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 4fd91e252d07..5a2e62332492 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 @@ -593,16 +593,20 @@ public List runImport(Context c, boolean change, changes.add(whatHasChanged); } - if (change) { - //only clear cache if changes have been made. - c.uncacheEntity(wsItem); - c.uncacheEntity(wfItem); - c.uncacheEntity(item); + if (change && (rowCount % configurationService.getIntProperty("bulkedit.change.commit.count", 100) == 0)) { + c.commit(); + handler.logInfo(LogHelper.getHeader(c, "metadata_import_commit", "lineNumber=" + rowCount)); } populateRefAndRowMap(line, item == null ? null : item.getID()); // keep track of current rows processed rowCount++; } + if (change) { + c.commit(); + } + + c.setMode(Context.Mode.READ_ONLY); + // Return the changes if (!change) { diff --git a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImport.java b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImport.java index 034bdca8c177..6870b94eee1d 100644 --- a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImport.java +++ b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImport.java @@ -67,6 +67,7 @@ public class ItemImport extends DSpaceRunnable { protected String eperson = null; protected String[] collections = null; protected boolean isTest = false; + protected boolean isExcludeContent = false; protected boolean isResume = false; protected boolean useWorkflow = false; protected boolean useWorkflowSendEmail = false; @@ -119,6 +120,8 @@ public void setup() throws ParseException { handler.logInfo("**Test Run** - not actually importing items."); } + isExcludeContent = commandLine.hasOption('x'); + if (commandLine.hasOption('p')) { template = true; } @@ -204,6 +207,7 @@ public void internalRun() throws Exception { .getItemImportService(); try { itemImportService.setTest(isTest); + itemImportService.setExcludeContent(isExcludeContent); itemImportService.setResume(isResume); itemImportService.setUseWorkflow(useWorkflow); itemImportService.setUseWorkflowSendEmail(useWorkflowSendEmail); 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 6582996f368e..d265cbf4a1d6 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 @@ /** * The {@link ScriptConfiguration} for the {@link ItemImportCLI} script - * + * * @author Francesco Pio Scognamiglio (francescopio.scognamiglio at 4science.com) */ public class ItemImportCLIScriptConfiguration extends ItemImportScriptConfiguration { @@ -55,6 +55,9 @@ public Options getOptions() { 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 fc761af0fac1..a3149040c49b 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 @@ /** * The {@link ScriptConfiguration} for the {@link ItemImport} script - * + * * @author Francesco Pio Scognamiglio (francescopio.scognamiglio at 4science.com) */ public class ItemImportScriptConfiguration extends ScriptConfiguration { @@ -81,6 +81,9 @@ public Options getOptions() { 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 047a589b6681..076cc8ebe20e 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.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 @@ * 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; @@ -1403,6 +1405,10 @@ protected List processContentsFile(Context c, Item i, String path, 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 +2348,11 @@ public void setTest(boolean isTest) { 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 76314897ec91..e99ece31b9bb 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 void replaceItems(Context c, List mycollections, String sourc */ 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/BrandedPreviewJPEGFilter.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/BrandedPreviewJPEGFilter.java index 7b082c6c21a4..7073f4f09776 100644 --- a/dspace-api/src/main/java/org/dspace/app/mediafilter/BrandedPreviewJPEGFilter.java +++ b/dspace-api/src/main/java/org/dspace/app/mediafilter/BrandedPreviewJPEGFilter.java @@ -22,7 +22,7 @@ * * @author Jason Sherman jsherman@usao.edu */ -public class BrandedPreviewJPEGFilter extends MediaFilter { +public class BrandedPreviewJPEGFilter extends JPEGFilter { @Override public String getFilteredName(String oldFilename) { return oldFilename + ".preview.jpg"; @@ -36,14 +36,6 @@ public String getBundleName() { return "BRANDED_PREVIEW"; } - /** - * @return String bitstreamformat - */ - @Override - public String getFormatString() { - return "JPEG"; - } - /** * @return String description */ @@ -81,9 +73,7 @@ public InputStream getDestinationStream(Item currentItem, InputStream source, bo String brandFont = configurationService.getProperty("webui.preview.brand.font"); int brandFontPoint = configurationService.getIntProperty("webui.preview.brand.fontpoint"); - JPEGFilter jpegFilter = new JPEGFilter(); - return jpegFilter - .getThumbDim(currentItem, buf, verbose, xmax, ymax, blurring, hqscaling, brandHeight, brandFontPoint, + return getThumbDim(currentItem, buf, verbose, xmax, ymax, blurring, hqscaling, brandHeight, brandFontPoint, brandFont); } } 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 a79fd42d5937..d16243e3e3bc 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.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 File getImageFile(File f, int page, boolean verbose) 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 50efa68ff410..6b7f833e6dde 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 @@ -397,8 +397,8 @@ public boolean processBitstream(Context context, Item item, Bitstream source, Fo 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 diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/service/impl/NBEventServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/nbevent/service/impl/NBEventServiceImpl.java index 4df11b054e67..e07fb16c72ca 100644 --- a/dspace-api/src/main/java/org/dspace/app/nbevent/service/impl/NBEventServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/app/nbevent/service/impl/NBEventServiceImpl.java @@ -8,14 +8,17 @@ package org.dspace.app.nbevent.service.impl; import java.io.IOException; +import java.sql.SQLException; import java.util.ArrayList; import java.util.Date; +import java.util.Iterator; import java.util.List; import java.util.UUID; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.json.JsonMapper; +import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.solr.client.solrj.SolrClient; @@ -33,6 +36,7 @@ import org.dspace.app.nbevent.NBTopic; import org.dspace.app.nbevent.dao.impl.NBEventsDaoImpl; import org.dspace.app.nbevent.service.NBEventService; +import org.dspace.authorize.AuthorizeException; import org.dspace.content.Item; import org.dspace.content.NBEvent; import org.dspace.content.service.ItemService; @@ -316,18 +320,38 @@ private String getResourceUUID(Context context, String originalId) throws Except String id = getHandleFromOriginalId(originalId); if (id != null) { Item item = (Item) handleService.resolveToObject(context, id); + if (item != null) { final String itemUuid = item.getID().toString(); context.uncacheEntity(item); return itemUuid; } else { - return null; + item = fromLegacyIdentifier(context, originalId); + return item == null ? null : item.getID().toString(); } } else { throw new RuntimeException("Malformed originalId " + originalId); } } + private Item fromLegacyIdentifier(Context context, String legacyIdentifier) { + if (StringUtils.isBlank(legacyIdentifier)) { + return null; + } + try { + Iterator + iterator = itemService.findUnfilteredByMetadataField( + context, "dspace", "legacy", "oai-identifier", + legacyIdentifier); + if (!iterator.hasNext()) { + return null; + } + return iterator.next(); + } catch (AuthorizeException | SQLException e) { + throw new RuntimeException(e); + } + } + // oai:www.openstarts.units.it:10077/21486 private String getHandleFromOriginalId(String originalId) { Integer startPosition = originalId.lastIndexOf(':'); 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 bc97bc64bfc2..5c6e48ee3f85 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 02054ee1a0fc..384f33decaf2 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 @@ static public void sendResponse(Context context, RequestItem ri, String subject, 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"); @@ -179,11 +180,19 @@ static public void sendResponse(Context context, RequestItem ri, String subject, bitstream.getFormat(context).getMIMEType()); } 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 +229,13 @@ static public void requestOpenAccess(Context context, RequestItem ri) 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 f440ba380a0e..dee0ed7a2351 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.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 RequestItemHelpdeskStrategy() { @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 List getRequestItemAuthor(Context context, Item item) } /** - * 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 d2b249f6ec9f..b915cfedd346 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 @@ 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 RequestItem findByToken(Context context, String token) { } } + @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 dcc1a3e80eac..6cfeee442600 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 @@ * @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 4a4ea6cd905d..b36ae58e0ca1 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 fa1ed9ffeb64..008174ded88c 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 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 RequestItem findByToken(Context context, String token) throws SQLExceptio 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 5cab72e4e903..efac3b18bc7c 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 List findAll(Context context) */ 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/suggestion/SuggestionSource.java b/dspace-api/src/main/java/org/dspace/app/suggestion/SuggestionSource.java index b9df687dec32..ced841cbfa0e 100644 --- a/dspace-api/src/main/java/org/dspace/app/suggestion/SuggestionSource.java +++ b/dspace-api/src/main/java/org/dspace/app/suggestion/SuggestionSource.java @@ -24,7 +24,7 @@ public SuggestionSource() { /** * Summarize the available suggestions from a source. * - * @param the name must be not null + * @param name must be not null */ public SuggestionSource(String name) { super(); diff --git a/dspace-api/src/main/java/org/dspace/app/suggestion/oaire/AuthorNamesScorer.java b/dspace-api/src/main/java/org/dspace/app/suggestion/oaire/AuthorNamesScorer.java index f429ae017ce9..a209a3cefb9c 100644 --- a/dspace-api/src/main/java/org/dspace/app/suggestion/oaire/AuthorNamesScorer.java +++ b/dspace-api/src/main/java/org/dspace/app/suggestion/oaire/AuthorNamesScorer.java @@ -54,7 +54,6 @@ public List getContributorMetadata() { /** * set the metadata key of the Item which to base the filter on - * @return metadata key */ public void setContributorMetadata(List contributorMetadata) { this.contributorMetadata = contributorMetadata; diff --git a/dspace-api/src/main/java/org/dspace/app/suggestion/oaire/EvidenceScorer.java b/dspace-api/src/main/java/org/dspace/app/suggestion/oaire/EvidenceScorer.java index 9df7621b46bb..6e00fdef0d8a 100644 --- a/dspace-api/src/main/java/org/dspace/app/suggestion/oaire/EvidenceScorer.java +++ b/dspace-api/src/main/java/org/dspace/app/suggestion/oaire/EvidenceScorer.java @@ -32,6 +32,6 @@ public interface EvidenceScorer { * @return the generated suggestion evidence or null if the record should be * discarded */ - public SuggestionEvidence computeEvidence(Item researcher, ExternalDataObject importRecords); + public SuggestionEvidence computeEvidence(Item researcher, ExternalDataObject importRecord); } 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 f863fc65a28a..91c5c09440e9 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 @@ -221,7 +221,7 @@ public DCInput(Map fieldMap, Map> listMap) || "yes".equalsIgnoreCase(closedVocabularyStr); // parsing of the element (using the colon as split separator) - typeBind = new ArrayList<>(); + typeBind = new ArrayList(); String typeBindDef = fieldMap.get("type-bind"); if (typeBindDef != null && typeBindDef.trim().length() > 0) { String[] types = typeBindDef.split(","); @@ -611,7 +611,7 @@ public boolean validate(String value) { if (StringUtils.isNotBlank(value)) { try { if (this.pattern != null) { - if (!this.pattern.matcher(value).matches()) { + if (!pattern.matcher(value).matches()) { return false; } } diff --git a/dspace-api/src/main/java/org/dspace/app/util/DCInputsReader.java b/dspace-api/src/main/java/org/dspace/app/util/DCInputsReader.java index 72051d31e8d8..ac8031880233 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/DCInputsReader.java +++ b/dspace-api/src/main/java/org/dspace/app/util/DCInputsReader.java @@ -158,8 +158,8 @@ public List getPairs(String name) { * Returns the set of DC inputs used for a particular collection, or the default * set if no inputs defined for the collection * - * @param collectionHandle collection - * @return DC input set + * @param collection collection + * @return DC input set * @throws DCInputsReaderException if no default set defined * @throws ServletException */ diff --git a/dspace-api/src/main/java/org/dspace/app/util/RegexPatternUtils.java b/dspace-api/src/main/java/org/dspace/app/util/RegexPatternUtils.java index 63657aa25daa..578e57fb0909 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/RegexPatternUtils.java +++ b/dspace-api/src/main/java/org/dspace/app/util/RegexPatternUtils.java @@ -34,7 +34,9 @@ public class RegexPatternUtils { /** * Computes a pattern starting from a regex definition with flags that - * uses the standard format: /{regex}/{flags}. + * 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, @@ -49,20 +51,21 @@ public static final Pattern computePattern(String regex) throws PatternSyntaxExc return null; } Matcher inputMatcher = PATTERN_REGEX_INPUT_VALIDATOR.matcher(regex); - Pattern pattern = null; + String regexPattern = regex; + String regexFlags = ""; if (inputMatcher.matches()) { - String regexPattern = inputMatcher.group(2); - String regexFlags = + 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", ""); - pattern = Pattern.compile(regexFlags + regexPattern); - } else { - pattern = Pattern.compile(regex); + .filter(StringUtils::isNotBlank) + .map(flags -> String.format(REGEX_FLAGS, flags)) + .orElse("") + .replaceAll("g", ""); } - return pattern; + return Pattern.compile(regexFlags + regexPattern); } private RegexPatternUtils() {} diff --git a/dspace-api/src/main/java/org/dspace/app/util/SubmissionStepConfig.java b/dspace-api/src/main/java/org/dspace/app/util/SubmissionStepConfig.java index 7f29e6d79e52..03d5dc6b7657 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/SubmissionStepConfig.java +++ b/dspace-api/src/main/java/org/dspace/app/util/SubmissionStepConfig.java @@ -11,6 +11,9 @@ import java.util.Map; import org.apache.commons.lang3.BooleanUtils; +import org.dspace.content.InProgressSubmission; +import org.dspace.content.WorkspaceItem; +import org.hibernate.proxy.HibernateProxyHelper; /** * Class representing configuration for a single step within an Item Submission @@ -179,6 +182,45 @@ public String getVisibilityOutside() { return visibilityOutside; } + public boolean isHiddenForInProgressSubmission(InProgressSubmission obj) { + + String scopeToCheck = getScope(obj); + + if (scope == null || scopeToCheck == null) { + return false; + } + + String visibility = getVisibility(); + String visibilityOutside = getVisibilityOutside(); + + if (scope.equalsIgnoreCase(scopeToCheck)) { + return "hidden".equalsIgnoreCase(visibility); + } else { + return visibilityOutside == null || "hidden".equalsIgnoreCase(visibilityOutside); + } + + } + + public boolean isReadOnlyForInProgressSubmission(InProgressSubmission obj) { + + String scopeToCheck = getScope(obj); + + if (scope == null || scopeToCheck == null) { + return false; + } + + String visibility = scope.equalsIgnoreCase(scopeToCheck) ? getVisibility() : getVisibilityOutside(); + return "read-only".equalsIgnoreCase(visibility); + + } + + private String getScope(InProgressSubmission obj) { + if (HibernateProxyHelper.getClassWithoutInitializingProxy(obj).equals(WorkspaceItem.class)) { + return "submission"; + } + return "workflow"; + } + /** * Get the number of this step in the current Submission process config. * Step numbers start with #0 (although step #0 is ALWAYS the special diff --git a/dspace-api/src/main/java/org/dspace/authority/service/ItemReferenceResolver.java b/dspace-api/src/main/java/org/dspace/authority/service/ItemReferenceResolver.java index 3c9a3d40068b..cd80accf9ba1 100644 --- a/dspace-api/src/main/java/org/dspace/authority/service/ItemReferenceResolver.java +++ b/dspace-api/src/main/java/org/dspace/authority/service/ItemReferenceResolver.java @@ -24,13 +24,11 @@ public interface ItemReferenceResolver { * * @param context the DSpace Context * @param item the item to search for - * @return the found items */ void resolveReferences(Context context, Item item); /** * Clears the resolver cache if any is used - * @return void */ void clearCache(); } diff --git a/dspace-api/src/main/java/org/dspace/authority/service/ItemReferenceResolverService.java b/dspace-api/src/main/java/org/dspace/authority/service/ItemReferenceResolverService.java index ba54c01c174d..3f5678185c71 100644 --- a/dspace-api/src/main/java/org/dspace/authority/service/ItemReferenceResolverService.java +++ b/dspace-api/src/main/java/org/dspace/authority/service/ItemReferenceResolverService.java @@ -24,13 +24,11 @@ public interface ItemReferenceResolverService { * * @param context the DSpace Context * @param item the item to search for - * @return the found items */ void resolveReferences(Context context, Item item); /** * Clears the resolver cache if any is used - * @return void */ void clearResolversCache(); } 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 645f703b18f5..014de4671c8b 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java @@ -35,10 +35,12 @@ import org.dspace.content.RelationshipType; 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; @@ -543,6 +545,15 @@ public void inheritPolicies(Context c, DSpaceObject src, 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 { @@ -852,7 +863,7 @@ public List findAdminAuthorizedCommunity(Context context, String quer 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); @@ -874,7 +885,7 @@ public long countAdminAuthorizedCommunity(Context context, String query) query = formatCustomQuery(query); DiscoverResult discoverResult = getDiscoverResult(context, query + "search.resourcetype:" + IndexableCommunity.TYPE, - null, null); + null, null, null, null); return discoverResult.getTotalSearchResults(); } @@ -895,11 +906,12 @@ public List findAdminAuthorizedCollection(Context context, String qu if (context.getCurrentUser() == null) { return collections; } - StringBuilder queryBuilder = new StringBuilder(); - queryBuilder.append(formatCustomQuery(query)); - queryBuilder.append("search.resourcetype:").append(IndexableCollection.TYPE); - DiscoverResult discoverResult = getDiscoverResult(context, queryBuilder.toString(), - offset, limit); + + query = formatCustomQuery(query); + DiscoverResult discoverResult = getDiscoverResult(context, query + "search.resourcetype:" + + IndexableCollection.TYPE, + offset, limit, CollectionService.SOLR_SORT_FIELD, SORT_ORDER.asc); + for (IndexableObject solrCollections : discoverResult.getIndexableObjects()) { Collection collection = ((IndexableCollection) solrCollections).getIndexedObject(); collections.add(collection); @@ -921,7 +933,7 @@ public long countAdminAuthorizedCollection(Context context, String query) query = formatCustomQuery(query); DiscoverResult discoverResult = getDiscoverResult(context, query + "search.resourcetype:" + IndexableCollection.TYPE, - null, null); + null, null, null, null); return discoverResult.getTotalSearchResults(); } @@ -941,7 +953,7 @@ private boolean performCheck(Context context, String query) throws SQLException } try { - DiscoverResult discoverResult = getDiscoverResult(context, query, null, null); + DiscoverResult discoverResult = getDiscoverResult(context, query, null, null, null, null); if (discoverResult.getTotalSearchResults() > 0) { return true; } @@ -953,8 +965,8 @@ private boolean performCheck(Context context, String query) throws SQLException return false; } - private DiscoverResult getDiscoverResult(Context context, String query, Integer offset, Integer limit) - throws SearchServiceException, SQLException { + private DiscoverResult getDiscoverResult(Context context, String query, Integer offset, Integer limit, + String sortField, SORT_ORDER sortOrder) throws SearchServiceException, SQLException { DiscoverQuery discoverQuery = new DiscoverQuery(); if (!this.isAdmin(context)) { StringBuilder stringBuilder = new StringBuilder(); @@ -970,7 +982,9 @@ private DiscoverResult getDiscoverResult(Context context, String query, Integer 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/service/AuthorizeService.java b/dspace-api/src/main/java/org/dspace/authorize/service/AuthorizeService.java index bf2f74e22313..43ae51544c9b 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 @@ -644,4 +644,16 @@ long countAdminAuthorizedCollection(Context context, String query) */ 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/batch/service/ImpRecordService.java b/dspace-api/src/main/java/org/dspace/batch/service/ImpRecordService.java index cc22b70fea39..0a07187e3fa1 100644 --- a/dspace-api/src/main/java/org/dspace/batch/service/ImpRecordService.java +++ b/dspace-api/src/main/java/org/dspace/batch/service/ImpRecordService.java @@ -59,7 +59,6 @@ public interface ImpRecordService { * withdrawn state * * @param impRecord the ImpRecord object - * @param ePerson the person */ public void setStatus(ImpRecord impRecord, Character status); @@ -69,7 +68,6 @@ public interface ImpRecordService { * update = update or create a new record delete = delete the record * * @param impRecord the ImpRecord object - * @param ePerson the person */ public void setOperation(ImpRecord impRecord, String operation); 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 29debf64e2af..03130e39e78b 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 6f0235e6c156..351c36248209 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 BrowseInfo browseMini(BrowserScope bs) 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 @@ private BrowseInfo browseByItem(BrowserScope bs) 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 @@ private BrowseInfo browseByValue(BrowserScope bs) 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 aa30862e3c34..1ce2e558866d 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 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 boolean hasLink(String metadata) { * @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 e48486d86c04..0194be59f3a7 100644 --- a/dspace-api/src/main/java/org/dspace/browse/SolrBrowseDAO.java +++ b/dspace-api/src/main/java/org/dspace/browse/SolrBrowseDAO.java @@ -13,12 +13,13 @@ 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 +31,8 @@ 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.ConfigurationService; @@ -124,9 +127,9 @@ public int compare(Object o1, Object o2) { 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 @@ -180,6 +183,7 @@ private DiscoverResult getSolrResponse() throws BrowseException { addLocationScopeFilter(query); addStatusFilter(query); addExtraFilter(query); + addDefaultFilterQueries(query); if (distinct) { DiscoverFacetField dff; if (StringUtils.isNotBlank(startsWith)) { @@ -210,7 +214,8 @@ private DiscoverResult getSolrResponse() throws BrowseException { 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 @@ -249,15 +254,20 @@ private void addStatusFilter(DiscoverQuery query) { } private void addLocationScopeFilter(DiscoverQuery query) { - if (containerID != null) { + if (container != null) { if (containerIDField.startsWith("collection")) { - query.addFilterQueries("location.coll:" + containerID); + query.addFilterQueries("location.coll:" + container.getID()); } else if (containerIDField.startsWith("community")) { - query.addFilterQueries("location.comm:" + containerID); + query.addFilterQueries("location.comm:" + container.getID()); } } } + private void addDefaultFilterQueries(DiscoverQuery query) { + DiscoveryConfiguration discoveryConfiguration = SearchUtils.getDiscoveryConfiguration(container); + discoveryConfiguration.getDefaultFilterQueries().forEach(query::addFilterQueries); + } + @Override public int doCountQuery() throws BrowseException { DiscoverResult resp = getSolrResponse(); @@ -348,6 +358,7 @@ public int doOffsetQuery(String column, String value, boolean isAscending) addLocationScopeFilter(query); addStatusFilter(query); addExtraFilter(query); + addDefaultFilterQueries(query); query.setMaxResults(0); query.addFilterQueries("search.resourcetype:" + IndexableItem.TYPE); @@ -408,8 +419,8 @@ public void setEnableBrowseFrequencies(boolean enableBrowseFrequencies) { * @see org.dspace.browse.BrowseDAO#getContainerID() */ @Override - public UUID getContainerID() { - return containerID; + public DSpaceObject getContainer() { + return container; } /* @@ -571,8 +582,8 @@ public void setAscending(boolean ascending) { * @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/CollectionServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java index 3df762e5423d..367c7a5d34b1 100644 --- a/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java @@ -48,6 +48,7 @@ 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; @@ -961,8 +962,8 @@ public List findCollectionsWithSubmit(String q, Context context, Com discoverQuery.setDSpaceObjectFilter(IndexableCollection.TYPE); discoverQuery.setStart(offset); discoverQuery.setMaxResults(limit); - DiscoverResult resp = retrieveCollectionsWithSubmit(context, discoverQuery, - entityType, community, q); + discoverQuery.setSortField(SOLR_SORT_FIELD, SORT_ORDER.asc); + DiscoverResult resp = retrieveCollectionsWithSubmit(context, discoverQuery, entityType, community, q); for (IndexableObject solrCollections : resp.getIndexableObjects()) { Collection c = ((IndexableCollection) solrCollections).getIndexedObject(); collections.add(c); 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 c1284c6dfd58..b4053a724f32 100644 --- a/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java @@ -79,6 +79,7 @@ public class CommunityServiceImpl extends DSpaceObjectServiceImpl imp protected SubscribeService subscribeService; @Autowired(required = true) protected CrisMetricsService crisMetricsService; + protected CommunityServiceImpl() { super(); @@ -554,6 +555,8 @@ protected void rawDelete(Context context, Community community) 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/InstallItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/InstallItemServiceImpl.java index 360fc20cadf4..213bbcbaa0cc 100644 --- a/dspace-api/src/main/java/org/dspace/content/InstallItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/InstallItemServiceImpl.java @@ -10,10 +10,15 @@ import java.io.IOException; import java.sql.SQLException; import java.util.List; +import java.util.Map; import org.apache.commons.lang.StringUtils; +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; @@ -21,8 +26,11 @@ 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; /** @@ -43,9 +51,13 @@ public class InstallItemServiceImpl implements InstallItemService { protected IdentifierService identifierService; @Autowired(required = true) protected ItemService itemService; + @Autowired(required = true) + protected SupervisionOrderService supervisionOrderService; + @Autowired(required = false) - protected InstallItemServiceImpl() { + Logger log = LogManager.getLogger(InstallItemServiceImpl.class); + protected InstallItemServiceImpl() { } @Override @@ -60,10 +72,14 @@ public Item installItem(Context c, InProgressSubmission is, 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) { @@ -224,9 +240,19 @@ protected Item finishItem(Context c, Item item, InProgressSubmission is) // 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 99bd3252149d..3ad03377cb27 100644 --- a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java @@ -31,11 +31,14 @@ import java.util.function.Supplier; import java.util.regex.Pattern; import java.util.stream.Collectors; +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.metrics.service.CrisMetricsService; +import org.dspace.app.requestitem.RequestItem; +import org.dspace.app.requestitem.service.RequestItemService; import org.dspace.app.util.AuthorizeUtil; import org.dspace.authority.service.impl.ItemSearcherByMetadata; import org.dspace.authorize.AuthorizeConfiguration; @@ -62,12 +65,16 @@ import org.dspace.core.LogHelper; import org.dspace.core.exception.SQLRuntimeException; import org.dspace.discovery.DiscoverQuery; +import org.dspace.discovery.DiscoverResult; import org.dspace.discovery.DiscoverResultItemIterator; +import org.dspace.discovery.SearchService; +import org.dspace.discovery.SearchServiceException; import org.dspace.discovery.indexobject.IndexableItem; import org.dspace.discovery.indexobject.IndexableWorkflowItem; import org.dspace.discovery.indexobject.IndexableWorkspaceItem; 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; @@ -117,6 +124,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; @@ -129,6 +138,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; @@ -181,6 +192,8 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl implements It @Autowired(required = true) private ResearcherProfileService researcherProfileService; + @Autowired(required = true) + private RequestItemService requestItemService; @Autowired private VersionHistoryService versionHistoryService; @@ -952,6 +965,8 @@ protected void rawDelete(Context context, Item item) throws AuthorizeException, // 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. @@ -1076,6 +1091,14 @@ private Optional getBusinesIdentifier(Item item) { return Optional.empty(); } + 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(); @@ -1353,6 +1376,53 @@ public boolean canEdit(Context context, Item item) throws SQLException { 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 * 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 b0eb77ec2aa8..000000000000 --- 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 d512637180ad..37e5bb056ff4 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.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 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 void setPublishedBefore(boolean b) { 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 34f02968b33e..f1a8e546ebb1 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.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.MetadataFieldService; @@ -37,6 +39,13 @@ import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; 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; @@ -63,6 +72,7 @@ public class WorkspaceItemServiceImpl implements WorkspaceItemService { protected ItemService itemService; @Autowired(required = true) protected WorkflowService workflowService; + @Autowired private MetadataFieldService metadataFieldService; @Autowired @@ -70,6 +80,10 @@ public class WorkspaceItemServiceImpl implements WorkspaceItemService { @Autowired private TemplateItemValueService templateItemValueService; + @Autowired(required = true) + protected DOIService doiService; + + protected WorkspaceItemServiceImpl() { } @@ -174,6 +188,26 @@ public WorkspaceItem create(Context context, Collection collection, UUID uuid, b } 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", @@ -226,16 +260,6 @@ public WorkspaceItem findByItem(Context context, Item item) throws SQLException 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); @@ -274,10 +298,6 @@ public void deleteAll(Context context, WorkspaceItem workspaceItem) "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); @@ -313,14 +333,6 @@ public void deleteWrapper(Context context, WorkspaceItem workspaceItem) throws S // 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/CustomAuthoritySolrFilter.java b/dspace-api/src/main/java/org/dspace/content/authority/CustomAuthoritySolrFilter.java index 7b63f4f733db..ef8c09d2569e 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/CustomAuthoritySolrFilter.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/CustomAuthoritySolrFilter.java @@ -22,7 +22,6 @@ public interface CustomAuthoritySolrFilter { /** * Get the confidence value for the generated choices - * @param searchTerm The search term string * @return solr query */ public int getConfidenceForChoices(Choice... choices); 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 3928dae3abec..cfd8c53f79ad 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 @@ -152,7 +152,9 @@ private synchronized void init() { } 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/PersonStrictCustomSolrFilterImpl.java b/dspace-api/src/main/java/org/dspace/content/authority/PersonStrictCustomSolrFilterImpl.java index f8332311fe60..9a52db998af9 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/PersonStrictCustomSolrFilterImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/PersonStrictCustomSolrFilterImpl.java @@ -10,6 +10,8 @@ import static org.apache.solr.client.solrj.util.ClientUtils.escapeQueryChars; import static org.dspace.discovery.SolrServiceStrictBestMatchIndexingPlugin.cleanNameWithStrictPolicies; +import java.util.Optional; + import org.dspace.discovery.SolrServiceStrictBestMatchIndexingPlugin; /** @@ -29,8 +31,9 @@ public String getSolrQuery(String searchTerm) { * @return solr query */ public String generateSearchQueryStrictBestMatch(String searchTerm) { - return SolrServiceStrictBestMatchIndexingPlugin.BEST_MATCH_INDEX + ":" - + escapeQueryChars(cleanNameWithStrictPolicies(searchTerm)); + return Optional.ofNullable(cleanNameWithStrictPolicies(searchTerm)) + .map(query -> SolrServiceStrictBestMatchIndexingPlugin.BEST_MATCH_INDEX + ":" + escapeQueryChars(query)) + .orElse(null); } @Override diff --git a/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java b/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java index f03da1896b8b..5d91327b8c8f 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java @@ -188,7 +188,6 @@ public Choices getBestMatch(String fieldKey, String query, int dsoType, Collecti /** * Get the entity type starting from the metadata field. * - * @param field single string identifying metadata field * @return the entity type as a String */ String getLinkedEntityType(String fieldKey); diff --git a/dspace-api/src/main/java/org/dspace/content/authority/service/ItemAuthorityService.java b/dspace-api/src/main/java/org/dspace/content/authority/service/ItemAuthorityService.java index 976fd07e8ccc..6d81c005c4ef 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/service/ItemAuthorityService.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/service/ItemAuthorityService.java @@ -32,7 +32,6 @@ public interface ItemAuthorityService { /** * Get the confidence value for the generated choices - * @param searchTerm The search term string * @return solr query */ public int getConfidenceForChoices(Choice... choices); diff --git a/dspace-api/src/main/java/org/dspace/content/crosswalk/SubscriptionDsoMetadataForEmailCompose.java b/dspace-api/src/main/java/org/dspace/content/crosswalk/SubscriptionDsoMetadataForEmailCompose.java index 0484b0e82ce1..05fda2b97475 100644 --- a/dspace-api/src/main/java/org/dspace/content/crosswalk/SubscriptionDsoMetadataForEmailCompose.java +++ b/dspace-api/src/main/java/org/dspace/content/crosswalk/SubscriptionDsoMetadataForEmailCompose.java @@ -7,15 +7,15 @@ */ package org.dspace.content.crosswalk; -import java.io.IOException; +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.apache.logging.log4j.Logger; -import org.dspace.authorize.AuthorizeException; import org.dspace.content.DSpaceObject; import org.dspace.content.Item; import org.dspace.content.service.ItemService; @@ -24,53 +24,40 @@ 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 at (atis.al) + * @author Alba Aliu */ public class SubscriptionDsoMetadataForEmailCompose implements StreamDisseminationCrosswalk { - /** - * log4j logger - */ - private static Logger log = - org.apache.logging.log4j.LogManager.getLogger(SubscriptionDsoMetadataForEmailCompose.class); private List metadata = new ArrayList<>(); + @Autowired private ItemService itemService; @Override public boolean canDisseminate(Context context, DSpaceObject dso) { - try { - return dso.getType() == Constants.ITEM; - } catch (Exception e) { - log.error("Failed getting mail body", e); - return false; - } + return Objects.nonNull(dso) && dso.getType() == Constants.ITEM; } @Override - public void disseminate(Context context, DSpaceObject dso, OutputStream out) - throws CrosswalkException, IOException, SQLException, AuthorizeException { + 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 (int i = 0; i < metadata.size(); i++) { - String actualMetadata = metadata.get(i); + for (String actualMetadata : metadata) { String[] splitted = actualMetadata.split("\\."); String qualifier = null; if (splitted.length == 1) { qualifier = splitted[2]; } - String metadataValue = itemService.getMetadataFirstValue(item, splitted[0], - splitted[1], qualifier, Item.ANY); + var metadataValue = itemService.getMetadataFirstValue(item, splitted[0], splitted[1], qualifier, ANY); printStream.print(metadataValue + " "); } - String itemURL = HandleServiceFactory.getInstance().getHandleService() - .resolveToURL(context, item.getHandle()); + String itemURL = HandleServiceFactory.getInstance() + .getHandleService() + .resolveToURL(context, item.getHandle()); printStream.print(itemURL); printStream.print("\n"); printStream.close(); 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 4ae8dc620b21..900858b72869 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 List findByEPerson(Context context, EPerson ep, Integer li 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/WorkspaceItemDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/WorkspaceItemDAOImpl.java index de1b9a5aea9e..138451365522 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 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.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 List findAll(Context context, Integer limit, Integer offse 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/edit/service/EditItemModeService.java b/dspace-api/src/main/java/org/dspace/content/edit/service/EditItemModeService.java index 1555300d38a3..915bf9256af7 100644 --- a/dspace-api/src/main/java/org/dspace/content/edit/service/EditItemModeService.java +++ b/dspace-api/src/main/java/org/dspace/content/edit/service/EditItemModeService.java @@ -43,7 +43,6 @@ public interface EditItemModeService { /** * Finds an edit mode by item and edit name, returns null if not exists * @param context DSpace context - * @param itemId UUID Item * @param name edit mode name * @return * @throws SQLException diff --git a/dspace-api/src/main/java/org/dspace/content/enhancer/impl/RelatedEntityItemEnhancer.java b/dspace-api/src/main/java/org/dspace/content/enhancer/impl/RelatedEntityItemEnhancer.java index a5d95582e41d..a6c97cc84e65 100644 --- a/dspace-api/src/main/java/org/dspace/content/enhancer/impl/RelatedEntityItemEnhancer.java +++ b/dspace-api/src/main/java/org/dspace/content/enhancer/impl/RelatedEntityItemEnhancer.java @@ -78,7 +78,7 @@ private void cleanObsoleteVirtualFields(Context context, Item item) throws SQLEx } private void updateVirtualFieldsPlaces(Context context, Item item) { - List virtualSourceFields = getMetadataValues(item, getVirtualSourceMetadataField()); + List virtualSourceFields = getVirtualSourceFields(item); for (MetadataValue virtualSourceField : virtualSourceFields) { metadataWithPlaceToUpdate(item, virtualSourceField) .ifPresent(updatePlaces(item, virtualSourceField)); @@ -113,9 +113,9 @@ private List getObsoleteVirtualFields(Item item) { List obsoleteVirtualFields = new ArrayList<>(); - List virtualSourceFields = getMetadataValues(item, getVirtualSourceMetadataField()); + List virtualSourceFields = getVirtualSourceFields(item); for (MetadataValue virtualSourceField : virtualSourceFields) { - if (isRelatedSourceNoMorePresent(item, virtualSourceField)) { + if (!isPlaceholder(virtualSourceField) && isRelatedSourceNoMorePresent(item, virtualSourceField)) { obsoleteVirtualFields.add(virtualSourceField); getRelatedVirtualField(item, virtualSourceField).ifPresent(obsoleteVirtualFields::add); } @@ -131,7 +131,7 @@ private boolean isRelatedSourceNoMorePresent(Item item, MetadataValue virtualSou } private Optional getRelatedVirtualField(Item item, MetadataValue virtualSourceField) { - return getMetadataValues(item, getVirtualMetadataField()).stream() + return getVirtualFields(item).stream() .filter(metadataValue -> metadataValue.getPlace() == virtualSourceField.getPlace()) .findFirst(); } @@ -141,6 +141,7 @@ private void performEnhancement(Context context, Item item) throws SQLException if (noEnhanceableMetadata(context, item)) { return; } + for (MetadataValue metadataValue : getEnhanceableMetadataValue(item)) { if (wasValueAlreadyUsedForEnhancement(item, metadataValue)) { @@ -191,9 +192,19 @@ private List getEnhanceableMetadataValue(Item item) { } private boolean wasValueAlreadyUsedForEnhancement(Item item, MetadataValue metadataValue) { - return getMetadataValues(item, getVirtualSourceMetadataField()).stream() + + if (isPlaceholderAtPlace(getVirtualFields(item), metadataValue.getPlace())) { + return true; + } + + return getVirtualSourceFields(item).stream() .anyMatch(virtualSourceField -> virtualSourceField.getPlace() == metadataValue.getPlace() && hasAuthorityEqualsTo(metadataValue, virtualSourceField.getValue())); + + } + + private boolean isPlaceholderAtPlace(List metadataValues, int place) { + return place < metadataValues.size() ? isPlaceholder(metadataValues.get(place)) : false; } private boolean hasAuthorityEqualsTo(MetadataValue metadataValue, String authority) { @@ -209,10 +220,22 @@ private Item findRelatedEntityItem(Context context, MetadataValue metadataValue) } } + private boolean isPlaceholder(MetadataValue metadataValue) { + return PLACEHOLDER_PARENT_METADATA_VALUE.equals(metadataValue.getValue()); + } + private List getMetadataValues(Item item, String metadataField) { return itemService.getMetadataByMetadataString(item, metadataField); } + private List getVirtualSourceFields(Item item) { + return getMetadataValues(item, getVirtualSourceMetadataField()); + } + + private List getVirtualFields(Item item) { + return getMetadataValues(item, getVirtualMetadataField()); + } + private void addVirtualField(Context context, Item item, String value) throws SQLException { itemService.addMetadata(context, item, VIRTUAL_METADATA_SCHEMA, VIRTUAL_METADATA_ELEMENT, getVirtualQualifier(), null, value); 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 2335af007ff2..0b06b34038e1 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,7 +31,6 @@ 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; @@ -72,8 +71,6 @@ public abstract class ContentServiceFactory { public abstract InstallItemService getInstallItemService(); - public abstract SupervisedItemService getSupervisedItemService(); - public abstract SiteService getSiteService(); public abstract SubscribeService getSubscribeService(); @@ -117,11 +114,7 @@ public InProgressSubmissionService getInProgressSubmissionService(InProgressSubm } 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 2a13243e9884..564ec052bcb2 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,7 +28,6 @@ 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; @@ -69,8 +68,6 @@ 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; @@ -150,15 +147,11 @@ public InstallItemService getInstallItemService() { return installItemService; } - @Override - public SupervisedItemService getSupervisedItemService() { - return supervisedItemService; - } - @Override public SiteService getSiteService() { return siteService; } + @Override public SubscribeService getSubscribeService() { return subscribeService ; 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 e0f88a7a1b72..e17f9bb7c761 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 @@ * 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 void setStatement(LogicalStatement statement) { 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 e156529651f8..70fce036e6cd 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 @@ 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 @@ -23,7 +24,7 @@ * @version $Revision$ * @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 @@ -31,5 +32,13 @@ public interface Filter extends LogicalStatement { * @return boolean * @throws LogicalStatementException */ + @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 000000000000..a878d69e6ed8 --- /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 38c1e298ee02..004a072eb91a 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 @@ * 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 5478a8acdb2e..9898a06b7ade 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 @@ * 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 b78de7f1902b..bf218eaa8a0f 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 @@ * 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 000000000000..d7d3022e5106 --- /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 6cd4f0f910d7..04f7db336dc4 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 @@ -24,7 +24,6 @@ * 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 dcb57c389ee9..65e26747584a 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 @@ * 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 c097263b030e..9c19a0b82dcd 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 @@ * 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 195dd7064fc1..c2a9258bd895 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 @@ * if the item belongs to any of them. * * @author Kim Shepherd - * @version $Revision$ */ public class InCollectionCondition extends AbstractCondition { 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 cebab799ba16..3be8aeed56c8 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 @@ * 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 000000000000..1ae36db08078 --- /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 b696c8484885..16f130d43832 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 @@ * 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 992d332345d8..6b7db5c1a960 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 @@ * 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 a5d97c615f13..b58a8c277103 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 @@ * 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 69699ebfaf1f..09107e8e55dc 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 @@ * 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 826eb99a8870..f42ca4cf6139 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 @@ * 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 cdc4b8b86dbe..25f4162893b4 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 @@ * 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 8d1cec727e8b..d95a20e347e4 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 @@ * 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 8d6a076c306d..476ae2c93921 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 @@ * 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 7943f094104d..ac4792dc8da0 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 @@ * 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/security/CrisSecurityServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/security/CrisSecurityServiceImpl.java index 572cf1eec26c..4a8b2c313846 100644 --- a/dspace-api/src/main/java/org/dspace/content/security/CrisSecurityServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/security/CrisSecurityServiceImpl.java @@ -176,16 +176,6 @@ private boolean hasAccessByGroup(Context context, EPerson user, List gro return false; } - try { - for (Group group : context.getSpecialGroups()) { - if (groupService.isMember(context, user, group)) { - return true; - } - } - } catch (SQLException e) { - throw new SQLRuntimeException(e.getMessage(), e); - } - List userGroups = user.getGroups(); if (CollectionUtils.isEmpty(userGroups)) { return false; @@ -194,7 +184,23 @@ private boolean hasAccessByGroup(Context context, EPerson user, List gro return groups.stream() .map(group -> findGroupByNameOrUUID(context, group)) .filter(group -> Objects.nonNull(group)) - .anyMatch(group -> userGroups.contains(group)); + .anyMatch(group -> userGroups.contains(group) || isSpecialGroup(context, group)); + } + + private boolean isSpecialGroup(Context context, Group group) { + return findInSpecialGroups(context, group) != null; + } + + private Group findInSpecialGroups(Context context, Group group) { + try { + return context.getSpecialGroups() + .stream() + .filter(specialGroup -> specialGroup != null && specialGroup.equals(group)) + .findFirst() + .orElse(null); + } catch (SQLException e) { + throw new SQLRuntimeException(e.getMessage(), e); + } } private Group findGroupByNameOrUUID(Context context, String group) { diff --git a/dspace-api/src/main/java/org/dspace/content/security/service/MetadataSecurityService.java b/dspace-api/src/main/java/org/dspace/content/security/service/MetadataSecurityService.java index fbcf48b44825..7b03f6464032 100644 --- a/dspace-api/src/main/java/org/dspace/content/security/service/MetadataSecurityService.java +++ b/dspace-api/src/main/java/org/dspace/content/security/service/MetadataSecurityService.java @@ -92,8 +92,6 @@ List getPermissionFilteredMetadataValues(Context context, Item it * @param context the DSpace Context * @param item the item * @param metadataField the metadata field - * @param preventBoxSecurityCheck true if the box security check must be - * skipped, false otherwise * @return true if the metadata field is visible, false * otherwise */ 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 ba9b665a5a0b..6ce376908bf7 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 @@ 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 9b15b640c935..e6823690743d 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 @@ -29,6 +29,7 @@ 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; @@ -753,6 +754,27 @@ public Iterator findByLastModifiedSince(Context context, Date last) */ 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 * @@ -815,8 +837,6 @@ public List getMetadata(Item item, String schema, String element, String lang, boolean enableVirtualMetadata); /** -<<<<<<< HEAD -======= * Returns the item's entity type, if any. * * @param item the item @@ -833,7 +853,6 @@ public List getMetadata(Item item, String schema, String element, public void setEntityType(Context context, Item item, String entityType); /** ->>>>>>> 4science-bitbucket/dspace-cris-7 * Find all the items in the archive or not with a given authority key value in LIKE format. * * @param context DSpace context object diff --git a/dspace-api/src/main/java/org/dspace/content/service/RelationshipService.java b/dspace-api/src/main/java/org/dspace/content/service/RelationshipService.java index 21bfc71198b0..6afe9f6b488d 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/RelationshipService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/RelationshipService.java @@ -511,7 +511,6 @@ int countByTypeName(Context context, String typeName) * by this relationship to the left and/or right item * @param context The relevant DSpace context * @param relationship The relationship to be deleted - * @param forceBypassValidation A boolean indicating whether we should force by-pass validation */ void delete(Context context, Relationship relationship, boolean bypassValidation) throws SQLException, AuthorizeException; 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 883e0f9fd2fb..000000000000 --- 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 8f572f6108ac..c8df68e43498 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 List findByCollection(Context context, Collection collecti public WorkspaceItem findByItem(Context context, Item item) throws SQLException; - public List 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 3255f13b70e1..67567ce97f6f 100644 --- a/dspace-api/src/main/java/org/dspace/core/Email.java +++ b/dspace-api/src/main/java/org/dspace/core/Email.java @@ -398,7 +398,7 @@ public void send() throws MessagingException, IOException { for (String headerName : templateHeaders) { 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 4e777d70a8b4..0765d7b000d1 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 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 @@ private String register(Item item) { 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 52dcce03abf8..e11edda0edd6 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; @@ -165,6 +167,9 @@ public List getFacetResult(DiscoverySearchFilterFacet field) { 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/IndexingUtils.java b/dspace-api/src/main/java/org/dspace/discovery/IndexingUtils.java new file mode 100644 index 000000000000..8dd02f5d44e0 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/discovery/IndexingUtils.java @@ -0,0 +1,119 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +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.DSpaceObject; +import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.core.Context; + +/** + * Util methods used by indexing. + * + * @author Koen Pauwels (koen.pauwels at atmire dot com) + */ +public class IndexingUtils { + private IndexingUtils() { + } + + /** + * Retrieve all ancestor communities of a given community, with the first one being the given community and the + * last one being the root. + *

+ * + * @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)) { + 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/SharedWorkspaceSolrIndexPlugin.java b/dspace-api/src/main/java/org/dspace/discovery/SharedWorkspaceSolrIndexPlugin.java index a30edbdf3f16..1e9fd3aa1b4b 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SharedWorkspaceSolrIndexPlugin.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SharedWorkspaceSolrIndexPlugin.java @@ -122,7 +122,7 @@ private void addRead(SolrInputDocument document, Optional subm) { private Optional findOwner(Context context, Item source) throws SQLException { List metadata = - itemService.getMetadata(source, "dspace", "object", "owner", null); + itemService.getMetadata(source, "dspace", "object", "owner", Item.ANY); if (metadata.isEmpty()) { return Optional.empty(); } 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 bdd77ef655bb..bcc169a6df9e 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java @@ -119,6 +119,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 @@ -923,6 +927,9 @@ protected SolrQuery resolveToSolrQuery(Context context, DiscoverQuery discoveryQ //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); if (!facetFieldConfig.fillGaps() && !facetFieldConfig.inverseDirection()) { @@ -1459,7 +1466,31 @@ public String toSortFieldIndex(String metadataField, String type) { } } + /** + * Gets the solr field that contains the facet value split on each word break to the end, so can be searched + * on each word in the value, see {@link org.dspace.discovery.indexobject.ItemIndexFactoryImpl + * #saveFacetPrefixParts(SolrInputDocument, DiscoverySearchFilter, String, String)} + * Ony applicable to facets of type {@link DiscoveryConfigurationParameters.TYPE_TEXT}, otherwise uses the regular + * 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")); @@ -1511,7 +1542,7 @@ protected String transformDisplayedValue(Context context, String field, String v 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"); @@ -1543,7 +1574,7 @@ protected String transformAuthorityValue(Context context, String field, String v 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 00b70f93d50e..ee93f954a5bd 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 void additionalIndex(Context context, IndexableObject idxObj, SolrInputDo 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 void additionalIndex(Context context, IndexableObject idxObj, SolrInputDo } } } - -} \ 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 000000000000..09308be75920 --- /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 aa6f0a8c971c..7be7ecff450d 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; @@ -269,9 +271,9 @@ public void additionalIndex(Context context, IndexableObject indexableObject, So } } } - 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() + "_authority_filter", facet); diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceStrictBestMatchIndexingPlugin.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceStrictBestMatchIndexingPlugin.java index 3ee84ce63642..d3154470a6af 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceStrictBestMatchIndexingPlugin.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceStrictBestMatchIndexingPlugin.java @@ -7,11 +7,11 @@ */ package org.dspace.discovery; -import static java.util.stream.Collectors.toSet; - import java.util.Collection; import java.util.HashSet; +import java.util.Objects; import java.util.Set; +import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; import org.apache.solr.common.SolrInputDocument; @@ -67,16 +67,19 @@ public Set getPossibleBestMatchValues(String firstName, String lastName, } public Set getPossibleBestMatchValues(Collection fullnames) { - - Set nameSet = new HashSet(); - // add all possible matches to the solr index - nameSet.addAll(fullnames.stream().map(SolrServiceStrictBestMatchIndexingPlugin::cleanNameWithStrictPolicies) - .collect(toSet())); - - return nameSet; + return fullnames + .stream() + .filter(Objects::nonNull) + // add all possible matches to the solr index + .map(SolrServiceStrictBestMatchIndexingPlugin::cleanNameWithStrictPolicies) + .collect(Collectors.toSet()); } public static String cleanNameWithStrictPolicies(String name) { + if (name == null) { + return null; + } + if (configurationService.getBooleanProperty(EXCLUDE_LETTER_CASE_CONFIG, true)) { name = name.toLowerCase(); } @@ -110,6 +113,7 @@ private static Set generateBaseNameSet(String firstName, String lastName return baseNameSet; } + @Override protected void addIndexValue(SolrInputDocument document, String value) { document.addField(BEST_MATCH_INDEX, value); } 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 000000000000..116b5ec88d1b --- /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/SolrServiceValuePairsIndexPlugin.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceValuePairsIndexPlugin.java index f6ccbabf0c6c..213d6547d958 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceValuePairsIndexPlugin.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceValuePairsIndexPlugin.java @@ -10,6 +10,7 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.dspace.discovery.SearchUtils.AUTHORITY_SEPARATOR; import static org.dspace.discovery.SearchUtils.FILTER_SEPARATOR; +import static org.dspace.discovery.SolrServiceImpl.SOLR_FIELD_SUFFIX_FACET_PREFIXES; import java.sql.SQLException; import java.util.List; @@ -127,10 +128,12 @@ private void addDiscoveryFieldFields(String language, SolrInputDocument document String keywordField = appendAuthorityIfNotBlank(value, authority); String acidField = appendAuthorityIfNotBlank(valueLowerCase + separator + value, authority); String filterField = appendAuthorityIfNotBlank(valueLowerCase + separator + value, authority); + String prefixField = appendAuthorityIfNotBlank(valueLowerCase + separator + value, authority); document.addField(fieldNameWithLanguage + "_keyword", keywordField); document.addField(fieldNameWithLanguage + "_acid", acidField); document.addField(fieldNameWithLanguage + "_filter", filterField); + document.addField(fieldNameWithLanguage + SOLR_FIELD_SUFFIX_FACET_PREFIXES, prefixField); document.addField(fieldNameWithLanguage + "_ac", valueLowerCase + separator + value); if (document.containsKey(searchFilter.getIndexFieldName() + "_authority")) { document.addField(fieldNameWithLanguage + "_authority", authority); 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 fd05be1cb521..161849475651 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 void additionalSearchParameters( ); 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 feaedf2cb5b8..8e9c1a77aeb5 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 @@ -169,4 +169,5 @@ public List getDiscoveryConfigurationWithPrefixName(fina } 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 index d069b9ce1d63..6c24a6bac671 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/configuration/DiscoveryRelatedItemConfiguration.java +++ b/dspace-api/src/main/java/org/dspace/discovery/configuration/DiscoveryRelatedItemConfiguration.java @@ -10,7 +10,7 @@ /** * This class extends {@link DiscoveryConfiguration} and add method for set parameters * to filter query list - * @author Danilo Di Nuzzo (danilo.dinuzzo at 4science.it) * + * @author Danilo Di Nuzzo (danilo.dinuzzo at 4science.it) */ -public class DiscoveryRelatedItemConfiguration extends DiscoveryConfiguration {} +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 index 074c3d40f9eb..7fb020cd560b 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/configuration/DiscoverySortFunctionConfiguration.java +++ b/dspace-api/src/main/java/org/dspace/discovery/configuration/DiscoverySortFunctionConfiguration.java @@ -58,9 +58,9 @@ public void setId(final String id) { * @return */ public String getFunction(final Serializable... functionArgs) { - final String args = String.join(",", - Optional.ofNullable(arguments).orElse(Collections.emptyList())); + final String args = String.join(",", Optional.ofNullable(arguments).orElse(Collections.emptyList())); final String result = function + "(" + args + ")"; - return MessageFormat.format(result, functionArgs); + 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 d0b0f363e64b..8a24b997ffae 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.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 void storeInprogressItemFields(Context context, SolrInputDocument doc, 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 void storeInprogressItemFields(Context context, SolrInputDocument doc, 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 464c82d8e25f..82247bb972d2 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.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; @@ -496,7 +500,7 @@ public void addDiscoveryFields(SolrInputDocument doc, Context context, Item item + var); } } - + // if searchFilter is of type "facet", delegate to indexIfFilterTypeFacet method if (searchFilter.getFilterType().equals(DiscoverySearchFilterFacet.FILTER_TYPE_FACET)) { if (searchFilter.getType().equals(DiscoveryConfigurationParameters.TYPE_TEXT)) { //Add a special filter @@ -622,6 +626,8 @@ public void addDiscoveryFields(SolrInputDocument doc, Context context, Item item facetValue.toLowerCase() + separator + facetValue); } } + indexIfFilterTypeFacet(doc, searchFilter, value, date, + authority, preferedLabel, separator); } } } @@ -822,4 +828,140 @@ public Optional findIndexableObject(Context context, String id) t final Item item = itemService.find(context, UUID.fromString(id)); return item == null ? Optional.empty() : Optional.of(new IndexableItem(item)); } + + /** + * Handles indexing when discoverySearchFilter is of type facet. + * + * @param doc the solr document + * @param searchFilter the discoverySearchFilter + * @param value the metadata value + * @param date Date object + * @param authority the authority key + * @param preferedLabel the preferred label for metadata field + * @param separator the separator being used to separate lowercase and regular case + */ + private void indexIfFilterTypeFacet(SolrInputDocument doc, DiscoverySearchFilter searchFilter, String value, + Date date, String authority, String preferedLabel, String separator) { + if (searchFilter.getType().equals(DiscoveryConfigurationParameters.TYPE_TEXT)) { + //Add a special filter + //We use a separator to split up the lowercase and regular case, this is needed to + // get our filters in regular case + //Solr has issues with facet prefix and cases + if (authority != null) { + String facetValue = preferedLabel != null ? preferedLabel : value; + doc.addField(searchFilter.getIndexFieldName() + "_filter", facetValue + .toLowerCase() + separator + facetValue + SearchUtils.AUTHORITY_SEPARATOR + + authority); + } else { + doc.addField(searchFilter.getIndexFieldName() + "_filter", + value.toLowerCase() + separator + value); + } + //Also add prefix field with all parts of value + saveFacetPrefixParts(doc, searchFilter, value, separator, authority, preferedLabel); + } else if (searchFilter.getType().equals(DiscoveryConfigurationParameters.TYPE_DATE)) { + if (date != null) { + String indexField = searchFilter.getIndexFieldName() + ".year"; + String yearUTC = DateFormatUtils.formatUTC(date, "yyyy"); + doc.addField(searchFilter.getIndexFieldName() + "_keyword", yearUTC); + // add the year to the autocomplete index + doc.addField(searchFilter.getIndexFieldName() + "_ac", yearUTC); + doc.addField(indexField, yearUTC); + + if (yearUTC.startsWith("0")) { + doc.addField( + searchFilter.getIndexFieldName() + + "_keyword", + yearUTC.replaceFirst("0*", "")); + // add date without starting zeros for autocomplete e filtering + doc.addField( + searchFilter.getIndexFieldName() + + "_ac", + yearUTC.replaceFirst("0*", "")); + doc.addField( + searchFilter.getIndexFieldName() + + "_ac", + value.replaceFirst("0*", "")); + doc.addField( + searchFilter.getIndexFieldName() + + "_keyword", + value.replaceFirst("0*", "")); + } + + //Also save a sort value of this year, this is required for determining the upper + // & lower bound year of our facet + if (doc.getField(indexField + "_sort") == null) { + //We can only add one year so take the first one + doc.addField(indexField + "_sort", yearUTC); + } + } + } else if (searchFilter.getType() + .equals(DiscoveryConfigurationParameters.TYPE_HIERARCHICAL)) { + HierarchicalSidebarFacetConfiguration hierarchicalSidebarFacetConfiguration = + (HierarchicalSidebarFacetConfiguration) searchFilter; + String[] subValues = value.split(hierarchicalSidebarFacetConfiguration.getSplitter()); + if (hierarchicalSidebarFacetConfiguration + .isSkipFirstNodeLevel() && 1 < subValues.length) { + //Remove the first element of our array + subValues = (String[]) ArrayUtils.subarray(subValues, 1, subValues.length); + } + for (int i = 0; i < subValues.length; i++) { + StringBuilder valueBuilder = new StringBuilder(); + for (int j = 0; j <= i; j++) { + valueBuilder.append(subValues[j]); + if (j < i) { + valueBuilder.append(hierarchicalSidebarFacetConfiguration.getSplitter()); + } + } + + String indexValue = valueBuilder.toString().trim(); + doc.addField(searchFilter.getIndexFieldName() + "_tax_" + i + "_filter", + indexValue.toLowerCase() + separator + indexValue); + //We add the field x times that it has occurred + for (int j = i; j < subValues.length; j++) { + doc.addField(searchFilter.getIndexFieldName() + "_filter", + indexValue.toLowerCase() + separator + indexValue); + doc.addField(searchFilter.getIndexFieldName() + "_keyword", indexValue); + } + } + //Also add prefix field with all parts of value + saveFacetPrefixParts(doc, searchFilter, value, separator, authority, preferedLabel); + } + } + + /** + * Stores every "value part" in lowercase, together with the original value in regular case, + * separated by the separator, in the {fieldName}{@link SolrServiceImpl.SOLR_FIELD_SUFFIX_FACET_PREFIXES} field. + *
+ * 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/eperson/AccountServiceImpl.java b/dspace-api/src/main/java/org/dspace/eperson/AccountServiceImpl.java index c54ffa629362..283f101f2ba5 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/AccountServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/AccountServiceImpl.java @@ -18,6 +18,7 @@ import org.apache.commons.collections4.CollectionUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.dspace.authenticate.service.AuthenticationService; import org.dspace.authorize.AuthorizeException; import org.dspace.core.Context; import org.dspace.core.Email; @@ -59,6 +60,9 @@ public class AccountServiceImpl implements AccountService { @Autowired private GroupService groupService; + @Autowired + private AuthenticationService authenticationService; + protected AccountServiceImpl() { } @@ -87,6 +91,9 @@ public void sendRegistrationInfo(Context context, String email, List group 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, groups, 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 000000000000..72822fb8716e --- /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 15b0cede7382..8fe4f94f9647 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 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; @@ -219,10 +215,6 @@ public Integer getLegacyId() { 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 389c59f25ab4..9fda372b4f2a 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java @@ -489,9 +489,6 @@ public void delete(Context context, Group group) throws SQLException { 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 index 42841ea36b9f..dd0bd8287bd3 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/SubscribeCLITool.java +++ b/dspace-api/src/main/java/org/dspace/eperson/SubscribeCLITool.java @@ -104,12 +104,12 @@ public static void processDaily(Context context, boolean test) throws SQLExcepti // Go through the list collating subscriptions for each e-person for (Subscription subscription : subscriptions) { - if (!(subscription.getdSpaceObject() != null && subscription.getdSpaceObject() instanceof Collection)) { + if (!(subscription.getDSpaceObject() != null && subscription.getDSpaceObject() instanceof Collection)) { continue; } // Does this row relate to the same e-person as the last? if ((currentEPerson == null) - || (!subscription.getePerson().getID().equals(currentEPerson + || (!subscription.getEPerson().getID().equals(currentEPerson .getID()))) { // New e-person. Send mail for previous e-person if (currentEPerson != null) { @@ -123,11 +123,11 @@ public static void processDaily(Context context, boolean test) throws SQLExcepti } } - currentEPerson = subscription.getePerson(); + currentEPerson = subscription.getEPerson(); collections = new ArrayList<>(); } - collections.add((Collection) subscription.getdSpaceObject()); + collections.add((Collection) subscription.getDSpaceObject()); } // Process the last person 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 ea7317e5e071..2e4d94f4431e 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,15 @@ 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; @@ -30,33 +34,28 @@ * @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, String resourceType, - Integer limit, Integer offset) throws Exception { - if (resourceType == null) { + public List findAll(Context context, String resourceType, Integer limit, Integer offset) + throws Exception { + if (StringUtils.isBlank(resourceType)) { return subscriptionDAO.findAllOrderedByDSO(context, limit, offset); } else { - if (resourceType.equals("Item") || resourceType.equals("Collection") || resourceType.equals("Community")) { + 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 Item, Collection or Community"); - throw new Exception("Resource type must be Item, Collection or Community"); + log.error("Resource type must be Collection or Community"); + throw new Exception("Resource type must be Collection or Community"); } } } @@ -73,19 +72,18 @@ public Subscription subscribe(Context context, EPerson eperson, Subscription newSubscription = subscriptionDAO.create(context, new Subscription()); subscriptionParameterList.forEach(subscriptionParameter -> newSubscription.addParameter(subscriptionParameter)); - newSubscription.setePerson(eperson); - newSubscription.setdSpaceObject(dSpaceObject); - newSubscription.setType(type); + newSubscription.setEPerson(eperson); + newSubscription.setDSpaceObject(dSpaceObject); + newSubscription.setSubscriptionType(type); return newSubscription; } else { - throw new AuthorizeException( - "Only admin or e-person themselves can subscribe"); + throw new AuthorizeException("Only admin or e-person themselves can subscribe"); } } @Override - public void unsubscribe(Context context, EPerson eperson, - DSpaceObject dSpaceObject) throws SQLException, AuthorizeException { + 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 @@ -101,45 +99,38 @@ public void unsubscribe(Context context, EPerson eperson, + 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 getSubscriptionsByEPerson(Context context, EPerson eperson, Integer limit, Integer offset) + public List findSubscriptionsByEPerson(Context context, EPerson eperson, Integer limit,Integer offset) throws SQLException { return subscriptionDAO.findByEPerson(context, eperson, limit, offset); } @Override - public List getSubscriptionsByEPersonAndDso(Context context, - EPerson eperson, DSpaceObject dSpaceObject, - Integer limit, Integer offset) - throws SQLException { + 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) - throws SQLException { - return getAvailableSubscriptions(context, null); + public List findAvailableSubscriptions(Context context) throws SQLException { + return findAvailableSubscriptions(context, null); } @Override - public List getAvailableSubscriptions(Context context, EPerson eperson) - throws SQLException { - List collections; - if (eperson != null) { + 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, - DSpaceObject dSpaceObject) throws SQLException { + public boolean isSubscribed(Context context, EPerson eperson, DSpaceObject dSpaceObject) throws SQLException { return subscriptionDAO.findByEPersonAndDso(context, eperson, dSpaceObject, -1, -1) != null; } @@ -154,94 +145,50 @@ public void deleteByEPerson(Context context, EPerson ePerson) throws SQLExceptio } @Override - public Subscription findById(Context context, int id) throws SQLException, AuthorizeException { - Subscription subscription = subscriptionDAO.findByID(context, Subscription.class, id); - if (context.getCurrentUser().equals(subscription.getePerson()) || - authorizeService.isAdmin(context, context.getCurrentUser())) { - return subscription; - } - throw new AuthorizeException("Only admin or e-person themselves can edit the subscription"); + public Subscription findById(Context context, int id) throws SQLException { + return subscriptionDAO.findByID(context, Subscription.class, id); } @Override - public Subscription updateSubscription(Context context, Integer id, - EPerson eperson, - DSpaceObject dSpaceObject, - List subscriptionParameterList, - String type) throws SQLException, AuthorizeException { - // must be admin or the subscriber of the subscription - if (authorizeService.isAdmin(context, context.getCurrentUser()) || eperson.equals(context.getCurrentUser())) { - Subscription subscriptionDB = subscriptionDAO.findByID(context, Subscription.class, id); - subscriptionDB.removeParameterList(); - subscriptionDB.setType(type); - subscriptionDB.setdSpaceObject(dSpaceObject); - subscriptionParameterList.forEach(subscriptionParameter -> - subscriptionDB.addParameter(subscriptionParameter)); - subscriptionDB.setePerson(eperson); - subscriptionDAO.save(context, subscriptionDB); - return subscriptionDB; - } else { - throw new AuthorizeException("Only admin or e-person themselves can edit the subscription"); - } + 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 subscriptionParameter) throws SQLException, AuthorizeException { - // must be admin or the subscriber of the subscription + public Subscription addSubscriptionParameter(Context context, Integer id, SubscriptionParameter subscriptionParam) + throws SQLException { Subscription subscriptionDB = subscriptionDAO.findByID(context, Subscription.class, id); - if (authorizeService.isAdmin(context, context.getCurrentUser()) - || subscriptionDB.getePerson().equals(context.getCurrentUser())) { - subscriptionDB.addParameter(subscriptionParameter); - subscriptionDAO.save(context, subscriptionDB); - return subscriptionDB; - } else { - throw new AuthorizeException("Only admin or e-person themselves can edit the subscription"); - } + subscriptionDB.addParameter(subscriptionParam); + subscriptionDAO.save(context, subscriptionDB); + return subscriptionDB; } @Override - public Subscription removeSubscriptionParameter(Context context, Integer id, - SubscriptionParameter subscriptionParameter) throws SQLException, AuthorizeException { - // must be admin or the subscriber of the subscription + public Subscription removeSubscriptionParameter(Context context,Integer id, SubscriptionParameter subscriptionParam) + throws SQLException { Subscription subscriptionDB = subscriptionDAO.findByID(context, Subscription.class, id); - if (authorizeService.isAdmin(context, context.getCurrentUser()) - || subscriptionDB.getePerson().equals(context.getCurrentUser())) { - subscriptionDB.removeParameter(subscriptionParameter); - subscriptionDAO.save(context, subscriptionDB); - return subscriptionDB; - } else { - throw new AuthorizeException("Only admin or e-person themselves can edit the subscription"); - } + subscriptionDB.removeParameter(subscriptionParam); + subscriptionDAO.save(context, subscriptionDB); + return subscriptionDB; } @Override - public void deleteSubscription(Context context, Integer id) throws SQLException, AuthorizeException { - // initially find the eperson associated with the subscription - Subscription subscription = subscriptionDAO.findByID(context, Subscription.class, id); - if (subscription != null) { - // must be admin or the subscriber of the subscription - if (authorizeService.isAdmin(context, context.getCurrentUser()) - || subscription.getePerson().equals(context.getCurrentUser())) { - try { - subscriptionDAO.delete(context, subscription); - } catch (SQLException sqlException) { - throw new SQLException(sqlException); - } - - } else { - throw new AuthorizeException("Only admin or e-person themselves can delete the subscription"); - } - } else { - throw new IllegalArgumentException("Subscription with id " + id + " is not found"); - } - + public void deleteSubscription(Context context, Subscription subscription) throws SQLException { + subscriptionDAO.delete(context, subscription); } @Override - public List findAllSubscriptionsByTypeAndFrequency(Context context, - String type, String frequencyValue) throws SQLException { - return subscriptionDAO.findAllSubscriptionsByTypeAndFrequency(context, type, frequencyValue); + public List findAllSubscriptionsBySubscriptionTypeAndFrequency(Context context, + String subscriptionType, String frequencyValue) throws SQLException { + return subscriptionDAO.findAllSubscriptionsBySubscriptionTypeAndFrequency(context, subscriptionType, + frequencyValue); } @Override @@ -250,13 +197,14 @@ public Long countAll(Context context) throws SQLException { } @Override - public Long countAllByEPerson(Context context, EPerson ePerson) throws SQLException { + public Long countSubscriptionsByEPerson(Context context, EPerson ePerson) throws SQLException { return subscriptionDAO.countAllByEPerson(context, ePerson); } @Override - public Long countAllByEPersonAndDSO(Context context, - EPerson ePerson, DSpaceObject dSpaceObject) throws SQLException { + 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 6f38dce058c4..5db63740f477 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/Subscription.java +++ b/dspace-api/src/main/java/org/dspace/eperson/Subscription.java @@ -26,8 +26,6 @@ import org.dspace.core.Context; import org.dspace.core.ReloadableEntity; - - /** * Database entity representation of the subscription table * @@ -43,7 +41,7 @@ public class Subscription implements ReloadableEntity { @SequenceGenerator(name = "subscription_seq", sequenceName = "subscription_seq", allocationSize = 1) private Integer id; - @ManyToOne(fetch = FetchType.LAZY) + @ManyToOne(fetch = FetchType.EAGER) @JoinColumn(name = "dspace_object_id") private DSpaceObject dSpaceObject; @@ -51,8 +49,13 @@ public class Subscription implements ReloadableEntity { @JoinColumn(name = "eperson_id") private EPerson ePerson; + /** + * Represent subscription type, for example, "content" or "statistics". + * + * NOTE: Currently, in DSpace we use only one "content" + */ @Column(name = "type") - private String type; + private String subscriptionType; @OneToMany(fetch = FetchType.LAZY, mappedBy = "subscription", cascade = CascadeType.ALL, orphanRemoval = true) private List subscriptionParameterList = new ArrayList<>(); @@ -61,15 +64,14 @@ public class Subscription implements ReloadableEntity { * Protected constructor, create object using: * {@link org.dspace.eperson.service.SubscribeService#subscribe(Context, EPerson, DSpaceObject, List, String)} */ - protected Subscription() { - } + protected Subscription() {} @Override public Integer getID() { return id; } - public DSpaceObject getdSpaceObject() { + public DSpaceObject getDSpaceObject() { return this.dSpaceObject; } @@ -77,24 +79,20 @@ void setDSpaceObject(DSpaceObject dSpaceObject) { this.dSpaceObject = dSpaceObject; } - public EPerson getePerson() { + public EPerson getEPerson() { return ePerson; } - public void setePerson(EPerson ePerson) { + public void setEPerson(EPerson ePerson) { this.ePerson = ePerson; } - public void setdSpaceObject(DSpaceObject dSpaceObject) { - this.dSpaceObject = dSpaceObject; - } - - public String getType() { - return type; + public String getSubscriptionType() { + return subscriptionType; } - public void setType(String type) { - this.type = type; + public void setSubscriptionType(String subscriptionType) { + this.subscriptionType = subscriptionType; } public List getSubscriptionParameterList() { @@ -117,4 +115,4 @@ public void removeParameterList() { 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 index c278f68eb87f..7526535d7fcd 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/SubscriptionParameter.java +++ b/dspace-api/src/main/java/org/dspace/eperson/SubscriptionParameter.java @@ -7,7 +7,6 @@ */ package org.dspace.eperson; - import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; @@ -18,29 +17,50 @@ 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 { +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) + @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; @@ -66,22 +86,13 @@ public void setValue(String value) { this.value = value; } - public SubscriptionParameter(Integer id, Subscription subscription, String name, String value) { - this.id = id; - this.subscription = subscription; - this.name = name; - this.value = value; - } - - public SubscriptionParameter() { - } - - public Integer getId() { + @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 64180a5e2231..000000000000 --- 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 a22ff4596f30..4d762c1775dd 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 @@ -26,32 +26,125 @@ */ public interface SubscriptionDAO extends GenericDAO { + /** + * 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, Integer limit, Integer offset) 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 List findByEPersonAndDso(Context context, - EPerson eperson, DSpaceObject dSpaceObject, + /** + * 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 deleteByDSOAndEPerson(Context context, DSpaceObject dSpaceObject, 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; - public List findAllSubscriptionsByTypeAndFrequency(Context context, - String type, String frequencyValue) 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; - public Long countAllByEPersonAndDso(Context context, EPerson ePerson, - DSpaceObject dSpaceObject) 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; + } 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 index f41be1e50b11..ea9c7b0bbd37 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/dao/SubscriptionParameterDAO.java +++ b/dspace-api/src/main/java/org/dspace/eperson/dao/SubscriptionParameterDAO.java @@ -19,5 +19,4 @@ * @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 cffb0bb1ea6d..6c36211f310c 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 @@ -35,13 +35,14 @@ * @author kevinvandevelde at atmire.com */ public class SubscriptionDAOImpl extends AbstractHibernateDAO implements SubscriptionDAO { + protected SubscriptionDAOImpl() { super(); } @Override - public List findByEPerson(Context context, EPerson eperson, - Integer limit, Integer offset) 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); @@ -51,7 +52,6 @@ public List findByEPerson(Context context, EPerson eperson, orderList.add(criteriaBuilder.asc(subscriptionRoot.get(Subscription_.dSpaceObject))); criteriaQuery.orderBy(orderList); return list(context, criteriaQuery, false, Subscription.class, limit, offset); - } @Override @@ -64,7 +64,7 @@ public List findByEPersonAndDso(Context context, EPerson eperson, Root subscriptionRoot = criteriaQuery.from(Subscription.class); criteriaQuery.select(subscriptionRoot); criteriaQuery.where(criteriaBuilder.and(criteriaBuilder.equal( - subscriptionRoot.get(Subscription_.ePerson), eperson), + subscriptionRoot.get(Subscription_.ePerson), eperson), criteriaBuilder.equal(subscriptionRoot.get(Subscription_.dSpaceObject), dSpaceObject) )); List orderList = new LinkedList<>(); @@ -132,14 +132,16 @@ public List findAllOrderedByDSO(Context context, Integer limit, In } @Override - public List findAllSubscriptionsByTypeAndFrequency(Context context, - String type, String frequencyValue) throws SQLException { + 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_.TYPE), type), + 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) )); @@ -182,4 +184,5 @@ public Long countAllByEPersonAndDso(Context context, 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 index eee80a73a510..37af787ed3a5 100644 --- 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 @@ -18,9 +18,11 @@ * * @author Alba Aliu at atis.al */ -public class SubscriptionParameterDAOImpl extends AbstractHibernateDAO - implements SubscriptionParameterDAO { +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 f7ce13a8a397..b80c37f13ff5 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.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 33d9249b6bfd..c4a6cbe9964c 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.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 SubscribeService getSubscribeService() { 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 466789294b0a..e70f40e0edf0 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 @@ -38,22 +38,26 @@ public interface SubscribeService { * @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, String resourceType, - Integer limit, Integer offset) throws Exception; + 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 - * @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 Subscription subscribe(Context context, EPerson eperson, - DSpaceObject dSpaceObject, - List subscriptionParameterList, - String type) 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 @@ -67,8 +71,8 @@ public Subscription subscribe(Context context, EPerson eperson, * @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, - DSpaceObject dSpaceObject) 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 @@ -80,8 +84,8 @@ public void unsubscribe(Context context, EPerson eperson, * @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 getSubscriptionsByEPerson(Context context, EPerson eperson, - Integer limit, Integer offset) 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 @@ -94,11 +98,9 @@ public List getSubscriptionsByEPerson(Context context, EPerson epe * @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 getSubscriptionsByEPersonAndDso(Context context, - EPerson eperson, + public List findSubscriptionsByEPersonAndDso(Context context, EPerson eperson, DSpaceObject dSpaceObject, - Integer limit, - Integer offset) throws SQLException; + Integer limit, Integer offset) throws SQLException; /** * Find out which collections the currently logged in e-person can subscribe to @@ -107,8 +109,7 @@ public List getSubscriptionsByEPersonAndDso(Context context, * @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 @@ -118,8 +119,7 @@ public List getAvailableSubscriptions(Context context) * @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? @@ -130,8 +130,7 @@ public List getAvailableSubscriptions(Context context, EPerson epers * @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, - DSpaceObject dSpaceObject) throws SQLException; + public boolean isSubscribed(Context context, EPerson eperson, DSpaceObject dSpaceObject) throws SQLException; /** * Delete subscription by collection. @@ -158,23 +157,19 @@ public boolean isSubscribed(Context context, EPerson eperson, * @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, AuthorizeException; + public Subscription findById(Context context, int id) throws SQLException; /** * Updates a subscription by id * * @param context DSpace context * @param id Integer id - * @param eperson EPerson eperson - * @param dSpaceObject DSpaceObject dSpaceObject * @param subscriptionParameterList List subscriptionParameterList - * @param type String type + * @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, EPerson eperson, - DSpaceObject dSpaceObject, - List subscriptionParameterList, - String type) throws SQLException, AuthorizeException; + public Subscription updateSubscription(Context context, Integer id, String subscriptionType, + List subscriptionParameterList) throws SQLException; /** * Adds a parameter to a subscription @@ -184,48 +179,46 @@ public Subscription updateSubscription(Context context, Integer id, EPerson eper * @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, AuthorizeException; + 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 subscriptionParameter SubscriptionParameter subscriptionParameter - * @throws SQLException An exception that provides information on a database access error or other errors. + * @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 subscriptionParameter) throws SQLException, AuthorizeException; + SubscriptionParameter subscriptionParam) throws SQLException; /** * Deletes a subscription * * @param context DSpace context - * @param id Integer id of subscription + * @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, Integer id) throws SQLException, AuthorizeException; + public void deleteSubscription(Context context, Subscription subscription) throws SQLException; /** - * Finds all subscriptions having given type and frequency + * Finds all subscriptions by subscriptionType and frequency * - * @param context DSpace context - * @param type String type of subscription - * @param frequencyValue String frequency value of subscription - * @throws SQLException An exception that provides information on a database access error or other errors. + * @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 findAllSubscriptionsByTypeAndFrequency(Context context, - String type, String frequencyValue) throws SQLException; - + 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, AuthorizeException; + public Long countAll(Context context) throws SQLException; /** * Counts all subscriptions by ePerson @@ -233,8 +226,7 @@ public List findAllSubscriptionsByTypeAndFrequency(Context context * @param context DSpace context * @param ePerson EPerson ePerson */ - public Long countAllByEPerson(Context context, EPerson ePerson) throws SQLException, AuthorizeException; - + public Long countSubscriptionsByEPerson(Context context, EPerson ePerson) throws SQLException; /** * Counts all subscriptions by ePerson and DSO @@ -243,6 +235,6 @@ public List findAllSubscriptionsByTypeAndFrequency(Context context * @param ePerson EPerson ePerson * @param dSpaceObject DSpaceObject dSpaceObject */ - public Long countAllByEPersonAndDSO(Context context, EPerson ePerson, - DSpaceObject dSpaceObject) throws SQLException, AuthorizeException; -} + 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 470c9133e59a..000000000000 --- 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/external/OpenAIRERestConnector.java b/dspace-api/src/main/java/org/dspace/external/OpenAIRERestConnector.java index 5e45d6324d2c..b0aa4aba13a9 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 void setClientSecret(String clientSecret) { /** * 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 8a1475f05af9..21c14813f93d 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 @@ -47,6 +47,14 @@ public String getSourceIdentifier() { return sourceIdentifier; } + public QuerySource getQuerySource() { + return querySource; + } + + public void setQuerySource(QuerySource querySource) { + this.querySource = querySource; + } + /** * This method set the SourceIdentifier for the ExternalDataProvider * @param sourceIdentifier The UNIQUE sourceIdentifier to be set on any LiveImport data provider @@ -57,8 +65,7 @@ public void setSourceIdentifier(String sourceIdentifier) { /** * This method set the MetadataSource for the ExternalDataProvider - * @param querySource {@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; @@ -83,9 +90,8 @@ public void setDisplayMetadata(String displayMetadata) { @Override public Optional getExternalDataObject(String id) { try { - ImportRecord record = querySource.getRecord(id); - ExternalDataObject externalDataObject = getExternalDataObject(record); - return Optional.ofNullable(externalDataObject); + ExternalDataObject externalDataObject = getExternalDataObject(querySource.getRecord(id)); + return Optional.of(externalDataObject); } catch (MetadataSourceException e) { throw new RuntimeException( "The live import provider " + querySource.getImportSource() + " throws an exception", e); @@ -129,8 +135,9 @@ public int getNumberOfResults(String query) { * @return */ private ExternalDataObject getExternalDataObject(ImportRecord record) { - if (record == null || getFirstValue(record, recordIdMetadata) == null) { - return null; + //return 400 if no record were found + if (record == null) { + throw new IllegalArgumentException("No record found for query or id"); } ExternalDataObject externalDataObject = new ExternalDataObject(sourceIdentifier); String id = getFirstValue(record, recordIdMetadata); @@ -160,7 +167,4 @@ private String getFirstValue(ImportRecord record, String metadata) { return id; } - public QuerySource getQuerySource() { - return 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 4aff1e2b5bc8..6d386395ab33 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 @@ 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 String createHandle(Context context, DSpaceObject dso, @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 DSpaceObject resolveToObject(Context context, String handle) @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 void modifyHandleDSpaceObject(Context context, String handle, DSpaceObjec //////////////////////////////////////// // 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/identifier/DOIIdentifierProvider.java b/dspace-api/src/main/java/org/dspace/identifier/DOIIdentifierProvider.java index 66e7b94a4bd4..b70eda960d35 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.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.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 @@ *

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 void setDOIConnector(DOIConnector connector) { 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 boolean supports(String identifier) { @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 String register(Context context, DSpaceObject dso) @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 String register(Context context, DSpaceObject dso, boolean skipFilter) * @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 void register(Context context, DSpaceObject dso, String identifier, boole // 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 void register(Context context, DSpaceObject dso, String identifier, boole + "is marked as DELETED.", DOIIdentifierException.DOI_IS_DELETED); } - // Check status of DOI if (IS_REGISTERED.equals(doiRow.getStatus())) { return; } @@ -290,6 +308,7 @@ public void register(Context context, DSpaceObject dso, String identifier, boole log.warn("SQLException while changing status of DOI {} to be registered.", doi); throw new RuntimeException(sqle); } + } /** @@ -309,7 +328,7 @@ public void register(Context context, DSpaceObject dso, String identifier, boole @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 void reserve(Context context, DSpaceObject dso, String identifier) * @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 void reserve(Context context, DSpaceObject dso, String identifier, boolea */ 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 void reserveOnline(Context context, DSpaceObject dso, String identifier) * @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 void reserveOnline(Context context, DSpaceObject dso, String identifier, 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 void registerOnline(Context context, DSpaceObject dso, String identifier) * @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 void registerOnline(Context context, DSpaceObject dso, String identifier, } 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 void updateMetadata(Context context, DSpaceObject dso, String identifier) 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 void updateMetadataOnline(Context context, DSpaceObject dso, String ident @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 String mint(Context context, DSpaceObject dso, boolean skipFilter) throws } 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 String getDOIByObject(Context context, DSpaceObject dso) throws SQLExcept */ 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 @@ protected DOI loadOrCreateDOI(Context context, DSpaceObject dso, String doiIdent * @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 @@ protected DOI loadOrCreateDOI(Context context, DSpaceObject dso, String doiIdent // 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 @@ protected DOI loadOrCreateDOI(Context context, DSpaceObject dso, String doiIdent } } - // 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 @@ protected DOI loadOrCreateDOI(Context context, DSpaceObject dso, String doiIdent 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 @@ protected DOI loadOrCreateDOI(Context context, DSpaceObject dso, String doiIdent // 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 @@ protected void removeDOIFromObject(Context context, DSpaceObject dso, String doi /** * 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 void checkMintable(Context context, DSpaceObject dso) throws DOIIdentifie 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 fdc7c1db23f5..c2254fa9a6fd 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 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 @@ */ 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 String register(Context context, DSpaceObject dso, boolean skipF * @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 void register(Context context, DSpaceObject dso, String identifi * @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 void reserve(Context context, DSpaceObject dso, String identifie */ 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 1a43595391ab..412baba6a8da 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 @@ 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.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 void reserve(Context context, DSpaceObject dso, String identifier) @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 void register(Context context, DSpaceObject dso) 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 void register(Context context, DSpaceObject object, String identifier) 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 a864b4be4bf2..e7c786d5f8ce 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.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."); @@ -79,6 +85,9 @@ public String mint(Context context, DSpaceObject dso) + " 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 String mint(Context context, DSpaceObject dso) // 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); @@ -127,7 +136,7 @@ public String mint(Context context, DSpaceObject dso) // if we have a history, we have a item doi = makeIdentifierBasedOnHistory(context, dso, history); } 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); @@ -140,7 +149,12 @@ public String mint(Context context, DSpaceObject dso) } @Override - public void register(Context context, DSpaceObject dso, String identifier) + public void register(Context context, DSpaceObject dso, String identifier) throws IdentifierException { + register(context, dso, identifier, this.filter); + } + + @Override + 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."); @@ -220,8 +234,14 @@ protected String getDOIPostfix(String identifier) 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 +264,9 @@ protected String makeIdentifierBasedOnHistory(Context context, DSpaceObject dso, } 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 +292,7 @@ protected String makeIdentifierBasedOnHistory(Context context, DSpaceObject dso, 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/doi/DOIConsumer.java b/dspace-api/src/main/java/org/dspace/identifier/doi/DOIConsumer.java index 654d275d8725..1961ce82744c 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,73 @@ public void consume(Context ctx, Event event) throws Exception { return; } Item item = (Item) dso; + 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"); + } - 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 + 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; } - - DOIIdentifierProvider provider = new DSpace().getSingletonService( - DOIIdentifierProvider.class); - - String doi = null; + 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); + } + } + ctx.commit(); } } 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 4cc8b77ddcfd..ad885314bb2b 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.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; @@ -65,6 +68,9 @@ public class DOIOrganiser { protected boolean skipFilter; + // This filter will override the default provider filter / behaviour + protected Filter filter; + /** * Constructor to be called within the main() method * @param context - DSpace context @@ -78,7 +84,8 @@ public DOIOrganiser(Context context, DOIIdentifierProvider provider) { 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); } /** @@ -123,12 +130,13 @@ public static void runCLI(Context context, DOIOrganiser organiser, String[] args "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") @@ -205,10 +213,12 @@ public static void runCLI(Context context, DOIOrganiser organiser, String[] args } 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')) { @@ -396,19 +406,18 @@ public void list(String processName, PrintStream out, PrintStream err, Integer . /** * 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: " @@ -468,29 +477,23 @@ public void register(DOI doiRow, boolean skipFilter) throws SQLException, DOIIde } /** - * 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); } /** @@ -499,14 +502,14 @@ public void reserve(DOI doiRow) { * @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."); @@ -701,7 +704,7 @@ public DOI resolveToDOI(String identifier) //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; @@ -725,7 +728,7 @@ public DOI resolveToDOI(String identifier) 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/doi/DataCiteConnector.java b/dspace-api/src/main/java/org/dspace/identifier/doi/DataCiteConnector.java index 57136d6143bb..43882918cd4a 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/doi/DataCiteConnector.java +++ b/dspace-api/src/main/java/org/dspace/identifier/doi/DataCiteConnector.java @@ -8,13 +8,14 @@ package org.dspace.identifier.doi; import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.net.URISyntaxException; import java.sql.SQLException; -import java.util.HashMap; import java.util.Iterator; import java.util.Map; +import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.StatusLine; @@ -35,13 +36,14 @@ import org.apache.http.util.EntityUtils; import org.dspace.authorize.AuthorizeException; import org.dspace.content.DSpaceObject; +import org.dspace.content.Item; import org.dspace.content.crosswalk.CrosswalkException; -import org.dspace.content.crosswalk.DisseminationCrosswalk; -import org.dspace.content.crosswalk.ParameterizedDisseminationCrosswalk; +import org.dspace.content.crosswalk.StreamDisseminationCrosswalk; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.DSpaceObjectService; +import org.dspace.content.service.ItemService; +import org.dspace.core.Constants; import org.dspace.core.Context; -import org.dspace.core.factory.CoreServiceFactory; import org.dspace.handle.service.HandleService; import org.dspace.identifier.DOI; import org.dspace.services.ConfigurationService; @@ -99,18 +101,6 @@ public class DataCiteConnector * dependency injection. */ protected String METADATA_PATH; - /** - * Name of crosswalk to convert metadata into DataCite Metadata Scheme. Set - * by spring dependency injection. - */ - protected String CROSSWALK_NAME; - /** - * DisseminationCrosswalk to map local metadata into DataCite metadata. - * The name of the crosswalk is set by spring dependency injection using - * {@link #setDisseminationCrosswalkName(String) setDisseminationCrosswalkName} which - * instantiates the crosswalk. - */ - protected ParameterizedDisseminationCrosswalk xwalk; protected ConfigurationService configurationService; @@ -119,8 +109,12 @@ public class DataCiteConnector @Autowired protected HandleService handleService; + @Autowired + private ItemService itemService; + + private Map disseminationCrosswalkByEntityType; + public DataCiteConnector() { - this.xwalk = null; this.USERNAME = null; this.PASSWORD = null; } @@ -189,34 +183,6 @@ public void setConfigurationService(ConfigurationService configurationService) { this.configurationService = configurationService; } - /** - * Set the name of the dissemination crosswalk used to convert the metadata - * into DataCite Metadata Schema. Used by spring dependency injection. - * - * @param CROSSWALK_NAME The name of the dissemination crosswalk to use. This - * crosswalk must be configured in dspace.cfg. - */ - @Autowired(required = true) - public void setDisseminationCrosswalkName(String CROSSWALK_NAME) { - this.CROSSWALK_NAME = CROSSWALK_NAME; - } - - protected void prepareXwalk() { - if (null != this.xwalk) { - return; - } - - this.xwalk = (ParameterizedDisseminationCrosswalk) CoreServiceFactory.getInstance().getPluginService() - .getNamedPlugin( - DisseminationCrosswalk.class, - this.CROSSWALK_NAME); - - if (this.xwalk == null) { - throw new RuntimeException("Can't find crosswalk '" - + CROSSWALK_NAME + "'!"); - } - } - protected String getUsername() { if (null == this.USERNAME) { this.USERNAME = this.configurationService.getProperty(CFG_USER); @@ -350,64 +316,43 @@ public void deleteDOI(Context context, String doi) @Override public void reserveDOI(Context context, DSpaceObject dso, String doi) throws DOIIdentifierException { - this.prepareXwalk(); DSpaceObjectService dSpaceObjectService = ContentServiceFactory.getInstance() .getDSpaceObjectService(dso); - if (!this.xwalk.canDisseminate(dso)) { - log.error("Crosswalk " + this.CROSSWALK_NAME - + " cannot disseminate DSO with type " + dso.getType() - + " and ID " + dso.getID() + ". Giving up reserving the DOI " - + doi + "."); - throw new DOIIdentifierException("Cannot disseminate " - + dSpaceObjectService.getTypeText(dso) + "/" + dso.getID() - + " using crosswalk " + this.CROSSWALK_NAME + ".", - DOIIdentifierException.CONVERSION_ERROR); - } + StreamDisseminationCrosswalk xwalk = getStreamDisseminationCrosswalkByDso(dso); - // Set the transform's parameters. - // XXX Should the actual list be configurable? - Map parameters = new HashMap<>(); - if (configurationService.hasProperty(CFG_PREFIX)) { - parameters.put("prefix", - configurationService.getProperty(CFG_PREFIX)); - } - if (configurationService.hasProperty(CFG_PUBLISHER)) { - parameters.put("publisher", - configurationService.getProperty(CFG_PUBLISHER)); - } - if (configurationService.hasProperty(CFG_DATAMANAGER)) { - parameters.put("datamanager", - configurationService.getProperty(CFG_DATAMANAGER)); - } - if (configurationService.hasProperty(CFG_HOSTINGINSTITUTION)) { - parameters.put("hostinginstitution", - configurationService.getProperty(CFG_HOSTINGINSTITUTION)); + if (xwalk == null) { + log.error("No crosswalk found for DSO with type " + dso.getType() + + " and ID " + dso.getID() + ". Giving up reserving the DOI " + + doi + "."); + throw new DOIIdentifierException("Cannot disseminate " + + dSpaceObjectService.getTypeText(dso) + "/" + dso.getID() + ".", + DOIIdentifierException.CONVERSION_ERROR); } Element root = null; try { - root = xwalk.disseminateElement(context, dso, parameters); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + xwalk.disseminate(context, dso, baos); + SAXBuilder builder = new SAXBuilder(); + Document document = builder.build(new ByteArrayInputStream(baos.toByteArray())); + root = document.getRootElement(); } catch (AuthorizeException ae) { log.error("Caught an AuthorizeException while disseminating DSO " + "with type " + dso.getType() + " and ID " + dso.getID() + ". Giving up to reserve DOI " + doi + ".", ae); throw new DOIIdentifierException("AuthorizeException occured while " - + "converting " + dSpaceObjectService.getTypeText(dso) + "/" + dso - .getID() - + " using crosswalk " + this.CROSSWALK_NAME + ".", ae, + + "converting " + dSpaceObjectService.getTypeText(dso) + "/" + dso + ".", ae, DOIIdentifierException.CONVERSION_ERROR); } catch (CrosswalkException ce) { log.error("Caught an CrosswalkException while reserving a DOI (" + doi + ") for DSO with type " + dso.getType() + " and ID " + dso.getID() + ". Won't reserve the doi.", ce); throw new DOIIdentifierException("CrosswalkException occured while " - + "converting " + dSpaceObjectService.getTypeText(dso) + "/" + dso - .getID() - + " using crosswalk " + this.CROSSWALK_NAME + ".", ce, + + "converting " + dSpaceObjectService.getTypeText(dso) + "/" + dso + ".", ce, DOIIdentifierException.CONVERSION_ERROR); - } catch (IOException | SQLException ex) { + } catch (IOException | SQLException | JDOMException ex) { throw new RuntimeException(ex); } @@ -462,6 +407,21 @@ public void reserveDOI(Context context, DSpaceObject dso, String doi) } } + private StreamDisseminationCrosswalk getStreamDisseminationCrosswalkByDso(DSpaceObject dso) { + + if (dso.getType() != Constants.ITEM) { + return null; + } + + String entityType = itemService.getEntityType((Item) dso); + if (StringUtils.isBlank(entityType)) { + entityType = "Publication"; + } + + return disseminationCrosswalkByEntityType.get(entityType); + + } + @Override public void registerDOI(Context context, DSpaceObject dso, String doi) throws DOIIdentifierException { @@ -631,7 +591,7 @@ protected DataCiteResponse sendMetadataPostRequest(String doi, Element metadataR Format format = Format.getCompactFormat(); format.setEncoding("UTF-8"); XMLOutputter xout = new XMLOutputter(format); - return sendMetadataPostRequest(doi, xout.outputString(new Document(metadataRoot))); + return sendMetadataPostRequest(doi, xout.outputString(metadataRoot.getDocument())); } protected DataCiteResponse sendMetadataPostRequest(String doi, String metadata) @@ -842,12 +802,21 @@ protected Element addDOI(String doi, Element root) { } Element identifier = new Element("identifier", configurationService.getProperty(CFG_NAMESPACE, - "http://datacite.org/schema/kernel-3")); + "http://datacite.org/schema/kernel-4")); identifier.setAttribute("identifierType", "DOI"); identifier.addContent(doi.substring(DOI.SCHEME.length())); return root.addContent(0, identifier); } + public Map getDisseminationCrosswalkByEntityType() { + return disseminationCrosswalkByEntityType; + } + + public void setDisseminationCrosswalkByEntityType( + Map disseminationCrosswalkByEntityType) { + this.disseminationCrosswalkByEntityType = disseminationCrosswalkByEntityType; + } + protected class DataCiteResponse { private final int statusCode; private final String content; 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 22a141e4685b..00dd7d6e7c15 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 @@ 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 @@ void reserve(Context context, DSpaceObject dso, String identifier) */ 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 04a08a7781a2..7c6336ed3c7f 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 @@ /** - * 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/ads/ADSImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/ads/ADSImportMetadataSourceServiceImpl.java index 8fbe4ef2cf57..da59472c45a6 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/ads/ADSImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/ads/ADSImportMetadataSourceServiceImpl.java @@ -273,6 +273,9 @@ public Integer count(String query, String token) { uriBuilder.addParameter("fl", this.resultFieldList); String resp = liveImportClient.executeHttpGetRequest(timeout, uriBuilder.toString(), params); + if (StringUtils.isEmpty(resp)) { + return 0; + } JsonNode jsonNode = convertStringJsonToJsonNode(resp); return jsonNode.at("/response/numFound").asInt(); } catch (URISyntaxException e) { @@ -296,6 +299,9 @@ public List search(String query, Integer start, Integer count, Str uriBuilder.addParameter("fl", this.resultFieldList); String resp = liveImportClient.executeHttpGetRequest(timeout, uriBuilder.toString(), params); + if (StringUtils.isEmpty(resp)) { + return adsResults; + } JsonNode jsonNode = convertStringJsonToJsonNode(resp); JsonNode docs = jsonNode.at("/response/docs"); 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 445dfedebdc6..0014088c8650 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 @@ private BibTeXDatabase parseBibTex(InputStream inputStream) throws IOException, /** - * 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/cinii/CiniiImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/cinii/CiniiImportMetadataSourceServiceImpl.java index 5eff46c790e4..53230d960831 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/cinii/CiniiImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/cinii/CiniiImportMetadataSourceServiceImpl.java @@ -289,6 +289,9 @@ protected List search(String id, String appId) URIBuilder uriBuilder = new URIBuilder(this.url + id + ".rdf?appid=" + appId); Map> params = new HashMap>(); String response = liveImportClient.executeHttpGetRequest(1000, uriBuilder.toString(), params); + if (StringUtils.isBlank(response)) { + return records; + } List elements = splitToRecords(response); for (Element record : elements) { records.add(transformSourceRecords(record)); @@ -416,6 +419,9 @@ private Integer countCiniiElement(String appId, Integer maxResult, String author Map> params = new HashMap>(); String response = liveImportClient.executeHttpGetRequest(1000, uriBuilder.toString(), params); + if (StringUtils.isEmpty(response)) { + return 0; + } SAXBuilder saxBuilder = new SAXBuilder(); Document document = saxBuilder.build(new StringReader(response)); diff --git a/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefImportMetadataSourceServiceImpl.java index 7dde330b27ec..5c4c49deaec9 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefImportMetadataSourceServiceImpl.java @@ -158,6 +158,9 @@ public List call() throws Exception { } Map> params = new HashMap>(); String response = liveImportClient.executeHttpGetRequest(1000, uriBuilder.toString(), params); + if (StringUtils.isEmpty(response)) { + return results; + } JsonNode jsonNode = convertStringJsonToJsonNode(response); Iterator nodes = jsonNode.at("/message/items").iterator(); while (nodes.hasNext()) { @@ -194,6 +197,9 @@ public List call() throws Exception { URIBuilder uriBuilder = new URIBuilder(url + "/" + ID); Map> params = new HashMap>(); String responseString = liveImportClient.executeHttpGetRequest(1000, uriBuilder.toString(), params); + if (StringUtils.isEmpty(responseString)) { + return results; + } JsonNode jsonNode = convertStringJsonToJsonNode(responseString); JsonNode messageNode = jsonNode.at("/message"); results.add(transformSourceRecords(messageNode.toString())); @@ -246,6 +252,9 @@ public List call() throws Exception { } Map> params = new HashMap>(); String resp = liveImportClient.executeHttpGetRequest(1000, uriBuilder.toString(), params); + if (StringUtils.isEmpty(resp)) { + return results; + } JsonNode jsonNode = convertStringJsonToJsonNode(resp); Iterator nodes = jsonNode.at("/message/items").iterator(); while (nodes.hasNext()) { @@ -284,6 +293,9 @@ public Integer call() throws Exception { uriBuilder.addParameter("query", query.getParameterAsClass("query", String.class)); Map> params = new HashMap>(); String responseString = liveImportClient.executeHttpGetRequest(1000, uriBuilder.toString(), params); + if (StringUtils.isEmpty(responseString)) { + return 0; + } JsonNode jsonNode = convertStringJsonToJsonNode(responseString); return jsonNode.at("/message/total-results").asInt(); } 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 000000000000..f8540307b916 --- /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 000000000000..a11f2bc2471d --- /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/epo/service/EpoImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/epo/service/EpoImportMetadataSourceServiceImpl.java index 395d6b48c987..77e34ba9e41b 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/epo/service/EpoImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/epo/service/EpoImportMetadataSourceServiceImpl.java @@ -59,6 +59,7 @@ * Implements a data source for querying EPO * * @author Pasquale Cavallo (pasquale.cavallo at 4Science dot it) + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4Science.com) */ public class EpoImportMetadataSourceServiceImpl extends AbstractImportMetadataSourceService implements QuerySource { @@ -147,6 +148,9 @@ protected String login() throws IOException, HttpException { Map> params = getLoginParams(); String entity = "grant_type=client_credentials"; String json = liveImportClient.executeHttpPostRequest(this.authUrl, params, entity); + if (StringUtils.isBlank(json)) { + return json; + } ObjectMapper mapper = new ObjectMapper(new JsonFactory()); JsonNode rootNode = mapper.readTree(json); JsonNode accessTokenNode = rootNode.get("access_token"); @@ -190,7 +194,8 @@ public int getRecordsCount(Query query) throws MetadataSourceException { String bearer = login(); return retry(new CountRecordsCallable(query, bearer)); } catch (IOException | HttpException e) { - e.printStackTrace(); + log.warn(e.getMessage(), e); + throw new RuntimeException(e.getMessage(), e); } } return 0; @@ -204,7 +209,7 @@ public Collection getRecords(String query, int start, String bearer = login(); return retry(new SearchByQueryCallable(query, bearer, start, count)); } catch (IOException | HttpException e) { - log.warn(e.getMessage()); + log.warn(e.getMessage(), e); throw new RuntimeException(e.getMessage(), e); } } @@ -247,14 +252,12 @@ public ImportRecord getRecord(Query query) throws MetadataSourceException { } @Override - public Collection findMatchingRecords(Item item) - throws MetadataSourceException { + public Collection findMatchingRecords(Item item) throws MetadataSourceException { return null; } @Override - public Collection findMatchingRecords(Query query) - throws MetadataSourceException { + public Collection findMatchingRecords(Query query) throws MetadataSourceException { return null; } @@ -303,7 +306,13 @@ private SearchByIdCallable(String id, String bearer) { } public List call() throws Exception { - if (id.contains(APP_NO_DATE_SEPARATOR)) { + int positionToSplit = id.indexOf(":"); + String docType = EpoDocumentId.EPODOC; + String idS = id; + if (positionToSplit != -1) { + docType = id.substring(0, positionToSplit); + idS = id.substring(positionToSplit + 1, id.length()); + } else if (id.contains(APP_NO_DATE_SEPARATOR)) { // special case the id is the combination of the applicationnumber and date filed String query = "applicationnumber=" + id.split(APP_NO_DATE_SEPARATOR_REGEX)[0]; SearchByQueryCallable search = new SearchByQueryCallable(query, bearer, 0, 10); @@ -316,12 +325,7 @@ public List call() throws Exception { return records; } // search by Patent Number - String[] identifier = id.split(":"); - String patentIdentifier = identifier.length == 2 ? identifier[1] : id; - List records = retry(new SearchByQueryCallable(patentIdentifier, bearer, null, null)); - if (records.size() > 1) { - log.warn("More record are returned with Patent Number: " + id); - } + List records = searchDocument(bearer, idS, docType); return records; } } @@ -375,7 +379,7 @@ public List call() throws Exception { private Integer countDocument(String bearer, String query) { if (StringUtils.isBlank(bearer)) { - return null; + return 0; } try { Map> params = new HashMap>(); @@ -388,6 +392,9 @@ private Integer countDocument(String bearer, String query) { uriBuilder.addParameter("q", query); String response = liveImportClient.executeHttpGetRequest(1000, uriBuilder.toString(), params); + if (StringUtils.isBlank(response)) { + return 0; + } SAXBuilder saxBuilder = new SAXBuilder(); Document document = saxBuilder.build(new StringReader(response)); @@ -402,7 +409,7 @@ private Integer countDocument(String bearer, String query) { return Integer.parseInt(totalRes); } catch (JDOMException | IOException | URISyntaxException | JaxenException e) { log.error(e.getMessage(), e); - return null; + return 0; } } @@ -425,6 +432,9 @@ private List searchDocumentIds(String bearer, String query, int s uriBuilder.addParameter("q", query); String response = liveImportClient.executeHttpGetRequest(1000, uriBuilder.toString(), params); + if (StringUtils.isBlank(response)) { + return results; + } SAXBuilder saxBuilder = new SAXBuilder(); Document document = saxBuilder.build(new StringReader(response)); @@ -465,6 +475,9 @@ private List searchDocument(String bearer, String id, String docTy String url = this.url.replace("$(doctype)", docType).replace("$(id)", id); String response = liveImportClient.executeHttpGetRequest(1000, url, params); + if (StringUtils.isBlank(response)) { + return results; + } List elements = splitToRecords(response); for (Element element : elements) { results.add(transformSourceRecords(element)); @@ -481,7 +494,7 @@ private List splitToRecords(String recordsSrc) { Document document = saxBuilder.build(new StringReader(recordsSrc)); Element root = document.getRootElement(); List namespaces = Arrays.asList(Namespace.getNamespace("ns", "http://www.epo.org/exchange")); - XPathExpression xpath = XPathFactory.instance().compile("//ns:exchange-document", + XPathExpression xpath = XPathFactory.instance().compile("//ns:exchange-documents", Filters.element(), null, namespaces); List recordsList = xpath.evaluate(root); diff --git a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/AuthorMetadataContributor.java b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/AuthorMetadataContributor.java index c28c229188a7..b2d2ea22b0c6 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/AuthorMetadataContributor.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/AuthorMetadataContributor.java @@ -111,10 +111,16 @@ private List getMetadataOfAuthors(Element element) throws JaxenExc addMetadatum(metadatums, getMetadata(getElementValue(surname) + ", " + getElementValue(givenName), this.authname)); - addMetadatum(metadatums, getMetadata(getElementValue(scopusId), this.scopusId)); - addMetadatum(metadatums, getMetadata(getElementValue(orcid), this.orcid)); - addMetadatum(metadatums, getMetadata(StringUtils.isNotBlank(afid.getValue()) - ? this.affId2affName.get(afid.getValue()) : null, this.affiliation)); + if (this.scopusId != null) { + addMetadatum(metadatums, getMetadata(getElementValue(scopusId), this.scopusId)); + } + if (this.orcid != null) { + addMetadatum(metadatums, getMetadata(getElementValue(orcid), this.orcid)); + } + if (this.affiliation != null) { + addMetadatum(metadatums, getMetadata(StringUtils.isNotBlank(afid.getValue()) + ? this.affId2affName.get(afid.getValue()) : null, this.affiliation)); + } return metadatums; } 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 f7399802200e..590fc63283b9 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 void setMetadataProcessor(JsonPathMetadataProcessor metadataProcessor) { * Retrieve the metadata associated with the given object. * The toString() of the resulting object will be used. * - * @param t A class to retrieve metadata from. + * @param fullJson 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/SimpleXpathMetadatumAndAttributeAndSubNodeContributor.java b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleXpathMetadatumAndAttributeAndSubNodeContributor.java new file mode 100644 index 000000000000..aae07b1ff263 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleXpathMetadatumAndAttributeAndSubNodeContributor.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.importer.external.metadatamapping.contributor; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.importer.external.metadatamapping.MetadatumDTO; +import org.jdom2.Element; +import org.jdom2.Namespace; +import org.jdom2.filter.Filters; +import org.jdom2.xpath.XPathExpression; +import org.jdom2.xpath.XPathFactory; + +/** + * Metadata contributor that takes multiple value of the some nome. + * Can fileter also nedes by attribute element value. + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.com) + */ +public class SimpleXpathMetadatumAndAttributeAndSubNodeContributor extends SimpleXpathMetadatumAndAttributeContributor { + + private String attributeValue; + private String queryToSubNode; + + @Override + public Collection contributeMetadata(Element t) { + List values = new LinkedList<>(); + List namespaces = new ArrayList(); + for (String ns : prefixToNamespaceMapping.keySet()) { + namespaces.add(Namespace.getNamespace(prefixToNamespaceMapping.get(ns), ns)); + } + + List nodes = getNodes(t, query, namespaces); + List subNodes = getSubNodes(namespaces, nodes); + for (Object el : subNodes) { + if (el instanceof Element) { + values.add(metadataFieldMapping.toDCValue(this.field, extractValue(el))); + } + } + return values; + } + + private List getSubNodes(List namespaces, List nodes) { + List allNodes = new ArrayList(); + for (Object el : nodes) { + if (el instanceof Element) { + List elements = ((Element) el).getChildren(); + for (Element element : elements) { + String attributeValue = element.getAttributeValue(this.attribute); + if (StringUtils.equals(attributeValue, this.attributeValue)) { + List subNodes = getNodes(element, queryToSubNode, namespaces); + allNodes.addAll(subNodes); + } + } + } + } + return allNodes; + } + + private List getNodes(Element t, String query, List namespaces) { + XPathExpression xpath = XPathFactory.instance().compile(query, Filters.fpassthrough(),null, namespaces); + return xpath.evaluate(t); + } + + private String extractValue(Object el) { + String value = ((Element) el).getText(); + return StringUtils.isNotBlank(value) ? value : ((Element) el).getValue().trim(); + } + + public void setAttributeValue(String attributeValue) { + this.attributeValue = attributeValue; + } + + public void setQueryToSubNode(String queryToSubNode) { + this.queryToSubNode = queryToSubNode; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleXpathMetadatumAndAttributeContributor.java b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleXpathMetadatumAndAttributeContributor.java index dea840d15b38..1fd9d168338d 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleXpathMetadatumAndAttributeContributor.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleXpathMetadatumAndAttributeContributor.java @@ -33,7 +33,7 @@ public class SimpleXpathMetadatumAndAttributeContributor extends SimpleXpathMeta private final static Logger log = LogManager.getLogger(); - private String attribute; + protected String attribute; @Override public Collection contributeMetadata(Element t) { diff --git a/dspace-api/src/main/java/org/dspace/importer/external/pubmedeurope/PubmedEuropeMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/pubmedeurope/PubmedEuropeMetadataSourceServiceImpl.java index 1ec0da74206e..ba95e1e9045a 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/pubmedeurope/PubmedEuropeMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/pubmedeurope/PubmedEuropeMetadataSourceServiceImpl.java @@ -292,6 +292,9 @@ public Integer count(String query) throws URISyntaxException, ClientProtocolExce try { Map> params = new HashMap>(); String response = liveImportClient.executeHttpGetRequest(1000, buildURI(1, query), params); + if (StringUtils.isEmpty(response)) { + return 0; + } SAXBuilder saxBuilder = new SAXBuilder(); Document document = saxBuilder.build(new StringReader(response)); 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 2574e187dfc6..1f460c19e697 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 @@ private List notAggregatedData(InputStream inputStrea } /** - * 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/scopus/service/ScopusImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/scopus/service/ScopusImportMetadataSourceServiceImpl.java index d0c2fb078a2c..36cf8ae2dc49 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/scopus/service/ScopusImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/scopus/service/ScopusImportMetadataSourceServiceImpl.java @@ -200,6 +200,9 @@ public Integer call() throws Exception { Map requestParams = getRequestParameters(query, null, null, null); params.put(URI_PARAMETERS, requestParams); String response = liveImportClient.executeHttpGetRequest(timeout, url, params); + if (StringUtils.isEmpty(response)) { + return 0; + } SAXBuilder saxBuilder = new SAXBuilder(); Document document = saxBuilder.build(new StringReader(response)); @@ -243,6 +246,9 @@ public List call() throws Exception { Map requestParams = getRequestParameters(queryString, viewMode, null, null); params.put(URI_PARAMETERS, requestParams); String response = liveImportClient.executeHttpGetRequest(timeout, url, params); + if (StringUtils.isEmpty(response)) { + return results; + } List elements = splitToRecords(response); for (Element record : elements) { results.add(transformSourceRecords(record)); @@ -302,6 +308,9 @@ public List call() throws Exception { Map requestParams = getRequestParameters(queryString, viewMode, start, count); params.put(URI_PARAMETERS, requestParams); String response = liveImportClient.executeHttpGetRequest(timeout, url, params); + if (StringUtils.isEmpty(response)) { + return results; + } List elements = splitToRecords(response); for (Element record : elements) { results.add(transformSourceRecords(record)); @@ -347,6 +356,9 @@ public List call() throws Exception { Map requestParams = getRequestParameters(queryString, viewMode, start, count); params.put(URI_PARAMETERS, requestParams); String response = liveImportClient.executeHttpGetRequest(timeout, url, params); + if (StringUtils.isEmpty(response)) { + return results; + } List elements = splitToRecords(response); for (Element record : elements) { results.add(transformSourceRecords(record)); 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 019cf33177c2..5d83b9a7cce4 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 List getSupportedExtensions() { * 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 List getRecords(InputStream is) throws FileSourceException * 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/FileSource.java b/dspace-api/src/main/java/org/dspace/importer/external/service/components/FileSource.java index 55f267573120..801f5474bb4e 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 @@ -32,7 +32,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 */ @@ -42,7 +42,7 @@ public List getRecords(InputStream inputStream) /** * 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/importer/external/vufind/VuFindImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/vufind/VuFindImportMetadataSourceServiceImpl.java index a4f90fa5ba61..1b942a7f1525 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/vufind/VuFindImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/vufind/VuFindImportMetadataSourceServiceImpl.java @@ -144,6 +144,9 @@ public Integer call() throws Exception { uriBuilder.addParameter("lookfor", query.getParameterAsClass("query", String.class)); Map> params = new HashMap>(); String responseString = liveImportClient.executeHttpGetRequest(1000, uriBuilder.toString(), params); + if (StringUtils.isEmpty(responseString)) { + return 0; + } JsonNode node = convertStringJsonToJsonNode(responseString); JsonNode resultCountNode = node.get("resultCount"); return resultCountNode.intValue(); @@ -182,8 +185,7 @@ public String call() throws Exception { } } Map> params = new HashMap>(); - String response = liveImportClient.executeHttpGetRequest(1000, uriBuilder.toString(), params); - return response; + return liveImportClient.executeHttpGetRequest(1000, uriBuilder.toString(), params); } } @@ -309,6 +311,9 @@ private JsonNode convertStringJsonToJsonNode(String json) { private List extractMetadataFromRecordList(String records) { List recordsResult = new ArrayList<>(); + if (StringUtils.isEmpty(records)) { + return recordsResult; + } JsonNode jsonNode = convertStringJsonToJsonNode(records); JsonNode node = jsonNode.get("records"); if (Objects.nonNull(node) && node.isArray()) { diff --git a/dspace-api/src/main/java/org/dspace/importer/external/wos/service/WOSImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/wos/service/WOSImportMetadataSourceServiceImpl.java index 2ccdc12b8db2..b669cd860078 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/wos/service/WOSImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/wos/service/WOSImportMetadataSourceServiceImpl.java @@ -145,6 +145,9 @@ public Integer call() throws Exception { Map> params = new HashMap>(); params.put(HEADER_PARAMETERS, getRequestParameters()); String response = liveImportClient.executeHttpGetRequest(timeout, url, params); + if (StringUtils.isEmpty(response)) { + return 0; + } SAXBuilder saxBuilder = new SAXBuilder(); Document document = saxBuilder.build(new StringReader(response)); @@ -179,6 +182,9 @@ public List call() throws Exception { Map> params = new HashMap>(); params.put(HEADER_PARAMETERS, getRequestParameters()); String response = liveImportClient.executeHttpGetRequest(timeout, urlString, params); + if (StringUtils.isEmpty(response)) { + return results; + } List elements = splitToRecords(response); for (Element record : elements) { @@ -226,6 +232,9 @@ public List call() throws Exception { String url = urlSearch + URLEncoder.encode(queryString, StandardCharsets.UTF_8) + "&count=" + count + "&firstRecord=" + (start + 1); String response = liveImportClient.executeHttpGetRequest(timeout, url, params); + if (StringUtils.isEmpty(response)) { + return results; + } List omElements = splitToRecords(response); for (Element el : omElements) { diff --git a/dspace-api/src/main/java/org/dspace/layout/dao/CrisLayoutFieldDAO.java b/dspace-api/src/main/java/org/dspace/layout/dao/CrisLayoutFieldDAO.java index 3d3787859901..695f8f96fd72 100644 --- a/dspace-api/src/main/java/org/dspace/layout/dao/CrisLayoutFieldDAO.java +++ b/dspace-api/src/main/java/org/dspace/layout/dao/CrisLayoutFieldDAO.java @@ -50,8 +50,6 @@ public interface CrisLayoutFieldDAO extends GenericDAO { * Returns the field that are available for specific Box * @param context The relevant DSpace Context * @param boxId id of the box {@link CrisLayoutBox} - * @param limit how many results return - * @param offset the position of the first result to return * @return List of CrisLayoutField {@link CrisLayoutField} * @throws SQLException An exception that provides information on a database errors. */ diff --git a/dspace-api/src/main/java/org/dspace/layout/factory/CrisLayoutServiceFactory.java b/dspace-api/src/main/java/org/dspace/layout/factory/CrisLayoutServiceFactory.java index 1f9e3f5960bd..1b2bb304c857 100644 --- a/dspace-api/src/main/java/org/dspace/layout/factory/CrisLayoutServiceFactory.java +++ b/dspace-api/src/main/java/org/dspace/layout/factory/CrisLayoutServiceFactory.java @@ -7,6 +7,7 @@ */ package org.dspace.layout.factory; +import org.dspace.layout.script.service.CrisLayoutToolConverter; import org.dspace.layout.script.service.CrisLayoutToolParser; import org.dspace.layout.script.service.CrisLayoutToolValidator; import org.dspace.layout.service.CrisLayoutBoxService; @@ -46,4 +47,6 @@ public static CrisLayoutServiceFactory getInstance() { public abstract CrisLayoutToolParser getCrisLayoutToolParser(); + public abstract CrisLayoutToolConverter getCrisLayoutToolConverter(); + } diff --git a/dspace-api/src/main/java/org/dspace/layout/factory/impl/CrisLayoutServiceFactoryImpl.java b/dspace-api/src/main/java/org/dspace/layout/factory/impl/CrisLayoutServiceFactoryImpl.java index 8a82f7586c0a..89eb36da6f4d 100644 --- a/dspace-api/src/main/java/org/dspace/layout/factory/impl/CrisLayoutServiceFactoryImpl.java +++ b/dspace-api/src/main/java/org/dspace/layout/factory/impl/CrisLayoutServiceFactoryImpl.java @@ -8,6 +8,7 @@ package org.dspace.layout.factory.impl; import org.dspace.layout.factory.CrisLayoutServiceFactory; +import org.dspace.layout.script.service.CrisLayoutToolConverter; import org.dspace.layout.script.service.CrisLayoutToolParser; import org.dspace.layout.script.service.CrisLayoutToolValidator; import org.dspace.layout.service.CrisLayoutBoxService; @@ -50,6 +51,9 @@ public class CrisLayoutServiceFactoryImpl extends CrisLayoutServiceFactory { @Autowired(required = true) private CrisLayoutToolParser parser; + @Autowired(required = true) + private CrisLayoutToolConverter converter; + @Override public CrisLayoutTabService getTabService() { return this.tabService; @@ -90,4 +94,9 @@ public CrisLayoutToolParser getCrisLayoutToolParser() { return parser; } + @Override + public CrisLayoutToolConverter getCrisLayoutToolConverter() { + return converter; + } + } diff --git a/dspace-api/src/main/java/org/dspace/layout/script/ExportCrisLayoutToolCliScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/layout/script/ExportCrisLayoutToolCliScriptConfiguration.java new file mode 100644 index 000000000000..0b672e6b46ec --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/layout/script/ExportCrisLayoutToolCliScriptConfiguration.java @@ -0,0 +1,18 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.layout.script; + +/** + * Extension of {@link ExportCrisLayoutToolScript} for CLI. + * + * @author Mohamed Eskander (mohamed.eskander at 4science.it) + */ +public class ExportCrisLayoutToolCliScriptConfiguration + extends ExportCrisLayoutToolScriptConfiguration { + +} diff --git a/dspace-api/src/main/java/org/dspace/layout/script/ExportCrisLayoutToolScript.java b/dspace-api/src/main/java/org/dspace/layout/script/ExportCrisLayoutToolScript.java new file mode 100644 index 000000000000..afff6782852a --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/layout/script/ExportCrisLayoutToolScript.java @@ -0,0 +1,116 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.layout.script; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.sql.SQLException; +import java.util.UUID; + +import org.apache.commons.cli.ParseException; +import org.apache.poi.ss.usermodel.Workbook; +import org.dspace.authorize.factory.AuthorizeServiceFactory; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.factory.EPersonServiceFactory; +import org.dspace.layout.factory.CrisLayoutServiceFactory; +import org.dspace.layout.script.service.CrisLayoutToolConverter; +import org.dspace.layout.service.CrisLayoutTabService; +import org.dspace.scripts.DSpaceRunnable; +import org.dspace.utils.DSpace; + +/** + * Script to export CRIS layout configuration into excel file. + * + * @author Mohamed Eskander (mohamed.eskander at 4science.it) + * + */ +public class ExportCrisLayoutToolScript + extends DSpaceRunnable> { + + private AuthorizeService authorizeService; + + private CrisLayoutTabService tabService; + + private CrisLayoutToolConverter converter; + + private Context context; + + @Override + public void setup() throws ParseException { + this.authorizeService = AuthorizeServiceFactory.getInstance().getAuthorizeService(); + this.converter = CrisLayoutServiceFactory.getInstance().getCrisLayoutToolConverter(); + this.tabService = CrisLayoutServiceFactory.getInstance().getTabService(); + } + + @Override + public void internalRun() throws Exception { + + context = new Context(); + assignCurrentUserInContext(); + assignSpecialGroupsInContext(); + + context.turnOffAuthorisationSystem(); + + if (!this.authorizeService.isAdmin(context)) { + throw new IllegalArgumentException("The user cannot use the cris layout configuration tool"); + } + + try { + performExport(); + context.complete(); + handler.logInfo("Export has completed successfully"); + } catch (Exception e) { + handler.handleException(e); + context.abort(); + } finally { + context.restoreAuthSystemState(); + } + } + + private void performExport() throws Exception { + Workbook workbook = converter.convert(tabService.findAll(context)); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + workbook.write(out); + + ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()); + handler.writeFilestream(context, getFileName(), in, getMIMEType()); + + handler.logInfo("Layout exported successfully into file named " + getFileName()); + } + + @Override + @SuppressWarnings("unchecked") + public ExportCrisLayoutToolScriptConfiguration getScriptConfiguration() { + return new DSpace().getServiceManager().getServiceByName("export-cris-layout-tool", + ExportCrisLayoutToolScriptConfiguration.class); + } + + private void assignCurrentUserInContext() throws SQLException { + UUID uuid = getEpersonIdentifier(); + if (uuid != null) { + EPerson ePerson = EPersonServiceFactory.getInstance().getEPersonService().find(context, uuid); + context.setCurrentUser(ePerson); + } + } + + private void assignSpecialGroupsInContext() { + for (UUID uuid : handler.getSpecialGroups()) { + context.setSpecialGroup(uuid); + } + } + + private String getFileName() { + return "cris-layout-tool-exported.xls"; + } + + public String getMIMEType() { + return "application/vnd.ms-excel"; + } +} diff --git a/dspace-api/src/main/java/org/dspace/layout/script/ExportCrisLayoutToolScriptCli.java b/dspace-api/src/main/java/org/dspace/layout/script/ExportCrisLayoutToolScriptCli.java new file mode 100644 index 000000000000..daadae29abc0 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/layout/script/ExportCrisLayoutToolScriptCli.java @@ -0,0 +1,18 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.layout.script; + +/** + * Extension of {@link ExportCrisLayoutToolScript} for CLI. + * + * @author Mohamed Eskander (mohamed.eskander at 4science.it) + * + */ +public class ExportCrisLayoutToolScriptCli extends ExportCrisLayoutToolScript{ + +} diff --git a/dspace-api/src/main/java/org/dspace/layout/script/ExportCrisLayoutToolScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/layout/script/ExportCrisLayoutToolScriptConfiguration.java new file mode 100644 index 000000000000..0be873b57985 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/layout/script/ExportCrisLayoutToolScriptConfiguration.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.layout.script; + +import java.sql.SQLException; + +import org.apache.commons.cli.Options; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.core.Context; +import org.dspace.scripts.configuration.ScriptConfiguration; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Configuration for {@link ExportCrisLayoutToolScript}. + * + * @author Mohamed Eskander (mohamed.eskander at 4science.it) + */ +public class ExportCrisLayoutToolScriptConfiguration + extends ScriptConfiguration { + + @Autowired + private AuthorizeService authorizeService; + + private Class dspaceRunnableClass; + + @Override + public boolean isAllowedToExecute(Context context) { + try { + return authorizeService.isAdmin(context); + } catch (SQLException e) { + throw new RuntimeException("SQLException occurred when checking if the current user is an admin", e); + } + } + + @Override + public Options getOptions() { + if (options == null) { + options = new Options(); + } + return options; + } + + @Override + public void setDspaceRunnableClass(Class dspaceRunnableClass) { + this.dspaceRunnableClass = dspaceRunnableClass; + } + + @Override + public Class getDspaceRunnableClass() { + return dspaceRunnableClass; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/layout/script/service/CrisLayoutToolConverter.java b/dspace-api/src/main/java/org/dspace/layout/script/service/CrisLayoutToolConverter.java new file mode 100644 index 000000000000..b57f4f35523c --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/layout/script/service/CrisLayoutToolConverter.java @@ -0,0 +1,30 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.layout.script.service; + +import java.util.List; + +import org.apache.poi.ss.usermodel.Workbook; +import org.dspace.layout.CrisLayoutTab; + +/** + * Cris layout configuration tool converter. + * + * @author Mohamed Eskander (mohamed.eskander at 4science.it) + * + */ +public interface CrisLayoutToolConverter { + + /** + * convert the given list of tabs to workbook. + * + * @param tabs the tabs to convert + * @return the workbook + */ + Workbook convert(List tabs); +} diff --git a/dspace-api/src/main/java/org/dspace/layout/script/service/CrisLayoutToolValidator.java b/dspace-api/src/main/java/org/dspace/layout/script/service/CrisLayoutToolValidator.java index 96d04fd7e087..c1a9cff5dbb4 100644 --- a/dspace-api/src/main/java/org/dspace/layout/script/service/CrisLayoutToolValidator.java +++ b/dspace-api/src/main/java/org/dspace/layout/script/service/CrisLayoutToolValidator.java @@ -37,6 +37,16 @@ public interface CrisLayoutToolValidator { String BOX_POLICY_SHEET = "boxpolicy"; + String UTILSDATA_SHEET = "utilsdata"; + + String TAB_i18n_SHEET = "tab_i18n"; + + String BOX_i18n_SHEET = "box_i18n"; + + String METADATA_i18n_SHEET = "metadata_i18n"; + + String METADATAGROUP_i18n_SHEET = "metadatagroup_i18n"; + String CONTAINER_COLUMN = "CONTAINER"; diff --git a/dspace-api/src/main/java/org/dspace/layout/script/service/impl/CrisLayoutToolConverterImpl.java b/dspace-api/src/main/java/org/dspace/layout/script/service/impl/CrisLayoutToolConverterImpl.java new file mode 100644 index 000000000000..1aec1c349372 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/layout/script/service/impl/CrisLayoutToolConverterImpl.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.layout.script.service.impl; + +import static org.dspace.layout.script.service.CrisLayoutToolValidator.BOX2METADATA_SHEET; +import static org.dspace.layout.script.service.CrisLayoutToolValidator.BOX2METRICS_SHEET; +import static org.dspace.layout.script.service.CrisLayoutToolValidator.BOX_POLICY_SHEET; +import static org.dspace.layout.script.service.CrisLayoutToolValidator.BOX_SHEET; +import static org.dspace.layout.script.service.CrisLayoutToolValidator.METADATAGROUPS_SHEET; +import static org.dspace.layout.script.service.CrisLayoutToolValidator.METADATAGROUP_TYPE; +import static org.dspace.layout.script.service.CrisLayoutToolValidator.TAB2BOX_SHEET; +import static org.dspace.layout.script.service.CrisLayoutToolValidator.TAB_POLICY_SHEET; +import static org.dspace.layout.script.service.CrisLayoutToolValidator.TAB_SHEET; +import static org.dspace.util.WorkbookUtils.createCell; + +import java.io.IOException; +import java.io.InputStream; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.apache.commons.collections4.CollectionUtils; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.ss.usermodel.WorkbookFactory; +import org.dspace.content.MetadataField; +import org.dspace.eperson.Group; +import org.dspace.layout.CrisLayoutBox; +import org.dspace.layout.CrisLayoutCell; +import org.dspace.layout.CrisLayoutField; +import org.dspace.layout.CrisLayoutFieldBitstream; +import org.dspace.layout.CrisLayoutMetric2Box; +import org.dspace.layout.CrisLayoutTab; +import org.dspace.layout.CrisMetadataGroup; +import org.dspace.layout.LayoutSecurity; +import org.dspace.layout.script.service.CrisLayoutToolConverter; + +/** + * Implementation of {@link CrisLayoutToolConverter}. + * + * @author Mohamed Eskander (mohamed.eskander at 4science.it) + * + */ +public class CrisLayoutToolConverterImpl implements CrisLayoutToolConverter { + + @Override + public Workbook convert(List tabs) { + Workbook workbook = getTemplateWorkBook(); + buildTab(workbook, tabs); + autoSizeAllSheetsColumns(workbook); + return workbook; + } + + private Workbook getTemplateWorkBook() { + try (InputStream inputStream = + CrisLayoutToolConverterImpl.class + .getResourceAsStream("cris-layout-configuration-template.xls")) { + return WorkbookFactory.create(inputStream); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private void buildTab(Workbook workbook, List tabs) { + Sheet sheet = workbook.getSheet(TAB_SHEET); + tabs.forEach(tab -> { + buildTabRow(sheet, tab); + buildTab2box(workbook, tab); + buildTabPolicy(workbook, tab); + }); + } + + private void buildTabRow(Sheet sheet, CrisLayoutTab tab) { + Row row = sheet.createRow(sheet.getLastRowNum() + 1); + createCell(row, 0, tab.getEntity().getLabel()); + createCell(row, 1, tab.getShortName()); + createCell(row, 2, tab.getHeader()); + createCell(row, 3, String.valueOf(tab.getPriority())); + createCell(row, 4, convertToString(tab.isLeading())); + createCell(row, 5, toSecurity(tab.getSecurity())); + } + + private void buildTab2box(Workbook workbook, CrisLayoutTab tab) { + Sheet sheet = workbook.getSheet(TAB2BOX_SHEET); + for (int i = 0 ; i < tab.getRows().size() ; i++) { + // position column into database starts from 0, so will increase 1 + int rowIndex = i + 1; + tab.getRows().get(i).getCells() + .forEach(cell -> { + buildTab2boxRow(sheet, rowIndex, cell); + buildBox(sheet.getWorkbook(), cell.getBoxes()); + buildBoxPolicy(sheet.getWorkbook(), cell.getBoxes()); + }); + } + } + + private void buildTab2boxRow(Sheet sheet, int cellIndex, CrisLayoutCell cell) { + Row row = sheet.createRow(sheet.getLastRowNum() + 1); + createCell(row, 0, cell.getRow().getTab().getEntity().getLabel()); + createCell(row, 1, cell.getRow().getTab().getShortName()); + createCell(row, 2, String.valueOf(cellIndex)); + createCell(row, 3, cell.getRow().getStyle()); + createCell(row, 4, cell.getStyle()); + createCell(row, 5, getBoxesNames(cell.getBoxes())); + } + + private String getBoxesNames(List boxes) { + return boxes.stream() + .map(box -> box.getShortname()) + .collect(Collectors.joining(", ")); + } + + private void buildBox(Workbook workbook, List boxes) { + Sheet sheet = workbook.getSheet(BOX_SHEET); + boxes.forEach(box -> { + buildBoxRow(sheet, box); + buildBox2metadata(sheet.getWorkbook(), box.getLayoutFields()); + buildBox2metrics(sheet.getWorkbook(), box); + }); + } + + private void buildBoxRow(Sheet sheet, CrisLayoutBox box) { + Row row = sheet.createRow(sheet.getLastRowNum() + 1); + createCell(row, 0, box.getCell().getRow().getTab().getEntity().getLabel()); + createCell(row, 1, convertToString(box.getCollapsed())); + createCell(row, 2, box.getType()); + createCell(row, 3, box.getShortname()); + createCell(row, 4, box.getHeader()); + createCell(row, 5, convertToString(box.isContainer())); + createCell(row, 6, convertToString(box.getMinor())); + createCell(row, 7, toSecurity(box.getSecurity())); + createCell(row, 8, box.getStyle()); + } + + private void buildBox2metadata(Workbook workbook, List layoutFields) { + Sheet sheet = workbook.getSheet(BOX2METADATA_SHEET); + layoutFields.forEach(layoutField -> { + buildBox2metadataRow(sheet, layoutField); + buildMetadataGroups(sheet.getWorkbook(), layoutField.getCrisMetadataGroupList()); + }); + } + + private void buildBox2metadataRow(Sheet sheet, CrisLayoutField layoutField) { + Row row = sheet.createRow(sheet.getLastRowNum() + 1); + createCell(row, 0, layoutField.getBox().getCell().getRow().getTab().getEntity().getLabel()); + createCell(row, 1, layoutField.getBox().getShortname()); + createCell(row, 2, String.valueOf(layoutField.getRow())); + createCell(row, 3, String.valueOf(layoutField.getCell())); + createCell(row, 4, getLayoutFieldType(layoutField)); + createCell(row, 5, getMetadataField(layoutField)); + createCell(row, 6, getMetadataValue(layoutField)); + createCell(row, 7, getBundle(layoutField)); + createCell(row, 8, layoutField.getLabel()); + createCell(row, 9, convertToString(layoutField.isLabelAsHeading())); + createCell(row, 10, layoutField.getRendering()); + createCell(row, 11, convertToString(layoutField.isValuesInline())); + createCell(row, 12, layoutField.getRowStyle()); + createCell(row, 13, layoutField.getCellStyle()); + createCell(row, 14, layoutField.getStyleLabel()); + createCell(row, 15, layoutField.getStyleValue()); + } + + private String getMetadataValue(CrisLayoutField layoutField) { + String value = ""; + if (layoutField instanceof CrisLayoutFieldBitstream) { + value = ((CrisLayoutFieldBitstream) layoutField).getMetadataValue(); + } + return value; + } + + private String getBundle(CrisLayoutField layoutField) { + String value = ""; + if (layoutField instanceof CrisLayoutFieldBitstream) { + value = ((CrisLayoutFieldBitstream) layoutField).getBundle(); + } + return value; + } + + private String getMetadataField(CrisLayoutField layoutField) { + return Optional.ofNullable(layoutField.getMetadataField()) + .map(metadataField -> metadataField.toString('.')) + .orElse(""); + } + + private String getLayoutFieldType(CrisLayoutField layoutField) { + String type = layoutField.getType(); + if (CollectionUtils.isNotEmpty(layoutField.getCrisMetadataGroupList())) { + type = METADATAGROUP_TYPE; + } + return type; + } + + private void buildMetadataGroups(Workbook workbook, List crisMetadataGroups) { + Sheet sheet = workbook.getSheet(METADATAGROUPS_SHEET); + crisMetadataGroups + .forEach(crisMetadataGroup -> + buildMetadataGroupRow(sheet, crisMetadataGroup)); + } + + private void buildMetadataGroupRow(Sheet sheet, CrisMetadataGroup crisMetadataGroup) { + Row row = sheet.createRow(sheet.getLastRowNum() + 1); + CrisLayoutField crisLayoutField = crisMetadataGroup.getCrisLayoutField(); + + createCell(row, 0, crisLayoutField.getBox().getCell().getRow().getTab().getEntity().getLabel()); + createCell(row, 1, crisLayoutField.getMetadataField().toString('.')); + createCell(row, 2, crisLayoutField.getType()); + createCell(row, 3, crisMetadataGroup.getMetadataField().toString('.')); + createCell(row, 4, ""); + createCell(row, 5, ""); + createCell(row, 6, crisMetadataGroup.getLabel()); + createCell(row, 7, crisMetadataGroup.getRendering()); + createCell(row, 8, crisMetadataGroup.getStyleLabel()); + createCell(row, 9, crisMetadataGroup.getStyleValue()); + } + + private void buildBox2metrics(Workbook workbook, CrisLayoutBox box) { + Sheet sheet = workbook.getSheet(BOX2METRICS_SHEET); + buildBox2metricsRow(sheet, box); + } + + private void buildBox2metricsRow(Sheet sheet, CrisLayoutBox box) { + if (CollectionUtils.isNotEmpty(box.getMetric2box())) { + Row row = sheet.createRow(sheet.getLastRowNum() + 1); + createCell(row, 0, box.getCell().getRow().getTab().getEntity().getLabel()); + createCell(row, 1, box.getShortname()); + createCell(row, 2, getMetric2BoxTypes(box.getMetric2box())); + } + } + + private String getMetric2BoxTypes(List crisLayoutMetric2Boxes) { + return crisLayoutMetric2Boxes.stream() + .map(CrisLayoutMetric2Box::getType) + .collect(Collectors.joining(", ")); + } + + private void buildTabPolicy(Workbook workbook, CrisLayoutTab tab) { + Sheet sheet = workbook.getSheet(TAB_POLICY_SHEET); + tab.getMetadataSecurityFields() + .forEach(metadataField -> + buildTabPolicyMetadataSecurityFieldRow(sheet, tab, metadataField) + ); + + tab.getGroupSecurityFields() + .forEach(group -> + buildTabPolicyGroupSecurityFieldRow(sheet, tab, group) + ); + } + + private void buildTabPolicyMetadataSecurityFieldRow(Sheet sheet, CrisLayoutTab tab, MetadataField metadataField) { + Row row = sheet.createRow(sheet.getLastRowNum() + 1); + createCell(row, 0, tab.getEntity().getLabel()); + createCell(row, 1, tab.getShortName()); + createCell(row, 2, metadataField.toString('.')); + createCell(row, 3, ""); + } + + private void buildTabPolicyGroupSecurityFieldRow(Sheet sheet, CrisLayoutTab tab, Group group) { + Row row = sheet.createRow(sheet.getLastRowNum() + 1); + createCell(row, 0, tab.getEntity().getLabel()); + createCell(row, 1, tab.getShortName()); + createCell(row, 2, ""); + createCell(row, 3, group.getName()); + } + + private void buildBoxPolicy(Workbook workbook, List boxes) { + Sheet sheet = workbook.getSheet(BOX_POLICY_SHEET); + boxes.forEach(box -> { + box.getMetadataSecurityFields() + .forEach(metadataField -> + buildBoxPolicyMetadataSecurityFieldRow(sheet, box, metadataField) + ); + + box.getGroupSecurityFields() + .forEach(group -> + buildBoxPolicyGroupSecurityFieldRow(sheet, box, group) + ); + }); + } + + private void buildBoxPolicyMetadataSecurityFieldRow(Sheet sheet, CrisLayoutBox box, MetadataField metadataField) { + Row row = sheet.createRow(sheet.getLastRowNum() + 1); + createCell(row, 0, box.getCell().getRow().getTab().getEntity().getLabel()); + createCell(row, 1, box.getShortname()); + createCell(row, 2, metadataField.toString('.')); + createCell(row, 3, ""); + } + + private void buildBoxPolicyGroupSecurityFieldRow(Sheet sheet, CrisLayoutBox box, Group group) { + Row row = sheet.createRow(sheet.getLastRowNum() + 1); + createCell(row, 0, box.getCell().getRow().getTab().getEntity().getLabel()); + createCell(row, 1, box.getShortname()); + createCell(row, 2, ""); + createCell(row, 3, group.getName()); + } + + private String convertToString(boolean value) { + return value ? "y" : "n"; + } + + private String toSecurity(Integer security) { + return String.valueOf(LayoutSecurity.valueOf(security)) + .replaceAll("_", " ") + .replaceAll("AND", "&"); + } + + private void autoSizeAllSheetsColumns(Workbook workbook) { + autoSizeColumns(workbook.getSheet(TAB_SHEET)); + autoSizeColumns(workbook.getSheet(TAB2BOX_SHEET)); + autoSizeColumns(workbook.getSheet(BOX_SHEET)); + autoSizeColumns(workbook.getSheet(BOX2METADATA_SHEET)); + autoSizeColumns(workbook.getSheet(METADATAGROUPS_SHEET)); + autoSizeColumns(workbook.getSheet(BOX2METRICS_SHEET)); + autoSizeColumns(workbook.getSheet(TAB_POLICY_SHEET)); + autoSizeColumns(workbook.getSheet(BOX_POLICY_SHEET)); + } + + private void autoSizeColumns(Sheet sheet) { + if (sheet.getPhysicalNumberOfRows() > 0) { + Row row = sheet.getRow(sheet.getFirstRowNum()); + for (Cell cell : row) { + int columnIndex = cell.getColumnIndex(); + sheet.autoSizeColumn(columnIndex); + } + } + } + +} diff --git a/dspace-api/src/main/java/org/dspace/layout/service/CrisLayoutBoxService.java b/dspace-api/src/main/java/org/dspace/layout/service/CrisLayoutBoxService.java index 072c85ca1176..1cf89eeb1fba 100644 --- a/dspace-api/src/main/java/org/dspace/layout/service/CrisLayoutBoxService.java +++ b/dspace-api/src/main/java/org/dspace/layout/service/CrisLayoutBoxService.java @@ -68,7 +68,6 @@ public List findByEntityType(Context context, String entityType, * @param context The relevant DSpace Context * @param box CrisLayoutBox instance * @param item the box's item - * @param values metadataValue of item * @return true if the box has content to show, false otherwise */ public boolean hasContent(Context context, CrisLayoutBox box, Item item); 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 619227432d7d..cdecadba5242 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 Map retrieveLicenses(String language) { 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 96f110c10196..c9c8127d1844 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 @@ private void removeLicenseField(Context context, Item item, String field) throws } - 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 Map retrieveFullAnswerMap(String licenseId, String langua } } - 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 void addLicense(Context context, Item item, String licenseUri, String lic 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/client/OrcidClient.java b/dspace-api/src/main/java/org/dspace/orcid/client/OrcidClient.java index 7186db31e351..ac4fdef84768 100644 --- a/dspace-api/src/main/java/org/dspace/orcid/client/OrcidClient.java +++ b/dspace-api/src/main/java/org/dspace/orcid/client/OrcidClient.java @@ -39,7 +39,6 @@ public interface OrcidClient { * 2-step OAuth. A single token can be used to register webhooks for multiple * records. * - * @param code the authorization code * @return the ORCID token * @throws OrcidClientException if some error occurs during the exchange */ @@ -222,7 +221,6 @@ public interface OrcidClient { * Perform an expanded search with the given query and pagination using the * public endpoint. * - * @param accessToken the access token * @param query the query * @param start the start index * @param rows the number of rows to retrieve 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 912083257636..6c521e2133db 100644 --- a/dspace-api/src/main/java/org/dspace/scripts/Process.java +++ b/dspace-api/src/main/java/org/dspace/scripts/Process.java @@ -229,15 +229,15 @@ public void addGroup(Group group) { } /** - * 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 4eb7cdbbc164..c8a7812a5159 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 ScriptConfiguration getScriptConfiguration(String name) { @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 fa8623f85dbe..0ae1311e697f 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/SolrLoggerServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/statistics/SolrLoggerServiceImpl.java @@ -209,7 +209,7 @@ public void postView(DSpaceObject dspaceObject, HttpServletRequest request, EPer public void postView(DSpaceObject dspaceObject, HttpServletRequest request, EPerson currentUser, Date time) { - if (solr == null || locationService == null) { + if (solr == null) { return; } initSolrYearCores(); @@ -250,7 +250,7 @@ public void postView(DSpaceObject dspaceObject, HttpServletRequest request, @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/content/StatisticsDatasetDisplay.java b/dspace-api/src/main/java/org/dspace/statistics/content/StatisticsDatasetDisplay.java index 941d4a33d314..75e18ec49e1f 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/content/StatisticsDatasetDisplay.java +++ b/dspace-api/src/main/java/org/dspace/statistics/content/StatisticsDatasetDisplay.java @@ -278,9 +278,14 @@ public String composeFilterQuery(String startDate, String endDate, boolean relat } //Creates query for usage raport generator - public String composeQueryWithInverseRelation(DSpaceObject dSpaceObject, List default_queries ) { + public String composeQueryWithInverseRelation(DSpaceObject dSpaceObject, List default_queries, int type) { StringBuilder query = new StringBuilder(); - query.append("{!join from=search.resourceid to=id fromIndex="); + if (type == Constants.BITSTREAM) { + query.append("{!join from=search.resourceid to=owningItem fromIndex="); + } else { + query.append("{!join from=search.resourceid to=id fromIndex="); + } + query.append(configurationService.getProperty("solr.multicorePrefix")); query.append("search} "); boolean isFirstDefaultQuery = true; diff --git a/dspace-api/src/main/java/org/dspace/statistics/service/WorkflowStatisticsService.java b/dspace-api/src/main/java/org/dspace/statistics/service/WorkflowStatisticsService.java index cce4a850bc6d..2f3cc6a0780b 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/service/WorkflowStatisticsService.java +++ b/dspace-api/src/main/java/org/dspace/statistics/service/WorkflowStatisticsService.java @@ -42,7 +42,6 @@ public interface WorkflowStatisticsService { * statistics, an empty Optional is returned. * * @param context the DSpace Context - * @param stepName the step name * @return the found statistics */ Optional findOwnerStatistics(Context context, UUID ownerId); 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 b1b31c0fe146..e45ce163ed77 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.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 @@ private static void updateSpiderFiles() { 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/S3BitStoreService.java b/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java index be4c5684f080..d6056028c7f3 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 @@ -151,8 +151,9 @@ public S3BitStoreService() {} * * @param s3Service AmazonS3 service */ - protected S3BitStoreService(AmazonS3 s3Service) { + protected S3BitStoreService(AmazonS3 s3Service, TransferManager tm) { this.s3Service = s3Service; + this.tm = tm; } @Override diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/ContentGenerator.java b/dspace-api/src/main/java/org/dspace/subscriptions/ContentGenerator.java index d5365c8cc2d4..f60ac3c98edb 100644 --- a/dspace-api/src/main/java/org/dspace/subscriptions/ContentGenerator.java +++ b/dspace-api/src/main/java/org/dspace/subscriptions/ContentGenerator.java @@ -5,17 +5,21 @@ * * 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.nio.charset.StandardCharsets; 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 javax.annotation.Resource; +import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.content.Item; import org.dspace.content.crosswalk.StreamDisseminationCrosswalk; @@ -29,62 +33,71 @@ import org.springframework.beans.factory.annotation.Autowired; + /** * Implementation class of SubscriptionGenerator * which will handle the logic of sending the emails - * in case of content subscriptions - * - * @author Alba Aliu + * in case of 'content' subscriptionType */ - +@SuppressWarnings("rawtypes") public class ContentGenerator implements SubscriptionGenerator { - private final Logger log = org.apache.logging.log4j.LogManager.getLogger(ContentGenerator.class); + + private final Logger log = LogManager.getLogger(ContentGenerator.class); + + @SuppressWarnings("unchecked") @Resource(name = "entityDissemination") - private Map mapEntityDisseminatorProperty = new HashMap(); + private Map entityType2Disseminator = new HashMap(); + @Autowired private ItemService itemService; @Override - public void notifyForSubscriptions(Context c, EPerson ePerson, List indexableComm, + public void notifyForSubscriptions(Context context, EPerson ePerson, + List indexableComm, List indexableColl, List indexableItems) { try { - // send the notification to the user - if (ePerson != null) { + if (Objects.nonNull(ePerson)) { Locale supportedLocale = I18nUtil.getEPersonLocale(ePerson); Email email = Email.getEmail(I18nUtil.getEmailFilename(supportedLocale, "subscriptions_content")); email.addRecipient(ePerson.getEmail()); - email.addArgument(generateHtmlBodyMail(c, indexableComm)); - email.addArgument(generateHtmlBodyMail(c, indexableColl)); - email.addArgument(generateHtmlBodyMail(c, indexableItems)); + email.addArgument(generateBodyMail(context, indexableComm)); + email.addArgument(generateBodyMail(context, indexableColl)); + email.addArgument(generateBodyMail(context, indexableItems)); email.send(); } - } catch (Exception ex) { - // log this email error - log.warn("cannot email user" + " eperson_id" + ePerson.getID() - + " eperson_email" + ePerson.getEmail()); + } catch (Exception e) { + log.error(e.getMessage(), e); + log.warn("Cannot email user eperson_id: {} eperson_email: {}", ePerson::getID, ePerson::getEmail); } } - private String generateHtmlBodyMail(Context context, List indexableObjects) { + private String generateBodyMail(Context context, List indexableObjects) { try { ByteArrayOutputStream out = new ByteArrayOutputStream(); - out.write("\n".getBytes(StandardCharsets.UTF_8)); + out.write("\n".getBytes(UTF_8)); if (indexableObjects.size() > 0) { for (IndexableObject indexableObject : indexableObjects) { - out.write("\n".getBytes(StandardCharsets.UTF_8)); + out.write("\n".getBytes(UTF_8)); Item item = (Item) indexableObject.getIndexedObject(); - mapEntityDisseminatorProperty.get(itemService.getEntityTypeLabel(item)).disseminate(context, item, - out); + 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(StandardCharsets.UTF_8)); + out.write("No items".getBytes(UTF_8)); } return out.toString(); } catch (Exception e) { - log.error(e.getMessage()); - return null; + 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/StatisticsGenerator.java b/dspace-api/src/main/java/org/dspace/subscriptions/StatisticsGenerator.java index 9bf162245986..c1f9be368e27 100644 --- a/dspace-api/src/main/java/org/dspace/subscriptions/StatisticsGenerator.java +++ b/dspace-api/src/main/java/org/dspace/subscriptions/StatisticsGenerator.java @@ -46,8 +46,7 @@ public class StatisticsGenerator implements SubscriptionGenerator { @Override public void notifyForSubscriptions(Context c, EPerson ePerson, List crisMetricsList, - List crisMetricsList1, - List crisMetricsList2) { + List crisMetricsList1, List crisMetricsList2) { // find statistics for all the subscribed objects try { // send the notification to the user diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotification.java b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotification.java index e7b32edf20ee..b429ecbd46e7 100644 --- a/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotification.java +++ b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotification.java @@ -5,33 +5,34 @@ * * 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 - > { +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); @@ -40,29 +41,29 @@ public SubscriptionEmailNotificationConfiguration @Override public void setup() throws ParseException { this.subscriptionEmailNotificationService = new DSpace().getServiceManager().getServiceByName( - SubscriptionEmailNotificationService.class.getName(), SubscriptionEmailNotificationService.class); + SubscriptionEmailNotificationServiceImpl.class.getName(), SubscriptionEmailNotificationServiceImpl.class); } @Override public void internalRun() throws Exception { assignCurrentUserInContext(); assignSpecialGroupsInContext(); - String typeOptions = commandLine.getOptionValue("t"); - String frequencyOptions = commandLine.getOptionValue("f"); - if (typeOptions == null || frequencyOptions == null) { - throw new IllegalArgumentException("Options type t and frequency f must be set"); + String frequencyOption = commandLine.getOptionValue("f"); + if (StringUtils.isBlank(frequencyOption)) { + throw new IllegalArgumentException("Option --frequency (-f) must be set"); } - if (!frequencyOptions.equals("D") && !frequencyOptions.equals("M") && !frequencyOptions.equals("W")) { - throw new IllegalArgumentException("Option f must be D, M or W"); + + 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, commandLine.getOptionValue("t"), commandLine.getOptionValue("f")); + subscriptionEmailNotificationService.perform(getContext(), handler, "content", frequencyOption); } - protected void assignCurrentUserInContext() throws SQLException { + private void assignCurrentUserInContext() throws SQLException { context = new Context(); UUID uuid = getEpersonIdentifier(); - if (uuid != null) { + if (Objects.nonNull(uuid)) { EPerson ePerson = EPersonServiceFactory.getInstance().getEPersonService().find(context, uuid); context.setCurrentUser(ePerson); } @@ -78,9 +79,8 @@ public SubscriptionEmailNotificationService getSubscriptionEmailNotificationServ return subscriptionEmailNotificationService; } - public void setSubscriptionEmailNotificationService(SubscriptionEmailNotificationService - subscriptionEmailNotificationService) { - this.subscriptionEmailNotificationService = subscriptionEmailNotificationService; + public void setSubscriptionEmailNotificationService(SubscriptionEmailNotificationService notificationService) { + this.subscriptionEmailNotificationService = notificationService; } public Context getContext() { @@ -90,4 +90,5 @@ public Context getContext() { 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 index 7647cef29410..338e7ff0e18b 100644 --- a/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationCli.java +++ b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationCli.java @@ -9,8 +9,6 @@ /** * Extension of {@link SubscriptionEmailNotification} for CLI. - * - * @author alba aliu at atis.al */ public class SubscriptionEmailNotificationCli extends SubscriptionEmailNotification { diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationCliScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationCliScriptConfiguration.java index 8783e3f54510..f0eb2fd5c83e 100644 --- a/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationCliScriptConfiguration.java +++ b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationCliScriptConfiguration.java @@ -9,9 +9,6 @@ /** * Extension of {@link SubscriptionEmailNotificationCli} for CLI. - * - * @author alba aliu at atis.al - * */ 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 index 9d45879f2c9d..52685b563d9b 100644 --- a/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationConfiguration.java +++ b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationConfiguration.java @@ -9,6 +9,7 @@ package org.dspace.subscriptions; import java.sql.SQLException; +import java.util.Objects; import org.apache.commons.cli.Options; import org.dspace.authorize.AuthorizeServiceImpl; @@ -19,10 +20,7 @@ /** * Implementation of {@link DSpaceRunnable} to find subscribed objects and send notification mails about them - * - * @author alba aliu */ - public class SubscriptionEmailNotificationConfiguration extends ScriptConfiguration { @@ -31,16 +29,6 @@ public class SubscriptionEmailNotificationConfiguration getDspaceRunnableClass() { - return dspaceRunnableClass; - } - - @Override - public void setDspaceRunnableClass(Class dspaceRunnableClass) { - this.dspaceRunnableClass = dspaceRunnableClass; - } - @Override public boolean isAllowedToExecute(Context context) { try { @@ -52,16 +40,24 @@ public boolean isAllowedToExecute(Context context) { @Override public Options getOptions() { - if (options == null) { + if (Objects.isNull(options)) { Options options = new Options(); - options.addOption("t", "Type", true, - "Subscription type, It can have values content or statistics"); - options.getOption("t").setRequired(true); - options.addOption("f", "Frequency", true, - "Subscription frequency. It can have value D, M or W"); + 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 index b73080c56676..95272235095a 100644 --- a/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationService.java +++ b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationService.java @@ -5,165 +5,33 @@ * * http://www.dspace.org/license/ */ - package org.dspace.subscriptions; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Comparator; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.stream.Collectors; +import java.util.Set; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.dspace.app.metrics.CrisMetrics; -import org.dspace.app.metrics.service.CrisMetricsService; -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.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.hibernate.proxy.HibernateProxy; -import org.hibernate.proxy.LazyInitializer; - /** - * Implementation of {@link DSpaceRunnable} to find subscribed objects and send notification mails about them + * Service interface class for the subscription e-mail notification services * - * @author alba aliu + * @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com) */ -public class SubscriptionEmailNotificationService { - private static final Logger log = LogManager.getLogger(SubscriptionEmailNotification.class); - public static final List FREQUENCIES = Arrays.asList("D", "W", "M"); - private final CrisMetricsService crisMetricsService; - private final SubscribeService subscribeService; - private Map contentUpdates = new HashMap<>(); - private Map generators = new HashMap<>(); - private List communities = new ArrayList<>(); - private List collections = new ArrayList<>(); - private List items = new ArrayList<>(); - - public void perform(Context context, DSpaceRunnableHandler handler, String type, String frequency) { - try { - context.turnOffAuthorisationSystem(); - List subscriptionList = findAllSubscriptionsByTypeAndFrequency(context, type, frequency); - // if content subscription - // Here is verified if type is "content" Or "statistics" as them are configured - if (type.equals(generators.keySet().toArray()[0])) { - // the list of the person who has subscribed - int iterator = 0; - for (Subscription subscription : subscriptionList) { - DSpaceObject dSpaceObject = getdSpaceObject(subscription); - if (dSpaceObject instanceof Community) { - communities.addAll(contentUpdates.get(Community.class.getSimpleName().toLowerCase(Locale.ROOT)) - .findUpdates(context, dSpaceObject, frequency)); - } else if (dSpaceObject instanceof Collection) { - collections.addAll(contentUpdates.get(Collection.class.getSimpleName().toLowerCase(Locale.ROOT)) - .findUpdates(context, dSpaceObject, frequency)); - } else if (dSpaceObject instanceof Item) { - items.addAll(contentUpdates.get(Item.class.getSimpleName().toLowerCase(Locale.ROOT)) - .findUpdates(context, dSpaceObject, frequency)); - } - if (iterator < subscriptionList.size() - 1) { - if (subscription.getePerson().equals(subscriptionList.get(iterator + 1).getePerson())) { - iterator++; - continue; - } else { - generators.get(type).notifyForSubscriptions(context, subscription.getePerson(), - communities, collections, items); - communities.clear(); - collections.clear(); - items.clear(); - } - } else { - //in the end of the iteration - generators.get(type).notifyForSubscriptions(context, subscription.getePerson(), - communities, collections, items); - } - iterator++; - } - } else { - if (!type.equals(generators.keySet().toArray()[1])) { - throw new IllegalArgumentException("Options type t and frequency f must be set correctly, " + - "type must be one of: " - + String.join(",", generators.keySet()) + " frequency one of: " - + String.join(", ", FREQUENCIES)); - } - int iterator = 0; - List crisMetricsList = new ArrayList<>(); - for (Subscription subscription : subscriptionList) { - try { - crisMetricsList.addAll(crisMetricsService.findAllByDSO(context, - subscription.getdSpaceObject())); - } catch (Exception e) { - log.error(e.getMessage()); - } - if (iterator < subscriptionList.size() - 1) { - if (subscription.getePerson().equals(subscriptionList.get(iterator + 1).getePerson())) { - iterator++; - continue; - } else { - generators.get(type).notifyForSubscriptions(context, subscription.getePerson(), - crisMetricsList, null, null); - } - } else { - //in the end of the iteration - generators.get(type).notifyForSubscriptions(context, subscription.getePerson(), - crisMetricsList, null, null); - } - iterator++; - } - } - } catch (Exception e) { - log.error(e.getMessage(), e); - handler.handleException(e); - context.abort(); - } finally { - context.restoreAuthSystemState(); - } - } - - private DSpaceObject getdSpaceObject(Subscription subscription) { - DSpaceObject dSpaceObject = subscription.getdSpaceObject(); - if (subscription.getdSpaceObject() instanceof HibernateProxy) { - HibernateProxy hibernateProxy = (HibernateProxy) subscription.getdSpaceObject(); - LazyInitializer initializer = hibernateProxy.getHibernateLazyInitializer(); - dSpaceObject = (DSpaceObject) initializer.getImplementation(); - } - return dSpaceObject; - } - - private List findAllSubscriptionsByTypeAndFrequency(Context context, String type, String frequency) { - try { - return this.subscribeService.findAllSubscriptionsByTypeAndFrequency(context, type, frequency) - .stream() - .sorted(Comparator.comparing(s -> s.getePerson().getID())) - .collect(Collectors.toList()); - } catch (SQLException sqlException) { - log.error(sqlException.getMessage()); - } - return null; - } - - public SubscriptionEmailNotificationService(CrisMetricsService crisMetricsService, - SubscribeService subscribeService, - Map generators, - Map contentUpdates) { - this.crisMetricsService = crisMetricsService; - this.subscribeService = subscribeService; - this.generators = generators; - this.contentUpdates = contentUpdates; - } +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 000000000000..2a30b89af3f5 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationServiceImpl.java @@ -0,0 +1,181 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.ITEM; +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<>(); + List items = 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 if (dSpaceObject.getType() == ITEM) { + List indexableCollectionItems = contentUpdates + .get(Item.class.getSimpleName().toLowerCase()) + .findUpdates(context, dSpaceObject, frequency); + items.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, items); + communityItems.clear(); + collectionsItems.clear(); + } + } else { + //in the end of the iteration + subscriptionType2generators.get(subscriptionType) + .notifyForSubscriptions(context, ePerson, communityItems, + collectionsItems, items); + } + 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/dSpaceObjectsUpdates/ItemsUpdates.java b/dspace-api/src/main/java/org/dspace/subscriptions/dSpaceObjectsUpdates/ItemsUpdates.java index bf0c1ab28e93..40fcd81dafa5 100644 --- a/dspace-api/src/main/java/org/dspace/subscriptions/dSpaceObjectsUpdates/ItemsUpdates.java +++ b/dspace-api/src/main/java/org/dspace/subscriptions/dSpaceObjectsUpdates/ItemsUpdates.java @@ -40,6 +40,7 @@ import org.dspace.discovery.indexobject.IndexableItem; import org.dspace.subscriptions.ContentGenerator; import org.dspace.subscriptions.service.DSpaceObjectUpdates; +import org.springframework.beans.factory.annotation.Autowired; /** @@ -49,10 +50,20 @@ * @author Alba Aliu */ public class ItemsUpdates implements DSpaceObjectUpdates { - private final CollectionService collectionService; - private final CommunityService communityService; - private final ItemService itemService; + + @Autowired + private CollectionService collectionService; + + @Autowired + private CommunityService communityService; + + @Autowired + private ItemService itemService; + + @Autowired private DiscoveryConfigurationService searchConfigurationService; + + @Autowired private SearchService searchService; private final Logger log = org.apache.logging.log4j.LogManager.getLogger(ContentGenerator.class); @@ -189,12 +200,4 @@ private DiscoverQuery buildBaseQuery(DiscoveryConfiguration discoveryConfigurati return discoverQuery; } - public ItemsUpdates(CollectionService collectionService, CommunityService communityService, ItemService itemService, - DiscoveryConfigurationService searchConfigurationService, SearchService searchService) { - this.collectionService = collectionService; - this.communityService = communityService; - this.itemService = itemService; - this.searchConfigurationService = searchConfigurationService; - this.searchService = searchService; - } } 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 000000000000..12d056f36800 --- /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 000000000000..0ae80d287aad --- /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 index 27f38b0da0f6..385fb31164a8 100644 --- a/dspace-api/src/main/java/org/dspace/subscriptions/service/DSpaceObjectUpdates.java +++ b/dspace-api/src/main/java/org/dspace/subscriptions/service/DSpaceObjectUpdates.java @@ -8,30 +8,37 @@ package org.dspace.subscriptions.service; import java.text.SimpleDateFormat; +import java.util.Arrays; import java.util.Calendar; 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 + * 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. + * Send an email to some addresses, concerning a Subscription, using a given dso. * * @param context current DSpace session. */ - public List findUpdates(Context context, DSpaceObject dSpaceObject - , String frequency) throws SearchServiceException; + @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); + } default String findLastFrequency(String frequency) { String startDate = ""; 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 index 42859064322a..994ada75b61b 100644 --- a/dspace-api/src/main/java/org/dspace/subscriptions/service/SubscriptionGenerator.java +++ b/dspace-api/src/main/java/org/dspace/subscriptions/service/SubscriptionGenerator.java @@ -12,15 +12,14 @@ import org.dspace.core.Context; import org.dspace.eperson.EPerson; - - /** - * Class which will be used to send - * email notifications to ePerson - * containing information for all list of objects + * 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, List items); + } \ 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 000000000000..52d5dacb74bb --- /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 000000000000..21a54f085f61 --- /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 000000000000..2dd5dad12a4d --- /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 000000000000..09cd0841e78f --- /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 000000000000..4f6b888d6082 --- /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 000000000000..8577ee8b1613 --- /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 000000000000..407a79c6899d --- /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 000000000000..0a3b6dae4b9c --- /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/validation/service/impl/ValidationServiceImpl.java b/dspace-api/src/main/java/org/dspace/validation/service/impl/ValidationServiceImpl.java index 10081bb914b5..b4c9b4bc4c1a 100644 --- a/dspace-api/src/main/java/org/dspace/validation/service/impl/ValidationServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/validation/service/impl/ValidationServiceImpl.java @@ -9,6 +9,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; import javax.annotation.PostConstruct; import org.dspace.app.util.SubmissionConfig; @@ -54,23 +55,51 @@ private void setup() throws SubmissionConfigReaderException { @Override public List validate(Context context, InProgressSubmission obj) { + SubmissionConfig submissionConfig = submissionConfigReader.getSubmissionConfigByInProgressSubmission(obj); + List errors = new ArrayList(); - SubmissionConfig submissionConfig = submissionConfigReader.getSubmissionConfigByInProgressSubmission(obj); + errors.addAll(notHiddenStepsValidations(context, obj, submissionConfig)); + errors.addAll(globalValidations(context, obj, submissionConfig)); + + return errors; + + } + + private List notHiddenStepsValidations(Context context, InProgressSubmission obj, + SubmissionConfig submissionConfig) { + + List errors = new ArrayList(); for (SubmissionStepConfig stepConfig : submissionConfig) { + + if (isStepHiddenOrReadOnly(stepConfig, obj)) { + continue; + } + stepValidators.stream() .filter(validation -> validation.getName().equals(stepConfig.getType())) .flatMap(validation -> validation.validate(context, obj, stepConfig).stream()) .forEach(errors::add); + } - globalValidators.stream() + return errors; + + } + + private List globalValidations(Context context, InProgressSubmission obj, + SubmissionConfig submissionConfig) { + + return globalValidators.stream() .flatMap(validator -> validator.validate(context, obj, submissionConfig).stream()) - .forEach(errors::add); + .collect(Collectors.toList()); - return errors; + } + private boolean isStepHiddenOrReadOnly(SubmissionStepConfig stepConfig, InProgressSubmission obj) { + return stepConfig.isHiddenForInProgressSubmission(obj) || stepConfig.isReadOnlyForInProgressSubmission(obj); } + } diff --git a/dspace-api/src/main/java/org/dspace/versioning/AbstractVersionProvider.java b/dspace-api/src/main/java/org/dspace/versioning/AbstractVersionProvider.java index 8d1b27e83546..318d59fd8732 100644 --- a/dspace-api/src/main/java/org/dspace/versioning/AbstractVersionProvider.java +++ b/dspace-api/src/main/java/org/dspace/versioning/AbstractVersionProvider.java @@ -125,7 +125,7 @@ public void setIgnoredMetadataFields(Set ignoredMetadataFields) { this.ignoredMetadataFields = ignoredMetadataFields; } - public Set getIgnoredMetadataFields() { + public Set getIgnoredMetadataFields() { if (ignoredMetadataFields == null) { return new HashSet<>(); } diff --git a/dspace-api/src/main/java/org/dspace/versioning/ItemCorrectionProvider.java b/dspace-api/src/main/java/org/dspace/versioning/ItemCorrectionProvider.java index dad879e0a5d5..a65ac8af97e1 100644 --- a/dspace-api/src/main/java/org/dspace/versioning/ItemCorrectionProvider.java +++ b/dspace-api/src/main/java/org/dspace/versioning/ItemCorrectionProvider.java @@ -11,6 +11,7 @@ import java.io.IOException; import java.sql.SQLException; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Set; @@ -24,7 +25,6 @@ import org.dspace.content.Collection; import org.dspace.content.Item; import org.dspace.content.MetadataField; -import org.dspace.content.MetadataSchema; import org.dspace.content.MetadataValue; import org.dspace.content.RelationshipMetadataValue; import org.dspace.content.WorkspaceItem; @@ -81,18 +81,15 @@ public WorkspaceItem createNewItemAndAddItInWorkspace(Context context, Collectio public XmlWorkflowItem updateNativeItemWithCorrection(Context context, XmlWorkflowItem workflowItem, Item correctionItem, Item nativeItem) throws AuthorizeException, IOException, SQLException { + Set ignoredMetadataFieldsOfCreation = + Sets.union(getIgnoredMetadataFields(), getIgnoredMetadataFieldsOfCreation()); Set ignoredMetadataFieldsOfMerging = Sets.union(getIgnoredMetadataFields(), getIgnoredMetadataFieldsOfMerging()); - // save entity type - MetadataValue entityType = itemService.getMetadata(nativeItem, "dspace", "entity", "type", Item.ANY).get(0); - // clear all metadata entries from native item - itemService.clearMetadata(context, nativeItem, Item.ANY, Item.ANY, Item.ANY, Item.ANY); + // clear all metadata that are not ignored inside the nativeItem + clearMetadataNotInSet(context, nativeItem, ignoredMetadataFieldsOfCreation); // copy metadata from corrected item to native item copyMetadata(context, nativeItem, correctionItem, ignoredMetadataFieldsOfMerging); - // restore entity type - itemService.addMetadata(context, nativeItem, entityType.getMetadataField(), entityType.getLanguage(), - entityType.getValue(), entityType.getAuthority(), entityType.getConfidence()); context.turnOffAuthorisationSystem(); // copy bundles and bitstreams of native item @@ -113,42 +110,82 @@ public XmlWorkflowItem updateNativeItemWithCorrection(Context context, XmlWorkfl context.restoreAuthSystemState(); - - - return workflowItem; } + private void clearMetadataNotInSet(Context context, Item nativeItem, Set ignoredMetadata) { + Set metadatasToClear = new HashSet<>(); + Iterator metadata = nativeItem.getMetadata().iterator(); + MetadataValue metadataValue = null; + while (!ignoredMetadata.isEmpty() && metadata.hasNext() && (metadataValue = metadata.next()) != null) { + if (!isIgnored(ignoredMetadata, metadataValue)) { + metadatasToClear.add(metadataValue.getMetadataField()); + } + } + if (!metadatasToClear.isEmpty()) { + for (MetadataField metadataField : metadatasToClear) { + try { + this.itemService.clearMetadata( + context, nativeItem, + metadataField.getMetadataSchema().getName(), + metadataField.getElement(), + metadataField.getQualifier(), + Item.ANY + ); + } catch (SQLException e) { + throw new RuntimeException( + "Cannot clear not ignored Metadata: " + metadataField.toString(), e + ); + } + } + } + } + private void copyMetadata(Context context, Item itemNew, Item nativeItem, Set ignoredMetadataFields) throws SQLException { - - List md = itemService.getMetadata(nativeItem, Item.ANY, Item.ANY, Item.ANY, Item.ANY); - for (MetadataValue aMd : md) { - MetadataField metadataField = aMd.getMetadataField(); - MetadataSchema metadataSchema = metadataField.getMetadataSchema(); - String unqualifiedMetadataField = metadataSchema.getName() + "." + metadataField.getElement(); - if (ignoredMetadataFields.contains(metadataField.toString('.')) || - ignoredMetadataFields.contains(unqualifiedMetadataField + "." + Item.ANY) || - aMd instanceof RelationshipMetadataValue) { + List metadataList = itemService.getMetadata(nativeItem, Item.ANY, Item.ANY, Item.ANY, Item.ANY); + for (MetadataValue metadataValue : metadataList) { + if (isRelationshipOrIgnored(ignoredMetadataFields, metadataValue)) { //Skip this metadata field (ignored and/or virtual) continue; } - + MetadataField metadataField = metadataValue.getMetadataField(); itemService.addMetadata( context, itemNew, metadataField.getMetadataSchema().getName(), metadataField.getElement(), metadataField.getQualifier(), - aMd.getLanguage(), - aMd.getValue(), - aMd.getAuthority(), - aMd.getConfidence(), - aMd.getPlace() + metadataValue.getLanguage(), + metadataValue.getValue(), + metadataValue.getAuthority(), + metadataValue.getConfidence(), + metadataValue.getPlace() ); } } + protected boolean isRelationshipOrIgnored(Set ignoredMetadataFields, MetadataValue metadataValue) { + return metadataValue instanceof RelationshipMetadataValue || isIgnored(ignoredMetadataFields, metadataValue); + } + + protected boolean isIgnored(Set ignoredMetadataFields, MetadataValue metadataValue) { + return isIgnoredWithQualifier(ignoredMetadataFields, metadataValue.getMetadataField()) || + isIgnoredAnyQualifier(ignoredMetadataFields, metadataValue.getMetadataField()); + } + + private boolean isIgnoredAnyQualifier(Set ignoredMetadataFields, MetadataField metadataField) { + return ignoredMetadataFields.contains(getUnqualifiedMetadata(metadataField)); + } + + private String getUnqualifiedMetadata(MetadataField metadataField) { + return metadataField.getMetadataSchema().getName() + "." + metadataField.getElement() + "." + Item.ANY; + } + + private boolean isIgnoredWithQualifier(Set ignoredMetadataFields, MetadataField metadataField) { + return ignoredMetadataFields.contains(metadataField.toString('.')); + } + protected void updateBundlesAndBitstreams(Context c, Item itemNew, Item nativeItem) throws SQLException, AuthorizeException, IOException { @@ -156,10 +193,25 @@ protected void updateBundlesAndBitstreams(Context c, Item itemNew, Item nativeIt List nativeBundles = nativeItem.getBundles(bundleName); List correctedBundles = itemNew.getBundles(bundleName); - if (CollectionUtils.isEmpty(nativeBundles) || CollectionUtils.isEmpty(correctedBundles)) { + if (CollectionUtils.isEmpty(nativeBundles) && CollectionUtils.isEmpty(correctedBundles)) { continue; } - updateBundleAndBitstreams(c, nativeBundles.get(0), correctedBundles.get(0)); + + Bundle nativeBundle; + if (CollectionUtils.isEmpty(nativeBundles)) { + nativeBundle = bundleService.create(c, nativeItem, bundleName); + } else { + nativeBundle = nativeBundles.get(0); + } + + Bundle correctedBundle; + if (CollectionUtils.isEmpty(correctedBundles)) { + correctedBundle = bundleService.create(c, nativeItem, bundleName); + } else { + correctedBundle = correctedBundles.get(0); + } + + updateBundleAndBitstreams(c, nativeBundle, correctedBundle); } } @@ -209,7 +261,26 @@ protected void updateBundleAndBitstreams(Context c, Bundle nativeBundle, Bundle } } + deleteBitstreams(nativeBundle, correctedBundle); bundleService.update(c, nativeBundle); + if (nativeBundle.getItems().isEmpty()) { + bundleService.delete(c, nativeBundle); + } + } + + private void deleteBitstreams(Bundle nativeBundle, Bundle correctedBundle) { + for (Bitstream bitstream : nativeBundle.getBitstreams()) { + if (contains(correctedBundle, bitstream)) { + continue; + } + nativeBundle.removeBitstream(bitstream); + } + } + + private boolean contains(Bundle bundle, Bitstream bitstream) { + return bundle.getBitstreams().stream() + .map(Bitstream::getChecksum) + .anyMatch(cs -> bitstream.getChecksum().equals(cs)); } protected Bitstream findBitstreamByChecksum(Bundle bundle, String bitstreamChecksum) { diff --git a/dspace-api/src/main/java/org/dspace/versioning/ItemCorrectionService.java b/dspace-api/src/main/java/org/dspace/versioning/ItemCorrectionService.java index b2ff194e04ee..4b8ba0871013 100644 --- a/dspace-api/src/main/java/org/dspace/versioning/ItemCorrectionService.java +++ b/dspace-api/src/main/java/org/dspace/versioning/ItemCorrectionService.java @@ -75,8 +75,6 @@ public ItemCorrectionService(String correctionRelationshipName) { * * @param context * the dspace context - * @param request - * the request containing the details about the workspace to create * @param itemUUID * the item UUID to use for creating the workspaceitem * @return the created workspaceitem @@ -101,8 +99,6 @@ public WorkspaceItem createWorkspaceItemByItem(Context context, UUID itemUUID) t * * @param context * the dspace context - * @param request - * the request containing the details about the workspace to create * @param itemUUID * the item UUID to use for creating the workspaceitem * @return the created workspaceitem 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 716b6cabd354..613c5821bcd1 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.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 WorkspaceItem sendWorkflowItemBackSubmission(Context c, T workflowItem, E 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 bfc5654cdd20..5b5ba5c1d3ba 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 void setScope(Scope scope) { 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 fcbd0b3d7d0f..56e710c51457 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/XmlWorkflowServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/XmlWorkflowServiceImpl.java @@ -1130,6 +1130,53 @@ public WorkspaceItem abort(Context c, XmlWorkflowItem wi, EPerson e) 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 0aabfab0573a..1cfa33b12170 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 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 @@ protected void addErrorField(HttpServletRequest request, String fieldName) { //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 000000000000..b49fdb34f869 --- /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 1dc61888b140..3475b04c7478 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 List getOptions() { 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 68a1e5d62cd0..bdf03591ae30 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 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.versioning.ItemCorrectionService; import org.dspace.xmlworkflow.factory.XmlWorkflowServiceFactory; @@ -36,8 +34,6 @@ */ 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 @@ -58,7 +54,7 @@ public ActionResult execute(Context c, XmlWorkflowItem wfi, Step step, HttpServl 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: @@ -74,33 +70,18 @@ public List getOptions() { 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) { @@ -117,26 +98,4 @@ public ActionResult processSubmitterIsDeletedPage(Context c, XmlWorkflowItem wfi } } - 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; - if (itemCorrectionService.checkIfIsCorrectionItem(c, wfi.getItem())) { - provDescription = getProvenanceStartId() + " Correction approved for entry into archive by " - + usersName + " on " + now + " (GMT) "; - } else { - 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 574401de5d91..d527c6ab3d9d 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,11 +14,8 @@ 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.versioning.ItemCorrectionService; -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; @@ -57,7 +54,7 @@ public ActionResult processMainPage(Context c, XmlWorkflowItem wfi, HttpServletR 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 @@ -72,31 +69,8 @@ public List getOptions() { 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; - if (itemCorrectionService.checkIfIsCorrectionItem(c, wfi.getItem())) { - provDescription = getProvenanceStartId() + " Correction approved for entry into archive by " - + usersName + " on " + now + " (GMT) "; - } else { - 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 8b8358a8d632..7a1c62adbd1e 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 boolean isAuthorized(Context context, HttpServletRequest request, XmlWork 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 e213f943e2be..ce755decb6ca 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 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.versioning.ItemCorrectionService; import org.dspace.xmlworkflow.factory.XmlWorkflowServiceFactory; @@ -38,8 +36,6 @@ 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"; @Autowired @@ -58,7 +54,7 @@ public ActionResult execute(Context c, XmlWorkflowItem wfi, Step step, HttpServl 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: @@ -73,56 +69,15 @@ public List getOptions() { 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; - if (itemCorrectionService.checkIfIsCorrectionItem(c, wfi.getItem())) { - provDescription = getProvenanceStartId() + " Correction approved for entry into archive by " - + usersName + " on " + now + " (GMT) "; - } else { - 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 a8346411114e..16d35b36683a 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.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 @@ */ public class ScoreEvaluationAction extends ProcessingAction { + // Minimum aggregate of scores private int minimumAcceptanceScore; @Override @@ -47,43 +50,64 @@ public void activate(Context c, XmlWorkflowItem wf) { @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 c28fe2d93ef8..43a3decacc7e 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 @@ 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 @@ * 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); + + // 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); - private static final String SUBMIT_SCORE = "submit_score"; + // 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 000000000000..5b97fe3195ae --- /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 28f21cc41859..0e8ab40a5205 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 @@ 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 @@ */ 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 void activate(Context c, XmlWorkflowItem wf) { @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.startsWith(SUBMIT_SELECT_REVIEWER)) { + 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); + } - } 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; + /** + * 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); } + } - int resultCount = ePersonService.searchResultCount(c, query); - List epeople = ePersonService.search(c, query, page * RESULTS_PER_PAGE, RESULTS_PER_PAGE); + 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); + } - 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); + 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; + } - //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); + 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 Role getRole() { 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 000000000000..7a86a0b03d1f --- /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 27cf98f77f06..b3fe896ace24 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.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 @@ * @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 List getOptions() { 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 51f4bf0a9301..401a7c506b98 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 ActionResult execute(Context c, XmlWorkflowItem wfi, Step step, HttpServl } //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 List getOptions() { 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 c9c61908aab6..21fcf6f30996 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 boolean isValidUserSelection(Context context, XmlWorkflowItem wfi, boolea 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/org/dspace/layout/script/service/impl/cris-layout-configuration-template.xls b/dspace-api/src/main/resources/org/dspace/layout/script/service/impl/cris-layout-configuration-template.xls new file mode 100644 index 000000000000..a921219abfad Binary files /dev/null and b/dspace-api/src/main/resources/org/dspace/layout/script/service/impl/cris-layout-configuration-template.xls differ 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 000000000000..703a9c605f99 --- /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,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/ +-- + +----------------------------------------------------------------------------------- +-- 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); +-- -- +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 000000000000..696e84433dcd --- /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/h2/V7.5_2022.12.09__Supervision_Orders_table.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.5_2022.12.09__Supervision_Orders_table.sql new file mode 100644 index 000000000000..33d3eb5c82c8 --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.5_2022.12.09__Supervision_Orders_table.sql @@ -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/ +-- + +------------------------------------------------------------------------------- +-- 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; 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 000000000000..9d13138fdada --- /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.5_2022.12.01__add_table_subscriptionparamter_change_columns_subscription_table.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.5_2022.12.01__add_table_subscriptionparamter_change_columns_subscription_table.sql new file mode 100644 index 000000000000..3862830230e3 --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.5_2022.12.01__add_table_subscriptionparamter_change_columns_subscription_table.sql @@ -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/ +-- + +----------------------------------------------------------------------------------- +-- 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 VARCHAR(255), + value VARCHAR(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); +-- +UPDATE subscription SET dspace_object_id = collection_id , type = 'content'; +-- +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); +-- +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/oracle/V7.5_2022.12.09__Supervision_Orders_table.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.5_2022.12.09__Supervision_Orders_table.sql new file mode 100644 index 000000000000..c7bb0b502ec2 --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.5_2022.12.09__Supervision_Orders_table.sql @@ -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/ +-- + +------------------------------------------------------------------------------- +-- 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; + +INSERT INTO supervision_orders (id, item_id, eperson_group_id) +SELECT supervision_orders_seq.nextval 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 + +DECLARE +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/oracle/V7.5_2022.12.15__system_wide_alerts.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.5_2022.12.15__system_wide_alerts.sql new file mode 100644 index 000000000000..9d13138fdada --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/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/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 000000000000..2d632b58004d --- /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,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/ +-- + +----------------------------------------------------------------------------------- +-- 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); +-- +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 000000000000..696e84433dcd --- /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 000000000000..f27a4f2a1bb6 --- /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 000000000000..9d13138fdada --- /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/spring/spring-dspace-addon-import-services.xml b/dspace-api/src/main/resources/spring/spring-dspace-addon-import-services.xml index b095470c7c56..e5b943c5c20f 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 @@ -42,6 +42,14 @@ + + + + + + @@ -136,7 +144,7 @@ - + diff --git a/dspace-api/src/test/data/dspaceFolder/assetstore/bulk-import/update-items.xls b/dspace-api/src/test/data/dspaceFolder/assetstore/bulk-import/update-items.xls index 01d923643cf0..2d30ccfd275f 100644 Binary files a/dspace-api/src/test/data/dspaceFolder/assetstore/bulk-import/update-items.xls and b/dspace-api/src/test/data/dspaceFolder/assetstore/bulk-import/update-items.xls differ diff --git a/dspace-api/src/test/data/dspaceFolder/assetstore/layout/script/export-valid-layout-with-3-tabs.xls b/dspace-api/src/test/data/dspaceFolder/assetstore/layout/script/export-valid-layout-with-3-tabs.xls new file mode 100644 index 000000000000..f8b2c0692f50 Binary files /dev/null and b/dspace-api/src/test/data/dspaceFolder/assetstore/layout/script/export-valid-layout-with-3-tabs.xls differ 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 a96fe550ac45..0b7def31ca3a 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/item-submission.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/item-submission.xml @@ -57,7 +57,6 @@ org.dspace.app.rest.submit.step.CollectionStep collection - submission submit.progressbar.describe.stepone @@ -245,12 +244,46 @@ correction workflow - + submit.progressbar.describe.stepone org.dspace.app.rest.submit.step.DescribeStep submission-form + + + submit.progressbar.identifiers + org.dspace.app.rest.submit.step.ShowIdentifiersStep + identifiers + + + + submit.progressbar.describe.stepone + org.dspace.app.rest.submit.step.DescribeStep + submission-form + workflow + + + + submit.progressbar.describe.stepone + org.dspace.app.rest.submit.step.DescribeStep + submission-form + submission + + + + + org.dspace.app.rest.submit.step.CollectionStep + collection + + + + + org.dspace.app.rest.submit.step.CollectionStep + collection + submission + + @@ -278,6 +311,8 @@ + + @@ -312,6 +347,13 @@ + + + + + + + diff --git a/dspace-api/src/test/data/dspaceFolder/config/local.cfg b/dspace-api/src/test/data/dspaceFolder/config/local.cfg index ec910dc9671a..08eb98710584 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/local.cfg +++ b/dspace-api/src/test/data/dspaceFolder/config/local.cfg @@ -211,3 +211,6 @@ management.health.solrOai.enabled = false logging.server.include-stacktrace-for-httpcode = 400, 401, 404, 403, 422 +# 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 000000000000..64512572ff73 --- /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/edititem-service.xml b/dspace-api/src/test/data/dspaceFolder/config/spring/api/edititem-service.xml index 95f341fac20c..787bb5ebdd5a 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/spring/api/edititem-service.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/spring/api/edititem-service.xml @@ -90,6 +90,15 @@ + + + + + ADMIN + + + + 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 206b801d0842..12a3b4e9565d 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 @@ -41,6 +41,12 @@ - + + + + + + + diff --git a/dspace-api/src/test/data/dspaceFolder/config/spring/api/iiif-processing.xml b/dspace-api/src/test/data/dspaceFolder/config/spring/api/iiif-processing.xml index 0683add603b8..ea2c6546085b 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/spring/api/iiif-processing.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/spring/api/iiif-processing.xml @@ -1,10 +1,12 @@ - + - + \ No newline at end of file diff --git a/dspace-api/src/test/data/dspaceFolder/config/spring/api/item-filters.xml b/dspace-api/src/test/data/dspaceFolder/config/spring/api/item-filters.xml new file mode 100644 index 000000000000..836d4f089677 --- /dev/null +++ b/dspace-api/src/test/data/dspaceFolder/config/spring/api/item-filters.xml @@ -0,0 +1,370 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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/scripts.xml b/dspace-api/src/test/data/dspaceFolder/config/spring/api/scripts.xml index 6a3830f616d6..0697423578bc 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/spring/api/scripts.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/spring/api/scripts.xml @@ -151,4 +151,9 @@ + + + + + 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 cc708f6895f4..53056cce78cb 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 @@ -16,9 +16,12 @@ - + + + + - + @@ -70,6 +73,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 1b6cdba6880e..d54893bbd146 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 @@ -169,6 +169,7 @@ + diff --git a/dspace-api/src/test/data/dspaceFolder/config/submission-forms.xml b/dspace-api/src/test/data/dspaceFolder/config/submission-forms.xml index 025561e92377..8ebe20c34f27 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/submission-forms.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/submission-forms.xml @@ -1955,7 +1955,7 @@ You can leave out the day and/or month if they aren't applicable. - +
@@ -1996,7 +1996,35 @@ You can leave out the day and/or month if they aren't applicable.
- + +
+ + + dc + title + + false + + onebox + Field required + + +
+ +
+ + + dc + type + + false + + onebox + Field required + + +
+ 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 000000000000..5d8d6ac594a6 --- /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 000000000000..b03d7576f991 --- /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 000000000000..f485a591b079 --- /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 000000000000..30a9100ad4a5 --- /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 a3e766eafcfa..c2a2e52d7db7 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 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.audit.AuditService; import org.dspace.app.metrics.service.CrisMetricsService; import org.dspace.app.nbevent.service.NBEventService; @@ -63,6 +64,8 @@ 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.utils.DSpace; import org.dspace.versioning.factory.VersionServiceFactory; import org.dspace.versioning.service.VersionHistoryService; @@ -128,6 +131,9 @@ public abstract class AbstractBuilder { static OrcidHistoryService orcidHistoryService; static OrcidQueueService orcidQueueService; static OrcidTokenService orcidTokenService; + static SystemWideAlertService systemWideAlertService; + static SupervisionOrderService supervisionOrderService; + protected Context context; @@ -198,6 +204,10 @@ public static void init() { 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(); } @@ -242,6 +252,9 @@ public static void destroy() { requestItemService = null; versioningService = null; orcidTokenService = null; + systemWideAlertService = null; + subscribeService = null; + supervisionOrderService = null; } public static void cleanupObjects() throws Exception { 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 f6cac1c14559..0665441b3d2b 100644 --- a/dspace-api/src/test/java/org/dspace/builder/ItemBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/ItemBuilder.java @@ -939,13 +939,18 @@ public void cleanup() throws Exception { 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(); + delete(c, item); } - } + c.complete(); + } } @Override diff --git a/dspace-api/src/test/java/org/dspace/builder/SubscribeBuilder.java b/dspace-api/src/test/java/org/dspace/builder/SubscribeBuilder.java index e3d23fc28301..5782ab73a565 100644 --- a/dspace-api/src/test/java/org/dspace/builder/SubscribeBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/SubscribeBuilder.java @@ -9,6 +9,7 @@ import java.sql.SQLException; import java.util.List; +import java.util.Objects; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -44,18 +45,27 @@ public void cleanup() throws Exception { // Ensure object and any related objects are reloaded before checking to see what needs cleanup subscription = c.reloadEntity(subscription); if (subscription != null) { - delete(subscription); + delete(c, subscription); } c.complete(); indexingService.commit(); } } - @Override - public void delete(Context c, Subscription subscription) throws Exception { - if (subscription != null) { - getService().deleteSubscription(c, subscription.getID()); + 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 @@ -76,7 +86,7 @@ public void delete(Subscription subscription) throws Exception { c.turnOffAuthorisationSystem(); Subscription subscription1 = c.reloadEntity(subscription); if (subscription1 != null) { - getService().deleteSubscription(c, subscription1.getID()); + getService().deleteSubscription(c, subscription1); } c.complete(); } @@ -103,4 +113,12 @@ private SubscribeBuilder create(Context context, String type, DSpaceObject dSpac } return this; } + + @Override + public void delete(Context c, Subscription dso) throws Exception { + if (Objects.nonNull(dso)) { + getService().deleteSubscription(c, dso); + } + } + } 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 000000000000..849e4cd4ffb5 --- /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 000000000000..cb6489815235 --- /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/checker/dao/impl/ChecksumHistoryDAOImplTest.java b/dspace-api/src/test/java/org/dspace/checker/dao/impl/ChecksumHistoryDAOImplTest.java index 860df5e887a5..d41537533b2f 100644 --- a/dspace-api/src/test/java/org/dspace/checker/dao/impl/ChecksumHistoryDAOImplTest.java +++ b/dspace-api/src/test/java/org/dspace/checker/dao/impl/ChecksumHistoryDAOImplTest.java @@ -83,6 +83,7 @@ public void testDeleteByDateAndCode() context.turnOffAuthorisationSystem(); bss.update(context, bs); context.restoreAuthSystemState(); + context.flush(); cal.add(Calendar.DATE, -1); Date matchDate = cal.getTime(); 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 aece739f25af..000000000000 --- 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 7ade9c582dc4..255b070e5eac 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 void testGetMatches() throws IOException, ClassNotFoundException { 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/enhancer/consumer/ItemEnhancerConsumerIT.java b/dspace-api/src/test/java/org/dspace/content/enhancer/consumer/ItemEnhancerConsumerIT.java index 5e95c28f65b7..eee35a81d045 100644 --- a/dspace-api/src/test/java/org/dspace/content/enhancer/consumer/ItemEnhancerConsumerIT.java +++ b/dspace-api/src/test/java/org/dspace/content/enhancer/consumer/ItemEnhancerConsumerIT.java @@ -10,6 +10,7 @@ import static org.dspace.app.matcher.MetadataValueMatcher.with; import static org.dspace.core.CrisConstants.PLACEHOLDER_PARENT_METADATA_VALUE; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasItem; @@ -290,6 +291,75 @@ public void testWithWorkspaceItem() throws Exception { } + @Test + @SuppressWarnings("unchecked") + public void testEnhancementAfterItemUpdate() throws Exception { + + context.turnOffAuthorisationSystem(); + + Item person = ItemBuilder.createItem(context, collection) + .withTitle("Walter White") + .withOrcidIdentifier("0000-0000-1111-2222") + .build(); + + String personId = person.getID().toString(); + + Item publication = ItemBuilder.createItem(context, collection) + .withTitle("Test publication") + .withEntityType("Publication") + .withAuthor("Jesse Pinkman") + .withAuthor("Saul Goodman") + .withAuthor("Walter White", person.getID().toString()) + .withAuthor("Gus Fring") + .build(); + + context.restoreAuthSystemState(); + publication = commitAndReload(publication); + + assertThat(getMetadataValues(publication, "dc.contributor.author"), contains( + with("dc.contributor.author", "Jesse Pinkman"), + with("dc.contributor.author", "Saul Goodman", 1), + with("dc.contributor.author", "Walter White", personId, 2, 600), + with("dc.contributor.author", "Gus Fring", 3))); + + assertThat(getMetadataValues(publication, "cris.virtual.author-orcid"), contains( + with("cris.virtual.author-orcid", PLACEHOLDER_PARENT_METADATA_VALUE), + with("cris.virtual.author-orcid", PLACEHOLDER_PARENT_METADATA_VALUE, 1), + with("cris.virtual.author-orcid", "0000-0000-1111-2222", 2), + with("cris.virtual.author-orcid", PLACEHOLDER_PARENT_METADATA_VALUE, 3))); + + assertThat(getMetadataValues(publication, "cris.virtualsource.author-orcid"), contains( + with("cris.virtualsource.author-orcid", PLACEHOLDER_PARENT_METADATA_VALUE), + with("cris.virtualsource.author-orcid", PLACEHOLDER_PARENT_METADATA_VALUE, 1), + with("cris.virtualsource.author-orcid", personId, 2), + with("cris.virtualsource.author-orcid", PLACEHOLDER_PARENT_METADATA_VALUE, 3))); + + context.turnOffAuthorisationSystem(); + itemService.addMetadata(context, publication, "dc", "title", "alternative", null, "Other name"); + itemService.update(context, publication); + context.restoreAuthSystemState(); + publication = commitAndReload(publication); + + assertThat(getMetadataValues(publication, "dc.contributor.author"), contains( + with("dc.contributor.author", "Jesse Pinkman"), + with("dc.contributor.author", "Saul Goodman", 1), + with("dc.contributor.author", "Walter White", personId, 2, 600), + with("dc.contributor.author", "Gus Fring", 3))); + + assertThat(getMetadataValues(publication, "cris.virtual.author-orcid"), contains( + with("cris.virtual.author-orcid", PLACEHOLDER_PARENT_METADATA_VALUE), + with("cris.virtual.author-orcid", PLACEHOLDER_PARENT_METADATA_VALUE, 1), + with("cris.virtual.author-orcid", "0000-0000-1111-2222", 2), + with("cris.virtual.author-orcid", PLACEHOLDER_PARENT_METADATA_VALUE, 3))); + + assertThat(getMetadataValues(publication, "cris.virtualsource.author-orcid"), contains( + with("cris.virtualsource.author-orcid", PLACEHOLDER_PARENT_METADATA_VALUE), + with("cris.virtualsource.author-orcid", PLACEHOLDER_PARENT_METADATA_VALUE, 1), + with("cris.virtualsource.author-orcid", personId, 2), + with("cris.virtualsource.author-orcid", PLACEHOLDER_PARENT_METADATA_VALUE, 3))); + + } + private MetadataValue getFirstMetadataValue(Item item, String metadataField) { return getMetadataValues(item, metadataField).get(0); } 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 681be214649c..6ea5bc5b784b 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 @@ -11,12 +11,15 @@ 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 static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import java.io.ByteArrayInputStream; +import java.io.InputStream; import java.sql.SQLException; import java.util.Comparator; import java.util.List; @@ -24,13 +27,20 @@ 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; @@ -40,6 +50,8 @@ 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.orcid.client.OrcidClient; import org.dspace.orcid.factory.OrcidServiceFactory; import org.dspace.orcid.factory.OrcidServiceFactoryImpl; @@ -666,6 +678,101 @@ public void testDeleteItemWithMultipleVersions() throws Exception { 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 f9e93e0df696..88610ea95943 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.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.Ignore; import org.junit.Test; @@ -39,10 +41,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(); @@ -50,11 +53,11 @@ public void testPerform() 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. @@ -78,4 +81,11 @@ public void testPerform() 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 000000000000..945dd481d00a --- /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 0a702fe576a3..db2be516ae49 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 @@ 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.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 void init() { 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 void testMintDOI() 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 void testMint_DOI_withNonMatchingFilter() 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 void testMint_DOI_withMatchingFilter() 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 void testCreate_and_Register_DOI() 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./. @@ -764,6 +761,104 @@ public void testDelete_all_DOIs() 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/iiif/MockIIIFApiQueryServiceImpl.java b/dspace-api/src/test/java/org/dspace/iiif/MockIIIFApiQueryServiceImpl.java index 6c52e3499c36..a240e76f9792 100644 --- a/dspace-api/src/test/java/org/dspace/iiif/MockIIIFApiQueryServiceImpl.java +++ b/dspace-api/src/test/java/org/dspace/iiif/MockIIIFApiQueryServiceImpl.java @@ -9,17 +9,12 @@ import org.dspace.content.Bitstream; - /** - * Simple Mock of the IIIF API Query Service to avoid the HTTP call - * - * @author Andrea Bollini (andrea.bollini at 4science.com) + * Mock for the IIIFApiQueryService. + * @author Michael Spalti (mspalti at willamette.edu) */ -public class MockIIIFApiQueryServiceImpl implements IIIFApiQueryService { - - @Override +public class MockIIIFApiQueryServiceImpl extends IIIFApiQueryServiceImpl { public int[] getImageDimensions(Bitstream bitstream) { - return null; + return new int[]{64, 64}; } - } diff --git a/dspace-api/src/test/java/org/dspace/layout/script/ExportCrisLayoutToolScriptIT.java b/dspace-api/src/test/java/org/dspace/layout/script/ExportCrisLayoutToolScriptIT.java new file mode 100644 index 000000000000..2d09eb48070b --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/layout/script/ExportCrisLayoutToolScriptIT.java @@ -0,0 +1,191 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.layout.script; + +import static org.dspace.app.launcher.ScriptLauncher.handleScript; +import static org.dspace.layout.script.service.CrisLayoutToolValidator.BOX2METADATA_SHEET; +import static org.dspace.layout.script.service.CrisLayoutToolValidator.BOX2METRICS_SHEET; +import static org.dspace.layout.script.service.CrisLayoutToolValidator.BOX_POLICY_SHEET; +import static org.dspace.layout.script.service.CrisLayoutToolValidator.BOX_SHEET; +import static org.dspace.layout.script.service.CrisLayoutToolValidator.METADATAGROUPS_SHEET; +import static org.dspace.layout.script.service.CrisLayoutToolValidator.TAB2BOX_SHEET; +import static org.dspace.layout.script.service.CrisLayoutToolValidator.TAB_POLICY_SHEET; +import static org.dspace.layout.script.service.CrisLayoutToolValidator.TAB_SHEET; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.hasSize; +import static org.junit.Assert.assertEquals; + +import java.io.File; +import java.io.FileInputStream; +import java.sql.SQLException; +import java.util.List; +import java.util.stream.Collectors; + +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.ss.usermodel.WorkbookFactory; +import org.dspace.AbstractIntegrationTestWithDatabase; +import org.dspace.app.launcher.ScriptLauncher; +import org.dspace.app.scripts.handler.impl.TestDSpaceRunnableHandler; +import org.dspace.authorize.AuthorizeException; +import org.dspace.builder.EntityTypeBuilder; +import org.dspace.builder.GroupBuilder; +import org.dspace.content.EntityType; +import org.dspace.layout.CrisLayoutTab; +import org.dspace.layout.factory.CrisLayoutServiceFactory; +import org.dspace.layout.service.CrisLayoutTabService; +import org.dspace.util.WorkbookUtils; +import org.junit.After; +import org.junit.Test; + +/** + * Integration tests for {@link ExportCrisLayoutToolScript}. + * + * @author Mohamed Eskander (mohamed.eskander at 4science.it) + * + */ +public class ExportCrisLayoutToolScriptIT extends AbstractIntegrationTestWithDatabase { + + private static final String BASE_XLS_DIR_PATH = "./target/testing/dspace/assetstore/layout/script"; + + private CrisLayoutTabService tabService = CrisLayoutServiceFactory.getInstance().getTabService(); + + @After + public void after() throws SQLException, AuthorizeException { + context.turnOffAuthorisationSystem(); + List tabs = tabService.findAll(context); + for (CrisLayoutTab tab : tabs) { + tabService.delete(context, tab); + } + context.restoreAuthSystemState(); + } + + @Test + public void testWithValidLayout() throws Exception { + context.turnOffAuthorisationSystem(); + createEntityType("Publication"); + createEntityType("Person"); + GroupBuilder.createGroup(context) + .withName("Researchers") + .build(); + context.restoreAuthSystemState(); + + String fileLocation = getXlsFilePath("export-valid-layout-with-3-tabs.xls"); + String[] args = new String[] { "cris-layout-tool", "-f", fileLocation }; + TestDSpaceRunnableHandler handler = new TestDSpaceRunnableHandler(); + + handleScript(args, ScriptLauncher.getConfig(kernelImpl), handler, kernelImpl, eperson); + assertThat(handler.getErrorMessages(), empty()); + assertThat(handler.getWarningMessages(), empty()); + + List infoMessages = handler.getInfoMessages(); + assertThat(infoMessages, hasSize(6)); + assertThat(infoMessages.get(0), containsString("The given workbook is valid. Proceed with the import")); + assertThat(infoMessages.get(1), containsString("The workbook has been parsed correctly, " + + "found 3 tabs to import")); + assertThat(infoMessages.get(2), containsString("Proceed with the clearing of the previous layout")); + assertThat(infoMessages.get(3), containsString("Found 0 tabs to delete")); + assertThat(infoMessages.get(4), containsString("The previous layout has been deleted, " + + "proceed with the import of the new configuration")); + assertThat(infoMessages.get(5), containsString("Import completed successfully")); + + assertThat(tabService.findAll(context), hasSize(3)); + + args = new String[] { "export-cris-layout-tool"}; + handleScript(args, ScriptLauncher.getConfig(kernelImpl), handler, kernelImpl, eperson); + + File importedFile = new File(fileLocation); + File exportedFile = new File("cris-layout-tool-exported.xls"); + exportedFile.deleteOnExit(); + + try (FileInputStream fisImported = new FileInputStream(importedFile); + FileInputStream fisExported = new FileInputStream(exportedFile)) { + Workbook importedWorkbook = WorkbookFactory.create(fisImported); + Workbook exportedWorkbook = WorkbookFactory.create(fisExported); + + Sheet importedTab = importedWorkbook.getSheet(TAB_SHEET); + Sheet exportedTab = exportedWorkbook.getSheet(TAB_SHEET); + + assertEqualsSheets(importedTab, exportedTab, 6); + + Sheet importedTab2Box = importedWorkbook.getSheet(TAB2BOX_SHEET); + Sheet exportedTab2Box = exportedWorkbook.getSheet(TAB2BOX_SHEET); + + assertEqualsSheets(importedTab2Box, exportedTab2Box, 6); + + Sheet importedBox = importedWorkbook.getSheet(BOX_SHEET); + Sheet exportedBOX = exportedWorkbook.getSheet(BOX_SHEET); + + assertEqualsSheets(importedBox, exportedBOX, 9); + + Sheet importedBox2Metadata = importedWorkbook.getSheet(BOX2METADATA_SHEET); + Sheet exportedBOX2Metadata = exportedWorkbook.getSheet(BOX2METADATA_SHEET); + + assertEqualsSheets(importedBox2Metadata, exportedBOX2Metadata, 16); + + + Sheet importedMetadataGroups = importedWorkbook.getSheet(METADATAGROUPS_SHEET); + Sheet exportedMetadataGroups = exportedWorkbook.getSheet(METADATAGROUPS_SHEET); + + assertEqualsSheets(importedMetadataGroups, exportedMetadataGroups, 10); + + + Sheet importedBox2Metrics = importedWorkbook.getSheet(BOX2METRICS_SHEET); + Sheet exportedBox2Metrics = exportedWorkbook.getSheet(BOX2METRICS_SHEET); + + assertEqualsSheets(importedBox2Metrics, exportedBox2Metrics, 3); + + + Sheet importedTabPolicy = importedWorkbook.getSheet(TAB_POLICY_SHEET); + Sheet exportedTabPolicy = exportedWorkbook.getSheet(TAB_POLICY_SHEET); + + assertEqualsSheets(importedTabPolicy, exportedTabPolicy, 4); + + Sheet importedBoxPolicy = importedWorkbook.getSheet(BOX_POLICY_SHEET); + Sheet exportedBOXPolicy = exportedWorkbook.getSheet(BOX_POLICY_SHEET); + + assertEqualsSheets(importedBoxPolicy, exportedBOXPolicy, 4); + } + + } + + private EntityType createEntityType(String entityType) { + return EntityTypeBuilder.createEntityTypeBuilder(context, entityType).build(); + } + + private String getXlsFilePath(String name) { + return new File(BASE_XLS_DIR_PATH, name).getAbsolutePath(); + } + + private void assertEqualsSheets(Sheet sheet1, Sheet sheet2, int cellsCount) { + assertEquals( + WorkbookUtils + .getRows(sheet1) + .map(row -> + convertToString(WorkbookUtils.getRowValues(row, cellsCount)) + ) + .sorted() + .collect(Collectors.toList()), + WorkbookUtils + .getRows(sheet2) + .map(row -> + convertToString(WorkbookUtils.getRowValues(row, cellsCount)) + ) + .sorted() + .collect(Collectors.toList()) + ); + } + + private String convertToString(List list) { + StringBuilder value = new StringBuilder(); + list.forEach(s -> value.append(s)); + return String.valueOf(value); + } +} 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 000000000000..4671e65d3875 --- /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/storage/bitstore/S3BitStoreServiceIntegrationTest.java b/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIT.java similarity index 99% rename from dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIntegrationTest.java rename to dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIT.java index f362e94dddc7..f757a746ab89 100644 --- a/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIntegrationTest.java +++ b/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIT.java @@ -60,7 +60,7 @@ /** * @author Luca Giamminonni (luca.giamminonni at 4science.com) */ -public class S3BitStoreServiceIntegrationTest extends AbstractIntegrationTestWithDatabase { +public class S3BitStoreServiceIT extends AbstractIntegrationTestWithDatabase { private static final String DEFAULT_BUCKET_NAME = "dspace-asset-localhost"; @@ -84,7 +84,7 @@ public void setup() throws Exception { amazonS3Client = createAmazonS3Client(); - s3BitStoreService = new S3BitStoreService(amazonS3Client); + s3BitStoreService = new S3BitStoreService(amazonS3Client, null); context.turnOffAuthorisationSystem(); 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 000000000000..60407823485b --- /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 69c4dc16f4b1..865abaca2152 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 @@ 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.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 void workflowUserRejectsItemTheySubmitted_ItemShouldBeEditable() throws E 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-iiif/pom.xml b/dspace-iiif/pom.xml index 359e6902e52f..97704a14dfef 100644 --- a/dspace-iiif/pom.xml +++ b/dspace-iiif/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - cris-2023.01.00-SNAPSHOT + cris-2023.01.01-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 ff8db8443271..716ecbb0f75c 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 @@ -81,29 +81,45 @@ public CanvasService(ConfigurationService configurationService) { } /** - * 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 6cc9cd3db5e9..1edb37d0da8d 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 @@ private void addCanvasAndRange(Context context, Item item, String manifestId) { List bundles = utils.getIIIFBundles(item); // Set the default canvas dimensions. if (guessCanvasDimension) { - canvasService.guessCanvasDimensions(bundles); + canvasService.guessCanvasDimensions(context, bundles); } int index = 1; for (Bundle bnd : bundles) { 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 1bdfb1196463..ad9025fae8dc 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 @@ -136,7 +136,7 @@ public List getIIIFBitstreams(Context context, Bundle bundle) { * @param b the DSpace bitstream to check * @return true if the bitstream can be used as IIIF resource */ - protected 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")); @@ -220,7 +220,7 @@ public String asJson(Resource resource) { * @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 236c7c88eb67..5c6050d48a4a 100644 --- a/dspace-oai/pom.xml +++ b/dspace-oai/pom.xml @@ -8,7 +8,7 @@ dspace-parent org.dspace - cris-2023.01.00-SNAPSHOT + cris-2023.01.01-SNAPSHOT .. diff --git a/dspace-oai/src/main/java/org/dspace/xoai/app/XOAI.java b/dspace-oai/src/main/java/org/dspace/xoai/app/XOAI.java index e27a3ee947cb..2db53bff9be5 100644 --- a/dspace-oai/src/main/java/org/dspace/xoai/app/XOAI.java +++ b/dspace-oai/src/main/java/org/dspace/xoai/app/XOAI.java @@ -36,6 +36,7 @@ import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.DefaultParser; import org.apache.commons.cli.Options; +import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.solr.client.solrj.SolrClient; @@ -81,7 +82,7 @@ */ @SuppressWarnings("deprecation") public class XOAI { - private static Logger log = LogManager.getLogger(XOAI.class); + private static final Logger log = LogManager.getLogger(XOAI.class); // needed because the solr query only returns 10 rows by default private final Context context; @@ -104,7 +105,7 @@ public class XOAI { private final static ConfigurationService configurationService = DSpaceServicesFactory.getInstance() .getConfigurationService(); - private List extensionPlugins; + private final List extensionPlugins; private List getFileFormats(Item item) { List formats = new ArrayList<>(); @@ -151,9 +152,9 @@ private void println(String line) { } public int index() throws DSpaceSolrIndexerException { - int result = 0; - try { + int result; + try { if (clean) { clearIndex(); System.out.println("Using full import."); @@ -169,8 +170,8 @@ public int index() throws DSpaceSolrIndexerException { } else { result = this.index((Date) results.get(0).getFieldValue("item.lastmodified")); } - } + solrServerResolver.getServer().commit(); if (optimize) { @@ -214,7 +215,7 @@ private int index(Date last) throws DSpaceSolrIndexerException, IOException { * @param last maximum date for an item to be considered for an update * @return Iterator over list of items which might have changed their visibility * since the last update. - * @throws DSpaceSolrIndexerException + * @throws DSpaceSolrIndexerException e */ private Iterator getItemsWithPossibleChangesBefore(Date last) throws DSpaceSolrIndexerException, IOException { try { @@ -365,7 +366,7 @@ private int index(Iterator iterator) throws DSpaceSolrIndexerException { * * @param item Item * @return date - * @throws SQLException + * @throws SQLException e */ private Date getMostRecentModificationDate(Item item) throws SQLException { List dates = new LinkedList<>(); @@ -398,7 +399,8 @@ private SolrInputDocument index(Item item) SolrInputDocument doc = new SolrInputDocument(); doc.addField("item.id", item.getID().toString()); - String handle = item.getHandle(); + String legacyOaiId = itemService.getMetadataFirstValue(item, "dspace", "legacy", "oai-identifier", Item.ANY); + String handle = StringUtils.isNotEmpty(legacyOaiId) ? legacyOaiId.split(":")[2] : item.getHandle(); doc.addField("item.handle", handle); boolean isEmbargoed = !this.isPublic(item); @@ -418,7 +420,7 @@ private SolrInputDocument index(Item item) * future will be marked as such. */ - boolean isPublic = isEmbargoed ? (isIndexed ? isCurrentlyVisible : false) : true; + boolean isPublic = !isEmbargoed || (isIndexed && isCurrentlyVisible); doc.addField("item.public", isPublic); // if the visibility of the item will change in the future due to an @@ -433,8 +435,7 @@ private SolrInputDocument index(Item item) * because this will override the item.public flag. */ - doc.addField("item.deleted", - (item.isWithdrawn() || !item.isDiscoverable() || (isEmbargoed ? isPublic : false))); + doc.addField("item.deleted", (item.isWithdrawn() || !item.isDiscoverable() || (isEmbargoed && isPublic))); /* * An item that is embargoed will potentially not be harvested by incremental @@ -574,8 +575,8 @@ private static void cleanCache(XOAIItemCacheService xoaiItemCacheService, XOAICa public static void main(String[] argv) throws IOException, ConfigurationException { - AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext( - new Class[] { BasicConfiguration.class }); + AnnotationConfigApplicationContext applicationContext = + new AnnotationConfigApplicationContext(BasicConfiguration.class); XOAICacheService cacheService = applicationContext.getBean(XOAICacheService.class); XOAIItemCacheService itemCacheService = applicationContext.getBean(XOAIItemCacheService.class); @@ -596,10 +597,9 @@ public static void main(String[] argv) throws IOException, ConfigurationExceptio String[] validDatabaseCommands = { COMMAND_CLEAN_CACHE, COMMAND_COMPILE_ITEMS, COMMAND_ERASE_COMPILED_ITEMS }; - boolean solr = true; // Assuming solr by default - solr = !("database").equals(configurationService.getProperty("oai.storage", "solr")); - + boolean solr = !("database").equals(configurationService.getProperty("oai.storage", "solr")); boolean run = false; + if (line.getArgs().length > 0) { if (solr) { if (Arrays.asList(validSolrCommands).contains(line.getArgs()[0])) { diff --git a/dspace-oai/src/main/java/org/dspace/xoai/filter/DSpaceAuthorizationFilter.java b/dspace-oai/src/main/java/org/dspace/xoai/filter/DSpaceAuthorizationFilter.java index ebb19c84b5e2..baf8552a6029 100644 --- a/dspace-oai/src/main/java/org/dspace/xoai/filter/DSpaceAuthorizationFilter.java +++ b/dspace-oai/src/main/java/org/dspace/xoai/filter/DSpaceAuthorizationFilter.java @@ -9,12 +9,17 @@ package org.dspace.xoai.filter; import java.sql.SQLException; +import java.util.Iterator; +import java.util.List; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.factory.AuthorizeServiceFactory; import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.Item; +import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.content.service.ItemService; import org.dspace.core.Constants; import org.dspace.handle.factory.HandleServiceFactory; import org.dspace.handle.service.HandleService; @@ -33,6 +38,9 @@ public class DSpaceAuthorizationFilter extends DSpaceFilter { private static final HandleService handleService = HandleServiceFactory.getInstance().getHandleService(); + private static final ItemService itemService + = ContentServiceFactory.getInstance().getItemService(); + @Override public boolean isShown(DSpaceItem item) { boolean pub = false; @@ -43,6 +51,11 @@ public boolean isShown(DSpaceItem item) { return false; } Item dspaceItem = (Item) handleService.resolveToObject(context, handle); + + if (dspaceItem == null) { + dspaceItem = fromLegacyIdentifier(item); + } + if (dspaceItem == null) { return false; } @@ -55,6 +68,25 @@ public boolean isShown(DSpaceItem item) { return pub; } + private Item fromLegacyIdentifier(DSpaceItem item) { + List legacyIdentifier = item.getMetadata("dspace.legacy.oai-identifier"); + if (legacyIdentifier.isEmpty()) { + return null; + } + try { + Iterator + iterator = itemService.findUnfilteredByMetadataField( + context, "dspace", "legacy", "oai-identifier", + legacyIdentifier.get(0)); + if (!iterator.hasNext()) { + return null; + } + return iterator.next(); + } catch (AuthorizeException | SQLException e) { + throw new RuntimeException(e); + } + } + @Override public SolrFilterResult buildSolrQuery() { return new SolrFilterResult("item.public:true"); diff --git a/dspace-rdf/pom.xml b/dspace-rdf/pom.xml index 8e6e09f94f69..c6d887b773c0 100644 --- a/dspace-rdf/pom.xml +++ b/dspace-rdf/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - cris-2023.01.00-SNAPSHOT + cris-2023.01.01-SNAPSHOT .. diff --git a/dspace-rest/pom.xml b/dspace-rest/pom.xml index ce6a94b64cb9..6a5945560682 100644 --- a/dspace-rest/pom.xml +++ b/dspace-rest/pom.xml @@ -3,7 +3,7 @@ org.dspace dspace-rest war - cris-2023.01.00-SNAPSHOT + cris-2023.01.01-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 - cris-2023.01.00-SNAPSHOT + cris-2023.01.01-SNAPSHOT .. diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index 6cbbb9b3a9d2..5ad15c1097b5 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - cris-2023.01.00-SNAPSHOT + cris-2023.01.01-SNAPSHOT .. @@ -503,11 +503,6 @@ org.apache.commons commons-collections4 - - org.apache.commons - commons-text - 1.9 - commons-validator commons-validator @@ -601,13 +596,6 @@ solr-core ${solr.client.version} test - - - - org.apache.commons - commons-text - - org.apache.lucene @@ -630,6 +618,10 @@ 2.0.7 test + + javax.annotation + javax.annotation-api + diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/Application.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/Application.java index c85efed835bf..02fd8ad0166a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/Application.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/Application.java @@ -12,6 +12,7 @@ import java.util.List; import javax.servlet.Filter; +import org.apache.commons.lang3.ArrayUtils; import org.dspace.app.rest.filter.DSpaceRequestContextFilter; import org.dspace.app.rest.model.hateoas.DSpaceLinkRelationProvider; import org.dspace.app.rest.parameter.resolver.SearchFilterResolver; @@ -187,9 +188,28 @@ public void addCorsMappings(@NonNull CorsRegistry registry) { .getCorsAllowedOrigins(configuration.getCorsAllowedOriginsConfig()); String[] iiifAllowedOrigins = configuration .getCorsAllowedOrigins(configuration.getIiifAllowedOriginsConfig()); + String[] bitstreamAllowedOrigins = configuration + .getCorsAllowedOrigins(configuration.getBitstreamAllowedOriginsConfig()); boolean corsAllowCredentials = configuration.getCorsAllowCredentials(); boolean iiifAllowCredentials = configuration.getIiifAllowCredentials(); + boolean bitstreamAllowCredentials = configuration.getBitstreamsAllowCredentials(); + + if (ArrayUtils.isEmpty(bitstreamAllowedOrigins)) { + bitstreamAllowedOrigins = corsAllowedOrigins; + } + if (!ArrayUtils.isEmpty(bitstreamAllowedOrigins)) { + registry.addMapping("/api/core/bitstreams/**").allowedMethods(CorsConfiguration.ALL) + // Set Access-Control-Allow-Credentials to "true" and specify which origins are valid + // for our Access-Control-Allow-Origin header + .allowCredentials(bitstreamAllowCredentials).allowedOrigins(bitstreamAllowedOrigins) + // Allow list of request preflight headers allowed to be sent to us from the client + .allowedHeaders("Accept", "Authorization", "Content-Type", "Origin", "X-On-Behalf-Of", + "X-Requested-With", "X-XSRF-TOKEN", "X-CORRELATION-ID", "X-REFERRER", + "x-recaptcha-token", "Access-Control-Allow-Origin") + // Allow list of response headers allowed to be sent by us (the server) to the client + .exposedHeaders("Authorization", "DSPACE-XSRF-TOKEN", "Location", "WWW-Authenticate"); + } if (corsAllowedOrigins != null) { registry.addMapping("/api/**").allowedMethods(CorsConfiguration.ALL) // Set Access-Control-Allow-Credentials to "true" and specify which origins are valid 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 74121c85820e..46be77ea5511 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 @@ -278,7 +278,7 @@ public AuthenticationTokenResource shortLivedTokenViaGet(HttpServletRequest requ } /** - * 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 d48bc04c109d..000000000000 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/IdentifierRestController.java +++ /dev/null @@ -1,111 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.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 { - if (identifierService.isGone(context, id)) { - response.setStatus(HttpServletResponse.SC_GONE); - } 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/OpenSearchController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/OpenSearchController.java index 6a760b7d0857..6b41efb9b5f5 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.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 void search(HttpServletRequest request, 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.getDiscoveryConfigurationByNameOrDefault(""); + 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 f2f1ae31f530..353e2b9957b2 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.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 @@ default String getDescription() { * @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 2b04bb983c29..941c614fcb37 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 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 01385d443565..766204364c73 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.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 000000000000..7b1493d56d89 --- /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 index 631b6679fa89..fb1398448691 100644 --- 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 @@ -7,6 +7,8 @@ */ package org.dspace.app.rest.authorization.impl; +import static org.dspace.core.Constants.READ; + import java.sql.SQLException; import java.util.Objects; @@ -19,26 +21,26 @@ import org.dspace.app.rest.utils.Utils; import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.DSpaceObject; -import org.dspace.core.Constants; 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 dataspace object + * 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 dataspace object") + 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 AuthorizeService authorizeService; + @Autowired private Utils utils; + @Autowired + private AuthorizeService authorizeService; @Override @SuppressWarnings("rawtypes") @@ -47,8 +49,7 @@ public boolean isAuthorized(Context context, BaseObjectRest object) throws SQLEx return false; } DSpaceObject dSpaceObject = (DSpaceObject) utils.getDSpaceAPIObjectFromRest(context, object); - return authorizeService.authorizeActionBoolean(context, context.getCurrentUser(), - dSpaceObject, Constants.READ, true); + return authorizeService.authorizeActionBoolean(context, context.getCurrentUser(), dSpaceObject, READ, true); } @Override @@ -59,4 +60,5 @@ public String[] getSupportedTypes() { ItemRest.CATEGORY + "." + ItemRest.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 000000000000..5c605daaf407 --- /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 000000000000..1e8af26a8232 --- /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, 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/AInprogressItemConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AInprogressItemConverter.java index 74a3c7264e75..248342e1b19b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AInprogressItemConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AInprogressItemConverter.java @@ -101,6 +101,10 @@ protected void fillFromModel(T obj, R witem, Projection projection) { for (SubmissionSectionRest sections : def.getPanels()) { SubmissionStepConfig stepConfig = submissionSectionConverter.toModel(sections); + if (stepConfig.isHiddenForInProgressSubmission(obj)) { + continue; + } + /* * First, load the step processing class (using the current * class loader) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/EditItemConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/EditItemConverter.java index eb2dedef3e62..24c8761268c5 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/EditItemConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/EditItemConverter.java @@ -101,6 +101,10 @@ protected void fillFromModel(EditItem obj, EditItemRest rest, Projection project for (SubmissionSectionRest sections : def.getPanels()) { SubmissionStepConfig stepConfig = submissionSectionConverter.toModel(sections); + if (stepConfig.isHiddenForInProgressSubmission(obj)) { + continue; + } + /* * First, load the step processing class (using the current * class loader) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ItemConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ItemConverter.java index 3f1490f99792..f386979ff9cd 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ItemConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ItemConverter.java @@ -54,7 +54,7 @@ public ItemRest convert(Item obj, Projection projection) { * When the context is null, it will return the metadatalist as for an anonymous user * Overrides the parent method to include virtual metadata * @param context The context - * @param obj The object of which the filtered metadata will be retrieved6 + * @param item The object of which the filtered metadata will be retrieved * @param projection The projection(s) used into current request * @return A list of object metadata (including virtual metadata) filtered based on the the hidden metadata * configuration 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 000000000000..44800f6e5035 --- /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 000000000000..3dd8f0b3b754 --- /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 index bc89116c261e..cd491ecb17bd 100644 --- 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 @@ -41,11 +41,10 @@ public SubscriptionRest convert(Subscription subscription, Projection projection SubscriptionParameterRest subscriptionParameterRest = new SubscriptionParameterRest(); subscriptionParameterRest.setName(subscriptionParameter.getName()); subscriptionParameterRest.setValue(subscriptionParameter.getValue()); - subscriptionParameterRest.setId(subscriptionParameter.getId()); subscriptionParameterRestList.add(subscriptionParameterRest); } rest.setSubscriptionParameterList(subscriptionParameterRestList); - rest.setType(subscription.getType()); + rest.setSubscriptionType(subscription.getSubscriptionType()); return rest; } 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 000000000000..e9ffb224468b --- /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 000000000000..419f2cf1d16b --- /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 ee6479433e35..f905bbf1b335 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 WorkflowActionRest convert(WorkflowActionConfig modelObject, Projection p restModel.setProjection(projection); restModel.setId(modelObject.getId()); restModel.setOptions(modelObject.getOptions()); + if (modelObject.isAdvanced()) { + restModel.setAdvancedOptions(modelObject.getAdvancedOptions()); + restModel.setAdvancedInfo(modelObject.getAdvancedInfo()); + } return restModel; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/DSpaceApiExceptionControllerAdvice.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/DSpaceApiExceptionControllerAdvice.java index edefd7434e76..72133e3de530 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/DSpaceApiExceptionControllerAdvice.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/DSpaceApiExceptionControllerAdvice.java @@ -137,7 +137,7 @@ protected void handleUnprocessableEntityException(HttpServletRequest request, Ht Exception ex) throws IOException { //422 is not defined in HttpServletResponse. Its meaning is "Unprocessable Entity". //Using the value from HttpStatus. - sendErrorResponse(request, response, null, + sendErrorResponse(request, response, ex, "Unprocessable or invalid entity", HttpStatus.UNPROCESSABLE_ENTITY.value()); } @@ -145,7 +145,7 @@ protected void handleUnprocessableEntityException(HttpServletRequest request, Ht @ExceptionHandler( {InvalidSearchRequestException.class}) protected void handleInvalidSearchRequestException(HttpServletRequest request, HttpServletResponse response, Exception ex) throws IOException { - sendErrorResponse(request, response, null, + sendErrorResponse(request, response, ex, "Invalid search request", HttpStatus.UNPROCESSABLE_ENTITY.value()); } @@ -184,7 +184,7 @@ protected void handleCustomUnprocessableEntityException(HttpServletRequest reque TranslatableException ex) throws IOException { Context context = ContextUtil.obtainContext(request); sendErrorResponse( - request, response, null, ex.getLocalizedMessage(context), HttpStatus.UNPROCESSABLE_ENTITY.value() + request, response, (Exception) ex, ex.getLocalizedMessage(context), HttpStatus.UNPROCESSABLE_ENTITY.value() ); } @@ -200,7 +200,7 @@ protected ResponseEntity handleCustomUnprocessableEditException(HttpServ protected void ParameterConversionException(HttpServletRequest request, HttpServletResponse response, Exception ex) throws IOException { // we want the 400 status for missing parameters, see https://jira.lyrasis.org/browse/DS-4428 - sendErrorResponse(request, response, null, + sendErrorResponse(request, response, ex, "A required parameter is invalid", HttpStatus.BAD_REQUEST.value()); } @@ -209,7 +209,7 @@ protected void ParameterConversionException(HttpServletRequest request, HttpServ protected void MissingParameterException(HttpServletRequest request, HttpServletResponse response, Exception ex) throws IOException { // we want the 400 status for missing parameters, see https://jira.lyrasis.org/browse/DS-4428 - sendErrorResponse(request, response, null, + sendErrorResponse(request, response, ex, "A required parameter is missing", HttpStatus.BAD_REQUEST.value()); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AdvancedInfoRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AdvancedInfoRest.java new file mode 100644 index 000000000000..4fbee25c38b6 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AdvancedInfoRest.java @@ -0,0 +1,37 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.model; + +import org.dspace.xmlworkflow.state.actions.ActionAdvancedInfo; + +/** + * Abstract class for {@link ActionAdvancedInfo} + * + * @author Marie Verdonck (Atmire) on 03/02/23 + */ +public abstract class AdvancedInfoRest { + + String id; + String type; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/IdentifierRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/IdentifierRest.java new file mode 100644 index 000000000000..6cfb147ea314 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/IdentifierRest.java @@ -0,0 +1,122 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import org.dspace.app.rest.RestResourceController; + +/** + * Implementation of IdentifierRest REST resource, representing some DSpace identifier + * for use with the REST API + * + * @author Kim Shepherd + */ +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 000000000000..169e40979c98 --- /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 ea9d173e1f2c..ed14790f737e 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 @@ name = ItemRest.BUNDLES, method = "getBundles" ), + @LinkRest( + name = ItemRest.IDENTIFIERS, + method = "getIdentifiers" + ), @LinkRest( name = ItemRest.MAPPED_COLLECTIONS, method = "getMappedCollections" @@ -61,6 +65,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 000000000000..14644be151ac --- /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 000000000000..86b2003b07ec --- /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 index 71e3c47fb73b..1f09f61c7cf4 100644 --- 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 @@ -8,16 +8,20 @@ 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() {} public SubscriptionParameterRest(Integer id, String name, String value, Subscription subscription) { this.id = id; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubscriptionRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubscriptionRest.java index bbb66ab5be48..6b45d5ce7ab8 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubscriptionRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubscriptionRest.java @@ -5,7 +5,6 @@ * * http://www.dspace.org/license/ */ - package org.dspace.app.rest.model; import java.util.ArrayList; @@ -13,20 +12,23 @@ import org.dspace.app.rest.RestResourceController; -@LinksRest(links = {@LinkRest(name = SubscriptionRest.DSPACE_OBJECT, - method = "getDSpaceObject"), @LinkRest( - name = SubscriptionRest.EPERSON, - method = "getEPerson") + +@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 = "dSpaceObject"; - public static final String EPERSON = "ePerson"; + public static final String DSPACE_OBJECT = "resource"; + public static final String EPERSON = "eperson"; private Integer id; - private String type; + private String subscriptionType; private List subscriptionParameterList = new ArrayList<>(); @Override @@ -35,7 +37,7 @@ public String getCategory() { } @Override - public Class getController() { + public Class getController() { return RestResourceController.class; } @@ -44,8 +46,8 @@ public String getType() { return NAME; } - public void setType(String type) { - this.type = type; + public void setSubscriptionType(String type) { + this.subscriptionType = type; } public List getSubscriptionParameterList() { @@ -57,7 +59,7 @@ public void setSubscriptionParameterList(List subscri } public String getSubscriptionType() { - return this.type; + return this.subscriptionType; } @Override @@ -69,4 +71,5 @@ public Integer getId() { 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 000000000000..e114fdeb39f2 --- /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 000000000000..995ec8e93404 --- /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/UsageReportCategoryRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/UsageReportCategoryRest.java index ad50f72708bf..5872db6d3c61 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/UsageReportCategoryRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/UsageReportCategoryRest.java @@ -72,7 +72,7 @@ public String getCategoryType() { /** * Sets the category type of this UsageReportCategory * - * @param reportType The category type of this UsageReportCategory + * @param categoryType The category type of this UsageReportCategory */ public void setCategoryType(String categoryType) { this.categoryType = categoryType; 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 e998df6bc2bc..07a2c36cff96 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 @@ 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 String getType() { 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 3fac0eeef158..f31dbfa9dcf8 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 @@ * * @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 000000000000..e25f04f6c91c --- /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 000000000000..2c6453d8cb11 --- /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/SupervisionOrderResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SupervisionOrderResource.java new file mode 100644 index 000000000000..06439f29efe4 --- /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 000000000000..9089103d2bdb --- /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 000000000000..01e0eabdd380 --- /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 97e6866073f6..137952866959 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.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 AuthorizationRest findOne(Context context, String id) { // restore the real current user context.restoreContextUser(); } - } catch (SQLException e) { + } catch (SQLException | SearchServiceException e) { throw new RuntimeException(e.getMessage(), e); } @@ -151,7 +152,7 @@ public AuthorizationRest findOne(Context context, String id) { @SearchRestMethod(name = "object") public Page 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 @@ private List findAuthorizationsForUri( Context context, EPerson user, String uri, - String featureName) throws SQLException { + String featureName) throws SQLException, SearchServiceException { BaseObjectRest restObject = utils.getBaseObjectRestFromUri(context, uri); return authorizationsForObject(context, user, featureName, restObject); @@ -244,7 +245,7 @@ private List authorizationsForObject( Context context, EPerson user, String featureName, BaseObjectRest obj) - throws SQLException { + throws SQLException, SearchServiceException { if (obj == null) { return new ArrayList<>(); @@ -269,7 +270,7 @@ private List authorizationsForObject( private List 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 01277ff29b19..8ffefb619b47 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 Page findAll(Context context, Pageable pageable) { } } + /** + * Find a browse index by a list of fields (first match will be returned) + * @param fields + * @return + * @throws SQLException + */ + @SearchRestMethod(name = "byFields") + public BrowseIndexRest findByFields(@Parameter(value = "fields", required = true) String[] fields) + throws SQLException { + BrowseIndexRest bi = null; + BrowseIndex bix = null; + try { + // Find the browse index definition that matches any field - once found, return + for (String field : fields) { + CrossLinks cl = new CrossLinks(); + if (cl.hasLink(field)) { + // Get the index name for this + String browseIndexName = cl.getLinkType(field); + bix = BrowseIndex.getBrowseIndex(browseIndexName); + break; + } + } + } catch (BrowseException e) { + throw new RuntimeException(e.getMessage(), e); + } + if (bix != null) { + bi = converter.toRest(bix, utils.obtainProjection()); + } + return bi; + } + @Override public Class getDomainClass() { return BrowseIndexRest.class; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/IdentifierRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/IdentifierRestRepository.java new file mode 100644 index 000000000000..1be569d18e5d --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/IdentifierRestRepository.java @@ -0,0 +1,306 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the 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.hateoas.server.mvc.WebMvcLinkBuilder.linkTo; + +import java.io.IOException; +import java.net.URI; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.UUID; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.NotSupportedException; + +import org.atteo.evo.inflector.English; +import org.dspace.app.rest.DiscoverableEndpointsService; +import org.dspace.app.rest.Parameter; +import org.dspace.app.rest.SearchRestMethod; +import org.dspace.app.rest.exception.DSpaceBadRequestException; +import org.dspace.app.rest.exception.LinkNotFoundException; +import org.dspace.app.rest.exception.RESTAuthorizationException; +import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException; +import org.dspace.app.rest.exception.UnprocessableEntityException; +import org.dspace.app.rest.model.DSpaceObjectRest; +import org.dspace.app.rest.model.IdentifierRest; +import org.dspace.app.rest.repository.handler.service.UriListHandlerService; +import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.DSpaceObject; +import org.dspace.content.Item; +import org.dspace.content.logic.TrueFilter; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +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.IdentifierNotFoundException; +import org.dspace.identifier.IdentifierNotResolvableException; +import org.dspace.identifier.factory.IdentifierServiceFactory; +import org.dspace.identifier.service.DOIService; +import org.dspace.identifier.service.IdentifierService; +import org.dspace.services.factory.DSpaceServicesFactory; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.hateoas.Link; +import org.springframework.hateoas.TemplateVariable; +import org.springframework.hateoas.TemplateVariables; +import org.springframework.hateoas.UriTemplate; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; +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; + +/** + * Item REST Repository and Controller for persistent identifiers. + * The controller annotation and endpoint registration allows the "find DSO by identifier" method which was + * previously implmented in org.dspace.app.rest.IdentifierRestController + * + * @author Kim Shepherd + */ +@RestController +@RequestMapping("/api/" + IdentifierRestRepository.CATEGORY) +@Component(IdentifierRestRepository.CATEGORY + "." + IdentifierRestRepository.NAME) +public class IdentifierRestRepository extends 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 000000000000..0714b7329bff --- /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 e65debe33d84..3fbd6c9d9163 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 @@ -30,6 +30,7 @@ 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.authorize.service.AuthorizeService; import org.dspace.content.DSpaceObject; @@ -60,12 +61,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 RequestItemRest put(Context context, HttpServletRequest request, 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 RequestItemRest put(Context context, HttpServletRequest request, 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/SubmissionCCLicenseRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionCCLicenseRestRepository.java index 0dab42f9bd04..356b3f22bfc4 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 index a57812222307..95c4714e9cae 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionDSpaceObjectLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionDSpaceObjectLinkRepository.java @@ -8,58 +8,42 @@ 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.authorize.AuthorizeException; -import org.dspace.content.Collection; -import org.dspace.content.Community; -import org.dspace.content.Item; -import org.dspace.core.Context; import org.dspace.eperson.Subscription; import org.dspace.eperson.service.SubscribeService; -import org.hibernate.proxy.HibernateProxy; -import org.hibernate.proxy.LazyInitializer; 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 "dataSpaceObject" of subscription + * Link repository for "DSpaceObject" of subscription */ @Component(SubscriptionRest.CATEGORY + "." + SubscriptionRest.NAME + "." + SubscriptionRest.DSPACE_OBJECT) public class SubscriptionDSpaceObjectLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { @Autowired - SubscribeService subscribeService; + private SubscribeService subscribeService; - - public DSpaceObjectRest getDSpaceObject(@Nullable HttpServletRequest request, - Integer subscriptionId, - @Nullable Pageable optionalPageable, - Projection projection) throws AuthorizeException { + @PreAuthorize("hasPermission(#subscriptionId, 'subscription', 'READ')") + public DSpaceObjectRest getDSpaceObject(@Nullable HttpServletRequest request, Integer subscriptionId, + @Nullable Pageable optionalPageable, Projection projection) { try { - Context context = obtainContext(); - Subscription subscription = subscribeService.findById(context, subscriptionId); - if (subscription == null) { + Subscription subscription = subscribeService.findById(obtainContext(), subscriptionId); + if (Objects.isNull(subscription)) { throw new ResourceNotFoundException("No such subscription: " + subscriptionId); } - if (subscription.getdSpaceObject() instanceof Item || subscription.getdSpaceObject() instanceof Community || - subscription.getdSpaceObject() instanceof Collection) { - return converter.toRest(subscription.getdSpaceObject(), projection); - } else { - HibernateProxy hibernateProxy = (HibernateProxy) subscription.getdSpaceObject(); - LazyInitializer initializer = hibernateProxy.getHibernateLazyInitializer(); - return converter.toRest(initializer.getImplementation(), projection); - } + return converter.toRest(subscription.getDSpaceObject(), projection); } catch (SQLException e) { - throw new RuntimeException(e); - } catch (AuthorizeException e) { - throw new AuthorizeException(e.getMessage()); + 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 index f25dd6b23376..dcf612e52daa 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionEPersonLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionEPersonLinkRepository.java @@ -8,19 +8,19 @@ 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.authorize.AuthorizeException; -import org.dspace.core.Context; 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; /** @@ -30,24 +30,20 @@ public class SubscriptionEPersonLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { @Autowired - SubscribeService subscribeService; + private SubscribeService subscribeService; - public EPersonRest getEPerson(@Nullable HttpServletRequest request, - Integer subscriptionId, - @Nullable Pageable optionalPageable, - Projection projection) throws AuthorizeException { + @PreAuthorize("hasPermission(#subscriptionId, 'subscription', 'READ')") + public EPersonRest getEPerson(@Nullable HttpServletRequest request, Integer subscriptionId, + @Nullable Pageable optionalPageable, Projection projection) { try { - Context context = obtainContext(); - Subscription subscription = subscribeService.findById(context, subscriptionId); - if (subscription == null) { + Subscription subscription = subscribeService.findById(obtainContext(), subscriptionId); + if (Objects.isNull(subscription)) { throw new ResourceNotFoundException("No such subscription: " + subscriptionId); } - - return converter.toRest(subscription.getePerson(), projection); + return converter.toRest(subscription.getEPerson(), projection); } catch (SQLException e) { - throw new RuntimeException(e); - } catch (AuthorizeException e) { - throw new AuthorizeException(e.getMessage()); + 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 index 6f534b4a23c0..19970e828409 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java @@ -7,159 +7,141 @@ */ 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.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 javax.ws.rs.BadRequestException; 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.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.model.patch.Patch; -import org.dspace.app.rest.repository.patch.ResourcePatch; 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.Constants; 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.eperson.service.SubscriptionParameterService; +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 Alba Aliu at atis.al + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.com) */ - @Component(SubscriptionRest.CATEGORY + "." + SubscriptionRest.NAME) -public class SubscriptionRestRepository extends DSpaceRestRepository - implements LinkRestRepository { - private static final Logger log = LogManager.getLogger(); - @Autowired - AuthorizeService authorizeService; +public class SubscriptionRestRepository extends DSpaceRestRepository + implements InitializingBean { + @Autowired - SubscribeService subscribeService; + private ConverterService converter; @Autowired - SubscriptionParameterService subscriptionParameterService; + private EPersonService ePersonService; @Autowired - protected ConverterService converter; + private AuthorizeService authorizeService; @Autowired - protected EPersonService personService; + private SubscribeService subscribeService; @Autowired private DSpaceObjectUtils dspaceObjectUtil; @Autowired - private ResourcePatch resourcePatch; + private DiscoverableEndpointsService discoverableEndpointsService; + @Autowired + private SubscriptionEmailNotificationService subscriptionEmailNotificationService; @Override - @PreAuthorize("isAuthenticated()") + @PreAuthorize("hasPermission(#id, 'subscription', 'READ')") public SubscriptionRest findOne(Context context, Integer id) { + Subscription subscription = null; try { - Subscription subscription = subscribeService.findById(context, id); - if (subscription == null) { - throw new ResourceNotFoundException("The subscription for ID: " + id + " could not be found"); - } - return converter.toRest(subscription, utils.obtainProjection()); - } catch (SQLException sqlException) { - throw new RuntimeException(sqlException.getMessage(), sqlException); - } catch (AuthorizeException authorizeException) { - throw new RuntimeException(authorizeException.getMessage()); + 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 { - HttpServletRequest req = getRequestService().getCurrentRequest().getHttpServletRequest(); - String resourceType = req.getParameter("resourceType"); - List subscriptionList = subscribeService.findAll(context, resourceType, - pageable.getPageSize(), Math.toIntExact(pageable.getOffset())); + 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()); + throw new RuntimeException(e.getMessage(), e); } } - @SearchRestMethod(name = "findByEPerson") - @PreAuthorize("isAuthenticated()") - public Page findAllSubscriptionsByEPerson(String id, Pageable pageable) throws Exception { + @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 = personService.findByIdOrLegacyId(context, id); - if (context.getCurrentUser().equals(ePerson) - || authorizeService.isAdmin(context, context.getCurrentUser())) { - List subscriptionList = subscribeService.getSubscriptionsByEPerson(context, - ePerson, pageable.getPageSize(), Math.toIntExact(pageable.getOffset())); - Long total = subscribeService.countAllByEPerson(context, ePerson); - return converter.toRestPage(subscriptionList, pageable, total, utils.obtainProjection()); - } else { - throw new AuthorizeException("Only admin or e-person themselves can search for it's subscription"); - } - } catch (SQLException sqlException) { - throw new SQLException(sqlException.getMessage(), sqlException); - - } catch (AuthorizeException authorizeException) { - throw new AuthorizeException(authorizeException.getMessage()); + 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()); } - @PreAuthorize("isAuthenticated()") @SearchRestMethod(name = "findByEPersonAndDso") - public Page findByEPersonAndDso(Pageable pageable) throws Exception { + @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(); - HttpServletRequest req = getRequestService().getCurrentRequest().getHttpServletRequest(); - String epersonId = req.getParameter("eperson_id"); - String dsoId = req.getParameter("dspace_object_id"); - DSpaceObject dSpaceObject = dspaceObjectUtil.findDSpaceObject(context, UUID.fromString(dsoId)); - EPerson ePerson = personService.findByIdOrLegacyId(context, epersonId); - // dso must always be set - if (dsoId == null || epersonId == null) { - throw new UnprocessableEntityException("error parsing the body"); - } - if (context.getCurrentUser().equals(ePerson) - || authorizeService.isAdmin(context, context.getCurrentUser())) { - List subscriptionList = - subscribeService.getSubscriptionsByEPersonAndDso(context, ePerson, dSpaceObject, - pageable.getPageSize(), Math.toIntExact(pageable.getOffset())); - Long total = subscribeService.countAllByEPersonAndDSO(context, ePerson, dSpaceObject); - return converter.toRestPage(subscriptionList, pageable, total, - utils.obtainProjection()); - } else { - throw new AuthorizeException("Only admin or e-person themselves can search for it's subscription"); - } - } catch (SQLException sqlException) { - throw new SQLException(sqlException.getMessage(), sqlException); - - } catch (AuthorizeException authorizeException) { - throw new AuthorizeException(authorizeException.getMessage()); + 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 @@ -167,152 +149,128 @@ public Page findByEPersonAndDso(Pageable pageable) throws Exce protected SubscriptionRest createAndReturn(Context context) throws SQLException, AuthorizeException { HttpServletRequest req = getRequestService().getCurrentRequest().getHttpServletRequest(); String epersonId = req.getParameter("eperson_id"); - String dsoId = req.getParameter("dspace_object_id"); - // dso must always be set - if (dsoId == null || epersonId == null) { - throw new UnprocessableEntityException("error parsing the body"); + String dsoId = req.getParameter("resource"); + + if (StringUtils.isBlank(dsoId) || StringUtils.isBlank(epersonId)) { + throw new UnprocessableEntityException("Both eperson than DSpaceObject uuids must be provieded!"); } - ObjectMapper mapper = new ObjectMapper(); - SubscriptionRest subscriptionRest = null; + try { DSpaceObject dSpaceObject = dspaceObjectUtil.findDSpaceObject(context, UUID.fromString(dsoId)); - EPerson ePerson = personService.findByIdOrLegacyId(context, epersonId); - if (ePerson == null || dSpaceObject == null) { - throw new BadRequestException("Id of person or dspace object must represents reals ids"); + 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 dataspace object - if (!authorizeService.authorizeActionBoolean(context, ePerson, dSpaceObject, Constants.READ, true)) { + + // 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 do not make this control, + + // 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"); } } - ServletInputStream input = req.getInputStream(); - subscriptionRest = mapper.readValue(input, SubscriptionRest.class); + Subscription subscription = null; + ServletInputStream input = req.getInputStream(); + SubscriptionRest subscriptionRest = new ObjectMapper().readValue(input, SubscriptionRest.class); List subscriptionParameterList = subscriptionRest.getSubscriptionParameterList(); - if (subscriptionParameterList != null) { + if (CollectionUtils.isNotEmpty(subscriptionParameterList)) { List subscriptionParameters = new ArrayList<>(); - for (SubscriptionParameterRest subscriptionParameterRest : subscriptionParameterList) { - SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); - subscriptionParameter.setName(subscriptionParameterRest.getName()); - subscriptionParameter.setValue(subscriptionParameterRest.getValue()); - subscriptionParameters.add(subscriptionParameter); - } - subscription = subscribeService.subscribe(context, ePerson, - dSpaceObject, - subscriptionParameters, - subscriptionRest.getSubscriptionType()); + validateParameters(subscriptionRest, subscriptionParameterList, subscriptionParameters); + subscription = subscribeService.subscribe(context, ePerson, dSpaceObject, subscriptionParameters, + subscriptionRest.getSubscriptionType()); } context.commit(); return converter.toRest(subscription, utils.obtainProjection()); } catch (SQLException sqlException) { throw new SQLException(sqlException.getMessage(), sqlException); - - } catch (AuthorizeException authorizeException) { - throw new AuthorizeException(authorizeException.getMessage()); } 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("isAuthenticated()") + @PreAuthorize("hasPermission(#id, 'subscription', 'WRITE')") protected SubscriptionRest put(Context context, HttpServletRequest request, String apiCategory, String model, - Integer id, JsonNode jsonNode) throws SQLException, AuthorizeException { - HttpServletRequest req = getRequestService().getCurrentRequest().getHttpServletRequest(); - String epersonId = req.getParameter("eperson_id"); - String dsoId = req.getParameter("dspace_object_id"); - SubscriptionRest subscriptionRest = null; - DSpaceObject dSpaceObject = null; - EPerson ePerson = null; + 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()); + throw new UnprocessableEntityException("Error parsing subscription json: " + e.getMessage(), e); } - String notFoundException = "ResourceNotFoundException:" + apiCategory + "." + model - + " with id: " + id + " not found"; - Subscription subscription; - try { - subscription = subscribeService.findById(context, id); - if (subscription == null) { - throw new ResourceNotFoundException(notFoundException); - } - dSpaceObject = dspaceObjectUtil.findDSpaceObject(context, UUID.fromString(dsoId)); - ePerson = personService.findByIdOrLegacyId(context, epersonId); - if (dSpaceObject == null || ePerson == null) { - throw new ResourceNotFoundException(notFoundException); - } - } catch (SQLException e) { - throw new ResourceNotFoundException(notFoundException); - } catch (AuthorizeException e) { - throw new AuthorizeException(e.getMessage()); + + 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 subscriptionParameterList = new ArrayList<>(); - for (SubscriptionParameterRest subscriptionParameterRest : - subscriptionRest.getSubscriptionParameterList()) { - SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); - subscriptionParameter.setSubscription(subscription); - subscriptionParameter.setValue(subscriptionParameterRest.getValue()); - subscriptionParameter.setName(subscriptionParameterRest.getName()); - subscriptionParameterList.add(subscriptionParameter); - } - subscription = subscribeService.updateSubscription(context, id, ePerson, - dSpaceObject, subscriptionParameterList, subscriptionRest.getSubscriptionType()); + 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()); + throw new IllegalArgumentException("The id in the Json and the id in the url do not match: " + id + ", " + + subscription.getID()); } } @Override - @PreAuthorize("isAuthenticated()") - public void patch(Context context, HttpServletRequest request, String apiCategory, - String model, Integer id, Patch patch) - throws UnprocessableEntityException, DSpaceBadRequestException, AuthorizeException { - Subscription subscription = null; + @PreAuthorize("hasPermission(#id, 'subscription', 'DELETE')") + protected void delete(Context context, Integer id) { try { - subscription = subscribeService.findById(context, id); - if (subscription == null) { - throw new ResourceNotFoundException(apiCategory + "." + model + " with id: " + id + " not found"); - } - if (!authorizeService.isAdmin(context) || subscription.getePerson().equals(context.getCurrentUser())) { - throw new AuthorizeException("Only admin or e-person themselves can edit the subscription"); + Subscription subscription = subscribeService.findById(context, id); + if (Objects.isNull(subscription)) { + throw new ResourceNotFoundException(CATEGORY + "." + NAME + " with id: " + id + " not found"); } - resourcePatch.patch(context, subscription, patch.getOperations()); + subscribeService.deleteSubscription(context, subscription); } catch (SQLException e) { - throw new RuntimeException(e.getMessage(), e); - } catch (AuthorizeException authorizeException) { - throw new AuthorizeException(authorizeException.getMessage()); - } catch (RuntimeException runtimeException) { - throw new RuntimeException(runtimeException.getMessage()); + throw new RuntimeException("Unable to delete Subscription with id = " + id, e); } } @Override - @PreAuthorize("isAuthenticated()") - public void delete(Context context, Integer id) throws AuthorizeException { - try { - subscribeService.deleteSubscription(context, id); - } catch (SQLException sqlException) { - throw new RuntimeException(sqlException.getMessage(), sqlException); - } catch (AuthorizeException authorizeException) { - throw new AuthorizeException(authorizeException.getMessage()); - } + public Class getDomainClass() { + return SubscriptionRest.class; } @Override - public Class getDomainClass() { - return SubscriptionRest.class; + 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 000000000000..fb2f589dbc2a --- /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 000000000000..73544145b20f --- /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 3f16217d7657..585fc1de9f0b 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 @@ protected VersionRest createAndReturn(Context context, List stringList) } EPerson submitter = item.getSubmitter(); - boolean isAdmin = authorizeService.isAdmin(context); + boolean isAdmin = authorizeService.isAdmin(context, item); boolean canCreateVersion = configurationService.getBooleanProperty("versioning.submitterCanCreateNewVersion"); if (!isAdmin && !(canCreateVersion && Objects.equals(submitter, context.getCurrentUser()))) { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsChildrenLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsChildrenLinkRepository.java index 7607ea3d6c0d..d96e43c79aac 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsChildrenLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsChildrenLinkRepository.java @@ -46,7 +46,7 @@ public class VocabularyEntryDetailsChildrenLinkRepository extends AbstractDSpace @Autowired private AuthorityUtils authorityUtils; - @PreAuthorize("hasAuthority('AUTHENTICATED')") + @PreAuthorize("permitAll()") public Page 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 cf36b57ba22e..0b91b3ecf681 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 @@ -41,7 +41,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 40de9b982ea6..31e8d7d3b926 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 @@ -67,7 +67,7 @@ public Page findAll(Context context, Pageable pageab throw new RepositoryMethodNotImplementedException(ResourcePolicyRest.NAME, "findAll"); } - @PreAuthorize("hasAuthority('AUTHENTICATED')") + @PreAuthorize("permitAll()") @Override public VocabularyEntryDetailsRest findOne(Context context, String name) { String[] parts = StringUtils.split(name, ":", 2); @@ -96,7 +96,7 @@ public VocabularyEntryDetailsRest findOne(Context context, String name) { } @SearchRestMethod(name = "top") - @PreAuthorize("hasAuthority('AUTHENTICATED')") + @PreAuthorize("permitAll()") public Page 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/VocabularyRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyRestRepository.java index 3d71abff8338..e95cefb9f04d 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 @@ -54,7 +54,7 @@ public class VocabularyRestRepository extends DSpaceRestRepository findItemRestById(Context context, String itemId) throws SQLException { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemSupervisionOrdersLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemSupervisionOrdersLinkRepository.java new file mode 100644 index 000000000000..e0d57ae0de52 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemSupervisionOrdersLinkRepository.java @@ -0,0 +1,61 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.repository; + +import java.sql.SQLException; +import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; + +import org.dspace.app.rest.model.SupervisionOrderRest; +import org.dspace.app.rest.model.WorkspaceItemRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.content.WorkspaceItem; +import org.dspace.content.service.WorkspaceItemService; +import org.dspace.core.Context; +import org.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; + +/** + * Link repository for the supervision orders of an WorkspaceItem + * + * @author Mohamed Eskander (mohamed.eskander at 4science dot it) + */ +@Component(WorkspaceItemRest.CATEGORY + "." + WorkspaceItemRest.NAME + "." + WorkspaceItemRest.SUPERVISION_ORDERS) +public class WorkspaceItemSupervisionOrdersLinkRepository + extends AbstractDSpaceRestRepository implements LinkRestRepository { + + @Autowired + WorkspaceItemService workspaceItemService; + + @Autowired + SupervisionOrderService supervisionOrderService; + + @PreAuthorize("hasAuthority('ADMIN')") + public Page 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/repository/patch/operation/DSpaceObjectMetadataAddOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/DSpaceObjectMetadataAddOperation.java index a85da31fd999..fe065941bd80 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/DSpaceObjectMetadataAddOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/DSpaceObjectMetadataAddOperation.java @@ -74,7 +74,7 @@ private void add(Context context, DSpaceObject dso, DSpaceObjectService dsoServi if (dso instanceof Item) { if (!itemConverter.checkMetadataFieldVisibility(context, (Item) dso, metadataField)) { throw new UnprocessableEntityException( - "Current user has not permession to esecute patch peration on " + metadataField); + "Current user has not permission to execute patch operation on " + metadataField); } } int indexInt = 0; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/SubscriptionParameterRemoveOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/SubscriptionParameterRemoveOperation.java index 660ed7f8d4f1..c337949da5bc 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/SubscriptionParameterRemoveOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/SubscriptionParameterRemoveOperation.java @@ -10,9 +10,7 @@ import java.sql.SQLException; import org.dspace.app.rest.exception.DSpaceBadRequestException; -import org.dspace.app.rest.exception.RESTAuthorizationException; import org.dspace.app.rest.model.patch.Operation; -import org.dspace.authorize.AuthorizeException; import org.dspace.core.Context; import org.dspace.eperson.Subscription; import org.dspace.eperson.SubscriptionParameter; @@ -43,12 +41,8 @@ public Subscription perform(Context context, Subscription subscription, Operatio throws SQLException { if (supports(subscription, operation)) { Integer path = Integer.parseInt(operation.getPath().split("/")[2]); - try { - SubscriptionParameter subscriptionParameter = subscriptionParameterService.findById(context, path); - subscribeService.removeSubscriptionParameter(context, subscription.getID(), subscriptionParameter); - } catch (AuthorizeException e) { - throw new RESTAuthorizationException("Unauthorized user for removing subscription parameter"); - } + SubscriptionParameter subscriptionParameter = subscriptionParameterService.findById(context, path); + subscribeService.removeSubscriptionParameter(context, subscription.getID(), subscriptionParameter); } else { throw new DSpaceBadRequestException("Subscription does not support this operation"); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/SubscriptionParameterReplaceOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/SubscriptionParameterReplaceOperation.java index 82b0896ff4a8..3a3dd07d5bce 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/SubscriptionParameterReplaceOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/SubscriptionParameterReplaceOperation.java @@ -88,7 +88,7 @@ public boolean supports(Object objectToMatch, Operation operation) { @SuppressWarnings("ReturnValueIgnored") private void checkModelForExistingValue(Subscription subscription, Integer id) { subscription.getSubscriptionParameterList().stream().filter(subscriptionParameter -> { - return subscriptionParameter.getId().equals(id); + return subscriptionParameter.getID().equals(id); }).findFirst().orElseThrow(); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/OrcidHistorySendToOrcidRestPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/OrcidHistorySendToOrcidRestPermissionEvaluatorPlugin.java index 9f6d83695295..7e7940372f7a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/OrcidHistorySendToOrcidRestPermissionEvaluatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/OrcidHistorySendToOrcidRestPermissionEvaluatorPlugin.java @@ -15,6 +15,7 @@ import org.dspace.app.rest.model.OrcidQueueRest; import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.content.Item; import org.dspace.content.MetadataValue; import org.dspace.content.service.ItemService; import org.dspace.core.Context; @@ -89,7 +90,7 @@ public boolean hasDSpacePermission(Authentication authentication, Serializable t private boolean hasAccess(Context context, EPerson currentUser, OrcidQueue orcidQueue) { if (orcidQueue != null) { List value = itemService.getMetadata(orcidQueue.getProfileItem(), - "dspace", "object", "owner", null); + "dspace", "object", "owner", Item.ANY); if (value.get(0).getAuthority().equals(currentUser.getID().toString())) { return true; } 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 537012c9e5b6..812889d029e2 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 @@ -61,7 +61,7 @@ void addAuthenticationDataForUser(HttpServletRequest request, HttpServletRespons * 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 000000000000..c93d966e73cb --- /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 29d2ac2557de..bd8a621fec2c 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 @@ -21,6 +21,7 @@ import org.dspace.profile.service.ResearcherProfileService; 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; @@ -60,6 +61,10 @@ public class WorkflowRestPermissionEvaluatorPlugin extends RestObjectPermissionE @Autowired private ResearcherProfileService researcherProfileService; + @Autowired + private SupervisionOrderService supervisionOrderService; + + @Override public boolean hasDSpacePermission(Authentication authentication, Serializable targetId, String targetType, DSpaceRestPermission permission) { @@ -94,7 +99,7 @@ public boolean hasDSpacePermission(Authentication authentication, Serializable t return true; } - if (researcherProfileService.isAuthorOf(context, ePerson, workflowItem.getItem())) { + if (supervisionOrderService.isSupervisor(context, ePerson, workflowItem.getItem())) { return true; } 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 29706f07a403..56633e80c025 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 @@ -21,6 +21,7 @@ import org.dspace.profile.service.ResearcherProfileService; 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; @@ -40,11 +41,14 @@ public class WorkspaceItemRestPermissionEvaluatorPlugin extends RestObjectPermis @Autowired private RequestService requestService; + @Autowired + WorkspaceItemService wis; + @Autowired private ResearcherProfileService researcherProfileService; @Autowired - WorkspaceItemService wis; + private SupervisionOrderService supervisionOrderService; @Autowired private AuthorizeService authorizeService; @@ -95,9 +99,12 @@ public boolean hasDSpacePermission(Authentication authentication, Serializable t return true; } - if (authorizeService.authorizeActionBoolean(context, witem.getItem(), - restPermission.getDspaceApiActionId())) { - 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) { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/statistics/AbstractTopSolrStatsFieldGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/statistics/AbstractTopSolrStatsFieldGenerator.java index 9d04803c71ea..6d8242ca9fb1 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/statistics/AbstractTopSolrStatsFieldGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/statistics/AbstractTopSolrStatsFieldGenerator.java @@ -66,8 +66,8 @@ Dataset getTypeStatsDataset(Context context, DSpaceObject dso, String typeAxisSt } else { hasValidRelation = true; - query = statisticsDatasetDisplay - .composeQueryWithInverseRelation(dso, discoveryConfiguration.getDefaultFilterQueries()); + query = statisticsDatasetDisplay.composeQueryWithInverseRelation( + dso, discoveryConfiguration.getDefaultFilterQueries(), getDsoType(dso)); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/statistics/TopCategoriesGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/statistics/TopCategoriesGenerator.java index 58532e46bf0f..39d8a1730c06 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/statistics/TopCategoriesGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/statistics/TopCategoriesGenerator.java @@ -93,7 +93,8 @@ private int getCategoryCount(DSpaceObject dso, DiscoveryConfiguration discoveryC private String composeCategoryQuery(DSpaceObject dso, DiscoveryConfiguration configuration, String categoryQuery) { List defaultFilterQueries = configuration.getDefaultFilterQueries(); - String query = new StatisticsDatasetDisplay().composeQueryWithInverseRelation(dso, defaultFilterQueries); + String query = new StatisticsDatasetDisplay().composeQueryWithInverseRelation(dso, + defaultFilterQueries, dso.getType()); if (categoryQuery.equals(OTHER_CATEGORY)) { return query + " AND " + getAllCategoryQueriesReverted(); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/statistics/TopItemsGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/statistics/TopItemsGenerator.java index d0805c68de36..1e67b0cac66c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/statistics/TopItemsGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/statistics/TopItemsGenerator.java @@ -81,9 +81,8 @@ public UsageReportRest createUsageReport(Context context, DSpaceObject root, Str } else { hasValidRelation = true; - query = statisticsDatasetDisplay - .composeQueryWithInverseRelation(root, - discoveryConfiguration.getDefaultFilterQueries()); + query = statisticsDatasetDisplay.composeQueryWithInverseRelation(root, + discoveryConfiguration.getDefaultFilterQueries(), getDsoType()); } } if (!hasValidRelation) { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/statistics/TotalDownloadsAndVisitsGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/statistics/TotalDownloadsAndVisitsGenerator.java index 2a1d95e0c291..620333ce61b9 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/statistics/TotalDownloadsAndVisitsGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/statistics/TotalDownloadsAndVisitsGenerator.java @@ -59,9 +59,8 @@ public UsageReportRest createUsageReport(Context context, DSpaceObject dso, Stri } else { hasValidRelation = true; - query = statisticsDatasetDisplay. - composeQueryWithInverseRelation( - dso, discoveryConfiguration.getDefaultFilterQueries()); + query = statisticsDatasetDisplay.composeQueryWithInverseRelation(dso, + discoveryConfiguration.getDefaultFilterQueries(), dso.getType()); } } if (!hasValidRelation) { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/statistics/TotalVisitGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/statistics/TotalVisitGenerator.java index 98ee393b5cd3..e419ac660fd6 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/statistics/TotalVisitGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/statistics/TotalVisitGenerator.java @@ -118,7 +118,7 @@ Dataset getDSOStatsDataset(Context context, DSpaceObject dso, int dsoType, Strin } else { hasValidRelation = true; query = statisticsDatasetDisplay.composeQueryWithInverseRelation( - dso, discoveryConfiguration.getDefaultFilterQueries()); + dso, discoveryConfiguration.getDefaultFilterQueries(), dso.getType()); type_of_dso = dso.getType(); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/statistics/TotalVisitPerPeriodGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/statistics/TotalVisitPerPeriodGenerator.java index 5f7f53898abe..465f1022372a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/statistics/TotalVisitPerPeriodGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/statistics/TotalVisitPerPeriodGenerator.java @@ -97,7 +97,8 @@ public UsageReportRest createUsageReport(Context context, DSpaceObject dso, Stri statisticsDatasetDisplay .composeQueryWithInverseRelation( dso, - discoveryConfiguration.getDefaultFilterQueries() + discoveryConfiguration.getDefaultFilterQueries(), + getDsoType(dso) ) ); } 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 6ce7d23d543b..1a8f0ba3bdab 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 @@ -35,6 +35,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 000000000000..e63d38ab2e36 --- /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/ApplicationConfig.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/ApplicationConfig.java index 4b2dddd6b707..67983ba8f007 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/ApplicationConfig.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/ApplicationConfig.java @@ -36,6 +36,11 @@ public class ApplicationConfig { @Value("${iiif.cors.allowed-origins}") private String[] iiifCorsAllowedOrigins; + // Allowed IIIF CORS origins ("Access-Control-Allow-Origin" header) + // Can be overridden in DSpace configuration + @Value("${rest.cors.bitstream-allow-origins}") + private String[] bitstreamCorsAllowedOrigins; + // Whether to allow credentials (cookies) in CORS requests ("Access-Control-Allow-Credentials" header) // Defaults to true. Can be overridden in DSpace configuration @Value("${rest.cors.allow-credentials:true}") @@ -46,6 +51,11 @@ public class ApplicationConfig { @Value("${iiif.cors.allow-credentials:true}") private boolean iiifCorsAllowCredentials; + // Whether to allow credentials (cookies) in CORS requests ("Access-Control-Allow-Credentials" header) + // Defaults to true. Can be overridden in DSpace configuration + @Value("${rest.cors.bitstream-allow-credentials:true}") + private boolean bitstreamsCorsAllowCredentials; + // Configured User Interface URL (default: http://localhost:4000) @Value("${dspace.ui.url:http://localhost:4000}") private String uiURL; @@ -91,6 +101,14 @@ public String[] getIiifAllowedOriginsConfig() { return this.iiifCorsAllowedOrigins; } + /** + * Returns the bitstream.cors.allowed-origins (for Bitstream access) defined in DSpace configuration. + * @return allowed origins + */ + public String[] getBitstreamAllowedOriginsConfig() { + return this.bitstreamCorsAllowedOrigins; + } + /** * Return whether to allow credentials (cookies) on CORS requests. This is used to set the * CORS "Access-Control-Allow-Credentials" header in Application class. @@ -108,4 +126,13 @@ public boolean getCorsAllowCredentials() { public boolean getIiifAllowCredentials() { return iiifCorsAllowCredentials; } + + /** + * Return whether to allow credentials (cookies) on IIIF requests. This is used to set the + * CORS "Access-Control-Allow-Credentials" header in Application class. Defaults to false. + * @return true or false + */ + public boolean getBitstreamsAllowCredentials() { + return bitstreamsCorsAllowCredentials; + } } 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 ffe2acc2e0cb..a69da4c5e86b 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 HttpHeaders initialiseHeaders() throws IOException { } + 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/UsageReportUtils.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/UsageReportUtils.java index 90f7ec252542..47dbcef1749d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/UsageReportUtils.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/UsageReportUtils.java @@ -101,6 +101,12 @@ public class UsageReportUtils { public static final String TOP_DOWNLOAD_COUNTRIES_REPORT_ID = "TopDownloadsCountries"; public static final String TOP_DOWNLOAD_CITIES_REPORT_ID = "TopDownloadsCities"; public static final String TOTAL_DOWNLOAD_PER_MONTH_REPORT_ID = "TotalDownloadsPerMonth"; + public static final String TOP_ITEMS_CITIES_REPORT_ID = "TopItemsCities"; + public static final String TOP_ITEMS_CONTINENTS_REPORT_ID = "TopItemsContinents"; + public static final String TOP_ITEMS_COUNTRIES_REPORT_ID = "TopItemsCountries"; + public static final String TOP_ITEMS_CATEGORIES_REPORT_ID = "TopItemsCategories"; + public static final String TOTAL_ITEMS_VISITS_REPORT_ID = "TotalItemsVisits"; + public static final String TOTAL_ITEMS_VISITS_PER_MONTH_REPORT_ID = "TotalItemsVisitsPerMonth"; /** * Get list of usage reports that are applicable to the DSO (of given UUID) 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 df529d935c0f..8fbd6451185e 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 @@ -65,6 +65,7 @@ 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.UsageReportCategoryRest; import org.dspace.app.rest.model.VersionHistoryRest; import org.dspace.app.rest.model.VocabularyRest; @@ -328,6 +329,9 @@ public static String makeSingular(String modelPlural) { if (StringUtils.equals(modelPlural, "categories")) { return UsageReportCategoryRest.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 cc23dbc09eca..eca9acf79fd7 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 000000000000..447d0a59dd1a --- /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 000000000000..462fc23a0d23 --- /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 9974d7e725f2..ac03e946e320 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 void emptyQueryTest() throws Exception { ; } + @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 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 6fdb911a0d15..e4ed507d473c 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 @@ -1288,64 +1288,6 @@ public void testShortLivedToken() throws Exception { .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); @@ -1372,15 +1314,6 @@ public void testShortLivedTokenNotAuthenticated() throws Exception { .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(); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorizationRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorizationRestRepositoryIT.java index 350e471f49c5..67185f2cdab2 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorizationRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorizationRestRepositoryIT.java @@ -25,7 +25,9 @@ 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.io.Serializable; +import java.sql.SQLException; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; @@ -64,6 +66,7 @@ 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.AuthorizeException; import org.dspace.builder.CollectionBuilder; import org.dspace.builder.CommunityBuilder; import org.dspace.builder.EPersonBuilder; @@ -81,8 +84,13 @@ import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; import org.dspace.services.ConfigurationService; +import org.dspace.xmlworkflow.storedcomponents.PoolTask; +import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; +import org.dspace.xmlworkflow.storedcomponents.service.PoolTaskService; +import org.dspace.xmlworkflow.storedcomponents.service.XmlWorkflowItemService; import org.hamcrest.Matcher; import org.hamcrest.Matchers; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -117,6 +125,12 @@ public class AuthorizationRestRepositoryIT extends AbstractControllerIntegration @Autowired private ItemConverter itemConverter; + @Autowired + private PoolTaskService poolTaskService; + + @Autowired + private XmlWorkflowItemService xmlWorkflowItemService; + @Autowired private Utils utils; private SiteService siteService; @@ -175,6 +189,14 @@ public void setUp() throws Exception { configurationService.setProperty("webui.user.assumelogin", true); } + @After + public void cleanUp() throws Exception { + context.turnOffAuthorisationSystem(); + poolTaskService.findAll(context).forEach(this::deletePoolTask); + xmlWorkflowItemService.findAll(context).forEach(this::deleteWorkflowItem); + context.restoreAuthSystemState(); + } + @Test /** * This method is not implemented @@ -2824,7 +2846,17 @@ public void verifySpecialGroupForNonAdministrativeUsersTest() throws Exception { .andExpect(status().isOk()) .andExpect(jsonPath("$.errors").doesNotExist()); - getClient(epersonToken).perform(get("/api/submission/workspaceitems/" + workspaceItemIdRef.get())) + AtomicReference workflowItemIdRef = new AtomicReference(); + + getClient(epersonToken).perform(post("/api/workflow/workflowitems") + .content("/api/submission/workspaceitems/" + workspaceItemIdRef.get()) + .contentType(textUriContentType)) + .andExpect(status().isCreated()) + .andDo(result -> workflowItemIdRef.set(read(result.getResponse().getContentAsString(), "$.id"))); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + + getClient(tokenAdmin).perform(get("/api/workflow/workflowitems/" + workflowItemIdRef.get())) .andExpect(status().isOk()) .andExpect(jsonPath("$.sections.correction.metadata", hasSize(equalTo(3)))) .andExpect(jsonPath("$.sections.correction.empty", is(false))) @@ -2931,5 +2963,20 @@ private String getAuthorizationID(String epersonUuid, String featureName, String + id.toString(); } + private void deletePoolTask(PoolTask poolTask) { + try { + poolTaskService.delete(context, poolTask); + } catch (SQLException | AuthorizeException e) { + throw new RuntimeException(e); + } + } + + private void deleteWorkflowItem(XmlWorkflowItem workflowItem) { + try { + xmlWorkflowItemService.delete(context, workflowItem); + } catch (SQLException | AuthorizeException | IOException e) { + throw new RuntimeException(e); + } + } } 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 316400476da6..427235e1367a 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 @@ -187,6 +187,24 @@ public void findBrowseBySubjectEntries() throws Exception { .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(); @@ -390,6 +408,23 @@ public void findBrowseBySubjectItems() throws Exception { .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 ** @@ -428,6 +463,31 @@ public void findBrowseBySubjectItems() throws Exception { 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 @@ -526,13 +586,52 @@ public void findBrowseByTitleItems() throws Exception { .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 /** * 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 { @@ -654,6 +753,18 @@ public void testPaginationBrowseByDateIssuedItems() throws Exception { .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 ** @@ -706,6 +817,32 @@ public void testPaginationBrowseByDateIssuedItems() throws Exception { .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") + ))); + + 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, @@ -1158,7 +1295,7 @@ public void testBrowseByItemsStartsWith() throws Exception { //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"))) @@ -1168,6 +1305,33 @@ public void testBrowseByItemsStartsWith() throws Exception { "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 @@ -1527,4 +1691,57 @@ public void testBrowseByDateIssuedItemsFullProjectionTest() throws Exception { .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/CorrectionStepIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CorrectionStepIT.java index 05b3db7a4ac6..34665592823e 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CorrectionStepIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CorrectionStepIT.java @@ -9,9 +9,9 @@ import static com.jayway.jsonpath.JsonPath.read; import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; -import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; @@ -20,6 +20,7 @@ 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.ArrayList; import java.util.Date; @@ -40,21 +41,17 @@ import org.dspace.builder.EntityTypeBuilder; import org.dspace.builder.ItemBuilder; import org.dspace.builder.RelationshipTypeBuilder; -import org.dspace.builder.WorkspaceItemBuilder; import org.dspace.content.Collection; import org.dspace.content.EntityType; import org.dspace.content.Item; -import org.dspace.content.MetadataSchemaEnum; import org.dspace.content.Relationship; import org.dspace.content.RelationshipType; import org.dspace.content.WorkspaceItem; import org.dspace.content.service.EntityTypeService; -import org.dspace.content.service.ItemService; import org.dspace.content.service.RelationshipService; import org.dspace.content.service.RelationshipTypeService; import org.dspace.content.service.WorkspaceItemService; import org.dspace.core.Constants; -import org.dspace.eperson.EPerson; import org.dspace.services.ConfigurationService; import org.dspace.xmlworkflow.storedcomponents.PoolTask; import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; @@ -68,12 +65,11 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.Resource; -import org.springframework.data.rest.webmvc.RestMediaTypes; /** * Integration tests for {@link CorrectionStep}. * - * @author Giuseppe Digilio (luca.giamminonni at 4science.it) + * @author Giuseppe Digilio (giuseppe.digilio at 4science.it) * */ public class CorrectionStepIT extends AbstractControllerIntegrationTest { @@ -84,9 +80,6 @@ public class CorrectionStepIT extends AbstractControllerIntegrationTest { @Autowired private EntityTypeService entityTypeService; - @Autowired - private ItemService itemService; - @Autowired private PoolTaskService poolTaskService; @@ -138,7 +131,7 @@ public void setup() throws Exception { date = "2020-02-20"; subject = "ExtraEntry"; - title = "Title " + (new Date().getTime()); + title = "Title " + new Date().getTime(); type = "text"; itemToBeCorrected = ItemBuilder.createItem(context, collection) @@ -160,6 +153,7 @@ public void setup() throws Exception { context.restoreAuthSystemState(); } + @Override @After public void destroy() throws Exception { //Clean up the database for the next test @@ -191,14 +185,12 @@ public void destroy() throws Exception { } } - if (workspaceItemIdRef.get() != null) { - WorkspaceItemBuilder.deleteWorkspaceItem(workspaceItemIdRef.get()); - } poolTaskService.findAll(context).forEach(this::deletePoolTask); - super.destroy(); + xmlWorkflowItemService.findAll(context).forEach(this::deleteWorkflowItem); super.destroy(); } + @Test public void checkCorrection() throws Exception { @@ -213,16 +205,15 @@ public void checkCorrection() throws Exception { .andExpect(status().isCreated()) .andDo(result -> workspaceItemIdRef.set(read(result.getResponse().getContentAsString(), "$.id"))); - Integer workspaceItemId = workspaceItemIdRef.get(); - List relationshipList = relationshipService.findByItem(context, itemToBeCorrected); - assert (relationshipList.size() > 0); + assert relationshipList.size() > 0; Item correctedItem = relationshipList.get(0).getLeftItem(); WorkspaceItem newWorkspaceItem = workspaceItemService.findByItem(context,correctedItem); //make a change on the title Map value = new HashMap(); - value.put("value", "New Title"); + final String newTitle = "New Title"; + value.put("value", newTitle); List addGrant = new ArrayList(); addGrant.add(new ReplaceOperation("/sections/traditionalpageone/dc.title/0", value)); String patchBody = getPatchContent(addGrant); @@ -241,9 +232,11 @@ public void checkCorrection() throws Exception { .contentType("application/json-patch+json")) .andExpect(status().isOk()) .andExpect(jsonPath("$.errors").doesNotExist()); + //add an asbtract description - Map addValue = new HashMap(); - addValue.put("value","Description Test"); + Map addValue = new HashMap(); + final String newDescription = "New Description"; + addValue.put("value", newDescription); addGrant = new ArrayList(); addGrant.add(new AddOperation("/sections/traditionalpagetwo/dc.description.abstract", List.of(addValue))); patchBody = getPatchContent(addGrant); @@ -252,17 +245,32 @@ public void checkCorrection() throws Exception { .contentType("application/json-patch+json")) .andExpect(status().isOk()) .andExpect(jsonPath("$.errors").doesNotExist()); - //check if the correction is present + getClient(tokenSubmitter).perform(get("/api/submission/workspaceitems/" + newWorkspaceItem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.correction.metadata").doesNotExist()); + + AtomicReference workflowItemIdRef = new AtomicReference(); + + getClient(tokenSubmitter).perform(post("/api/workflow/workflowitems") + .content("/api/submission/workspaceitems/" + newWorkspaceItem.getID()) + .contentType(textUriContentType)) + .andExpect(status().isCreated()) + .andDo(result -> workflowItemIdRef.set(read(result.getResponse().getContentAsString(), "$.id"))); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + + //check if the correction is present + final String extraEntry = "ExtraEntry"; + getClient(tokenAdmin).perform(get("/api/workflow/workflowitems/" + workflowItemIdRef.get())) //The status has to be 200 OK .andExpect(status().isOk()) //The array of browse index should have a size equals to 4 .andExpect(jsonPath("$.sections.correction.metadata", hasSize(equalTo(3)))) .andExpect(jsonPath("$.sections.correction.empty", is(false))) - .andExpect(jsonPath("$.sections.correction.metadata", - containsInAnyOrder(matchMetadataCorrection("New Title"), - matchMetadataCorrection("Description Test"), - matchMetadataCorrection("ExtraEntry")))); + .andExpect(jsonPath("$.sections.correction.metadata",hasItem(matchMetadataCorrection(newTitle)))) + .andExpect(jsonPath("$.sections.correction.metadata",hasItem(matchMetadataCorrection(newDescription)))) + .andExpect(jsonPath("$.sections.correction.metadata",hasItem(matchMetadataCorrection(extraEntry)))); } @@ -279,14 +287,23 @@ public void checkEmptyCorrection() throws Exception { .andExpect(status().isCreated()) .andDo(result -> workspaceItemIdRef.set(read(result.getResponse().getContentAsString(), "$.id"))); - Integer workspaceItemId = workspaceItemIdRef.get(); List relationshipList = relationshipService.findByItem(context, itemToBeCorrected); - assert (relationshipList.size() > 0); + assert relationshipList.size() > 0; Item correctedItem = relationshipList.get(0).getLeftItem(); WorkspaceItem newWorkspaceItem = workspaceItemService.findByItem(context, correctedItem); + AtomicReference workflowItemIdRef = new AtomicReference(); + + getClient(tokenSubmitter).perform(post("/api/workflow/workflowitems") + .content("/api/submission/workspaceitems/" + newWorkspaceItem.getID()) + .contentType(textUriContentType)) + .andExpect(status().isCreated()) + .andDo(result -> workflowItemIdRef.set(read(result.getResponse().getContentAsString(), "$.id"))); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + //check if the correction section is empty on relation item - getClient(tokenSubmitter).perform(get("/api/submission/workspaceitems/" + newWorkspaceItem.getID())) + getClient(tokenAdmin).perform(get("/api/workflow/workflowitems/" + workflowItemIdRef.get())) //The status has to be 200 OK .andExpect(status().isOk()) //The array of browse index should have a size greater or equals to 1 @@ -295,72 +312,10 @@ public void checkEmptyCorrection() throws Exception { } - private static Matcher matchMetadataCorrection(String value) { + private static Matcher matchMetadataCorrection(String value) { return Matchers.anyOf( - // Check workspaceitem properties - hasJsonPath("$.newValues[0]", is(value)), - hasJsonPath("$.oldValues[0]", is(value))); - } - - private void claimTaskAndCheckResponse(String authToken, Integer poolTaskId) throws SQLException, Exception { - getClient(authToken).perform(post("/api/workflow/claimedtasks") - .contentType(RestMediaTypes.TEXT_URI_LIST) - .content("/api/workflow/pooltasks/" + poolTaskId)) - .andExpect(status().isCreated()) - .andExpect(jsonPath("$", Matchers.allOf(hasJsonPath("$.type", is("claimedtask"))))); - } - - private XmlWorkflowItem setSubmission(EPerson user, String title, String date) - throws Exception { - - context.setCurrentUser(user); - - AtomicReference idRef = new AtomicReference(); - String tokenSubmitter = getAuthToken(user.getEmail(), password); - // create empty workSpaceItem - getClient(tokenSubmitter).perform(post("/api/submission/workspaceitems") - .param("owningCollection", collection.getID().toString()) - .contentType(org.springframework.http.MediaType.APPLICATION_JSON)) - .andExpect(status().isCreated()) - .andDo((result -> idRef - .set(read(result.getResponse().getContentAsString(), "$.id")))); - - WorkspaceItem witem = workspaceItemService.find(context, idRef.get()); - Item item = witem.getItem(); - - // add metadata - itemService.addMetadata(context, item, MetadataSchemaEnum.DC.getName(), - "title", null, null, title); - itemService.addMetadata(context, item, MetadataSchemaEnum.DC.getName(), - "date", "issued", null, date); - itemService.addMetadata(context, item, MetadataSchemaEnum.DC.getName(), - "subject", null, null, "ExtraEntry"); - itemService.addMetadata(context, item, MetadataSchemaEnum.DC.getName(), - "type", null, null, "text"); - // accept license - List addGrant = new ArrayList(); - addGrant.add(new AddOperation("/sections/license/granted", true)); - String patchBody = getPatchContent(addGrant); - getClient(tokenSubmitter).perform(patch("/api/submission/workspaceitems/" + witem.getID()) - .content(patchBody) - .contentType("application/json-patch+json")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.errors").doesNotExist()) - .andExpect(jsonPath("$.sections.license.granted", - is(true))) - .andExpect(jsonPath("$.sections.license.acceptanceDate").isNotEmpty()) - .andExpect(jsonPath("$.sections.license.url").isNotEmpty()); - - //deposit workSpaceItem, so it become workFlowItem - getClient(tokenSubmitter).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")))); - - XmlWorkflowItem xmlWorkFlowItem = xmlWorkflowItemService.find(context, idRef.get()); - return xmlWorkFlowItem; + hasJsonPath("$.newValues[0]", equalTo(value)), + hasJsonPath("$.oldValues[0]", equalTo(value))); } private void deletePoolTask(PoolTask poolTask) { @@ -371,5 +326,12 @@ private void deletePoolTask(PoolTask poolTask) { } } + private void deleteWorkflowItem(XmlWorkflowItem workflowItem) { + try { + xmlWorkflowItemService.delete(context, workflowItem); + } catch (SQLException | AuthorizeException | IOException e) { + throw new RuntimeException(e); + } + } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CrossRefImportMetadataSourceServiceIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CrossRefImportMetadataSourceServiceIT.java index 9a0d39225c3d..1b17215054ad 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CrossRefImportMetadataSourceServiceIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CrossRefImportMetadataSourceServiceIT.java @@ -150,7 +150,7 @@ private ArrayList getRecords() { MetadatumDTO ispartof = createMetadatumDTO("dc", "relation", "ispartof", "Ukraïnsʹkij žurnal medicini, bìologìï ta sportu"); MetadatumDTO doi = createMetadatumDTO("dc", "identifier", "doi", "10.26693/jmbs01.02.184"); - MetadatumDTO issn = createMetadatumDTO("dc", "identifier", "issn", "2415-3060"); + MetadatumDTO issn = createMetadatumDTO("dc", "relation", "issn", "2415-3060"); MetadatumDTO volume = createMetadatumDTO("oaire", "citation", "volume", "1"); MetadatumDTO issue = createMetadatumDTO("oaire", "citation", "issue", "2"); @@ -176,7 +176,7 @@ private ArrayList getRecords() { MetadatumDTO ispartof2 = createMetadatumDTO("dc", "relation", "ispartof", "Ukraïnsʹkij žurnal medicini, bìologìï ta sportu"); MetadatumDTO doi2 = createMetadatumDTO("dc", "identifier", "doi", "10.26693/jmbs01.02.105"); - MetadatumDTO issn2 = createMetadatumDTO("dc", "identifier", "issn", "2415-3060"); + MetadatumDTO issn2 = createMetadatumDTO("dc", "relation", "issn", "2415-3060"); MetadatumDTO volume2 = createMetadatumDTO("oaire", "citation", "volume", "1"); MetadatumDTO issue2 = createMetadatumDTO("oaire", "citation", "issue", "2"); 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 000000000000..83ebc40c7966 --- /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 259b29f36ba6..64c1907a5a46 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,6 +9,7 @@ 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; @@ -59,6 +60,7 @@ import org.dspace.builder.PoolTaskBuilder; import org.dspace.builder.RelationshipBuilder; import org.dspace.builder.RelationshipTypeBuilder; +import org.dspace.builder.SupervisionOrderBuilder; import org.dspace.builder.WorkflowItemBuilder; import org.dspace.builder.WorkspaceItemBuilder; import org.dspace.content.Bitstream; @@ -83,6 +85,7 @@ import org.dspace.eperson.Group; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; +import org.dspace.supervision.SupervisionOrder; import org.dspace.util.UUIDUtils; import org.dspace.utils.DSpace; import org.dspace.xmlworkflow.storedcomponents.ClaimedTask; @@ -401,6 +404,93 @@ public void discoverFacetsAuthorTestWithPrefix() throws Exception { ; } + @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 @@ -6262,6 +6352,490 @@ public void discoverFacetsTestWithDsoTypeTest() throws Exception { } + @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"))) + //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"))) + //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"))); + } + + + /** * This test verifies a known bug fund with the DSC-940, * the number of date facets returned by issuing a search with scope should be diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonGroupRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonGroupRestControllerIT.java index 5c19286a0419..fd23fd21c335 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonGroupRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonGroupRestControllerIT.java @@ -6,6 +6,9 @@ * http://www.dspace.org/license/ */ package org.dspace.app.rest; + +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.hasSize; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertNotNull; @@ -53,6 +56,7 @@ public void postGroupsForEpersonUsingRegistrationDataMultipleGroups() throws Exc registrationRest.setGroups(groupList); String adminToken = getAuthToken(admin.getEmail(), password); getClient(adminToken).perform(post("/api/eperson/registrations") + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .contentType(MediaType.APPLICATION_JSON) .content(mapper.writeValueAsBytes(registrationRest))) .andExpect(status().isCreated()); @@ -66,6 +70,7 @@ public void postGroupsForEpersonUsingRegistrationDataMultipleGroups() throws Exc try { getClient(token).perform(post("/api/eperson/epersons/" + alreadyRegisteredPerson.getID() + "/groups") .param("token", newRegisterToken) + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isCreated()) .andExpect(jsonPath("$.uuid", is((alreadyRegisteredPerson.getID().toString())))) @@ -105,6 +110,7 @@ public void postGroupsForEpersonUsingRegistrationDataOneGroup() throws Exception String adminToken = getAuthToken(admin.getEmail(), password); getClient(adminToken).perform(post("/api/eperson/registrations") .contentType(MediaType.APPLICATION_JSON) + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .content(mapper.writeValueAsBytes(registrationRest))) .andExpect(status().isCreated()); EPerson alreadyRegisteredPerson = EPersonBuilder.createEPerson(context) @@ -117,6 +123,7 @@ public void postGroupsForEpersonUsingRegistrationDataOneGroup() throws Exception try { getClient(token).perform(post("/api/eperson/epersons/" + alreadyRegisteredPerson.getID() + "/groups") .param("token", newRegisterToken) + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isCreated()) .andExpect(jsonPath("$.uuid", is((alreadyRegisteredPerson.getID().toString())))) @@ -155,6 +162,7 @@ public void postGroupsForEpersonDifferentFromLoggedInUsingRegistration() throws String adminToken = getAuthToken(admin.getEmail(), password); getClient(adminToken).perform(post("/api/eperson/registrations") .contentType(MediaType.APPLICATION_JSON) + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .content(mapper.writeValueAsBytes(registrationRest))) .andExpect(status().isCreated()); EPerson alreadyRegisteredPerson = EPersonBuilder.createEPerson(context) @@ -167,6 +175,7 @@ public void postGroupsForEpersonDifferentFromLoggedInUsingRegistration() throws try { getClient(token).perform(post("/api/eperson/epersons/" + alreadyRegisteredPerson.getID() + "/groups") .param("token", newRegisterToken) + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isForbidden()); assertNotNull(registrationDataService.findByToken(context, newRegisterToken)); @@ -199,6 +208,7 @@ public void postGroupsForEpersonWithFakeTokenRegistration() throws Exception { try { getClient(token).perform(post("/api/eperson/epersons/" + alreadyRegisteredPerson.getID() + "/groups") .param("token", "justToBeTested") + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isBadRequest()); } finally { @@ -216,6 +226,7 @@ public void postGroupsForEpersonUnregistered() throws Exception { registrationRest.setEmail(newRegisterEmail); String adminToken = getAuthToken(admin.getEmail(), password); getClient(adminToken).perform(post("/api/eperson/registrations") + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .contentType(MediaType.APPLICATION_JSON) .content(mapper.writeValueAsBytes(registrationRest))) .andExpect(status().isCreated()); 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 9c41996052d8..fab9dffa4616 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.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; @@ -2292,6 +2294,7 @@ public void registerNewAccountPatchUpdatePasswordRandomUserUuidFail() throws Exc 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()); @@ -2340,6 +2343,7 @@ public void postEPersonWithTokenWithoutEmailProperty() throws Exception { 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()); @@ -2404,6 +2408,7 @@ public void postEPersonWithTokenWithEmailProperty() throws Exception { 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()); @@ -2466,6 +2471,7 @@ public void postEPersonWithTokenWithEmailAndSelfRegisteredProperty() throws Exce 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()); @@ -2532,6 +2538,7 @@ public void postEPersonWithTokenWithTwoTokensDifferentEmailProperty() throws Exc 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()); @@ -2541,6 +2548,7 @@ public void postEPersonWithTokenWithTwoTokensDifferentEmailProperty() throws Exc 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()); @@ -2591,6 +2599,7 @@ public void postEPersonWithRandomTokenWithEmailProperty() throws Exception { 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()); @@ -2639,6 +2648,7 @@ public void postEPersonWithTokenWithEmailAndSelfRegisteredFalseProperty() throws 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()); @@ -2688,6 +2698,7 @@ public void postEPersonWithTokenWithoutLastNameProperty() throws Exception { 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()); @@ -2755,6 +2766,7 @@ public void postEPersonWithTokenWithoutFirstNameProperty() throws Exception { 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()); @@ -2823,6 +2835,7 @@ public void postEPersonWithTokenWithoutPasswordProperty() throws Exception { 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()); @@ -2870,6 +2883,7 @@ public void postEPersonWithWrongToken() throws Exception { 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()); @@ -2919,6 +2933,7 @@ public void postEPersonWithTokenWithEmailPropertyAnonUser() throws Exception { 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()); @@ -3251,6 +3266,7 @@ public void validatePasswordRobustnessContainingAtLeastAnUpperCaseCharUnprocessa 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()); @@ -3296,6 +3312,7 @@ public void validatePasswordRobustnessContainingAtLeastAnUpperCaseCharTest() thr 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()); @@ -3531,4 +3548,4 @@ private String buildPasswordAddOperationPatchBody(String password, String curren } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryInviationIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryInviationIT.java index 92f11549f5b1..534c3b2e7e11 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryInviationIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryInviationIT.java @@ -9,6 +9,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.repository.RegistrationRestRepository.TYPE_QUERY_PARAM; +import static org.dspace.app.rest.repository.RegistrationRestRepository.TYPE_REGISTER; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; @@ -81,6 +83,7 @@ public void adminInvitedEPersonToGroupsTest() throws Exception { String tokenAdmin = getAuthToken(admin.getEmail(), password); getClient(tokenAdmin).perform(post("/api/eperson/registrations") + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .contentType(MediaType.APPLICATION_JSON) .content(mapper.writeValueAsBytes(registrationRest))) .andExpect(status().isCreated()); @@ -105,6 +108,7 @@ public void adminInvitedEPersonToGroupsTest() throws Exception { try { getClient().perform(post("/api/eperson/epersons") .param("token", newRegisterToken) + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .content(mapper.writeValueAsBytes(ePersonRest)) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isCreated()) @@ -160,6 +164,7 @@ public void simpleUserInvitedEPersonToGroupsTest() throws Exception { String tokenEPerson = getAuthToken(eperson.getEmail(), password); getClient(tokenEPerson).perform(post("/api/eperson/registrations") .contentType(MediaType.APPLICATION_JSON) + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .content(mapper.writeValueAsBytes(registrationRest))) .andExpect(status().isForbidden()); @@ -189,6 +194,7 @@ public void anonymousUserInvitedEPersonToGroupsTest() throws Exception { String tokenEPerson = getAuthToken(eperson.getEmail(), password); getClient(tokenEPerson).perform(post("/api/eperson/registrations") .contentType(MediaType.APPLICATION_JSON) + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .content(mapper.writeValueAsBytes(registrationRest))) .andExpect(status().isForbidden()); @@ -217,6 +223,7 @@ public void adminInvitedEPersonToGroupNotExistTest() throws Exception { String tokenAdmin = getAuthToken(admin.getEmail(), password); getClient(tokenAdmin).perform(post("/api/eperson/registrations") .contentType(MediaType.APPLICATION_JSON) + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .content(mapper.writeValueAsBytes(registrationRest))) .andExpect(status().isUnprocessableEntity()); @@ -246,6 +253,7 @@ public void removeOneGroupTest() throws Exception { String tokenAdmin = getAuthToken(admin.getEmail(), password); getClient(tokenAdmin).perform(post("/api/eperson/registrations") .contentType(MediaType.APPLICATION_JSON) + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .content(mapper.writeValueAsBytes(registrationRest))) .andExpect(status().isCreated()); @@ -271,6 +279,7 @@ public void removeOneGroupTest() throws Exception { try { getClient().perform(post("/api/eperson/epersons") .param("token", newRegisterToken) + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .content(mapper.writeValueAsBytes(ePersonRest)) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isCreated()) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EditItemRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EditItemRestRepositoryIT.java index b2cc8e5ea7c6..7a511f662fc3 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EditItemRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EditItemRestRepositoryIT.java @@ -15,6 +15,7 @@ import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.greaterThanOrEqualTo; +import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; @@ -1906,6 +1907,50 @@ public void testEditItemModeConfigurationWithEntityTypeAndSubmission() throws Ex } + @Test + public void testEditWithHiddenSections() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .withEntityType("Publication") + .build(); + Item item = ItemBuilder.createItem(context, collection) + .withTitle("Item") + .build(); + context.restoreAuthSystemState(); + getClient(getAuthToken(admin.getEmail(), password)) + .perform(get("/api/core/edititems/" + item.getID() + ":MODE-TEST-HIDDEN")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.test-outside-workflow-hidden").exists()) + .andExpect(jsonPath("$.sections.test-outside-submission-hidden").doesNotExist()) + .andExpect(jsonPath("$.sections.test-never-hidden").exists()) + .andExpect(jsonPath("$.sections.test-always-hidden").doesNotExist()); + } + + @Test + public void testValidationWithHiddenSteps() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .withEntityType("Publication") + .build(); + Item item = ItemBuilder.createItem(context, collection) + .build(); + context.restoreAuthSystemState(); + getClient(getAuthToken(admin.getEmail(), password)) + .perform(get("/api/core/edititems/" + item.getID() + ":MODE-TEST-HIDDEN")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.errors", hasSize(1))) + .andExpect(jsonPath("$.errors[0].message", is("error.validation.required"))) + .andExpect(jsonPath("$.errors[0].paths", contains("/sections/test-outside-workflow-hidden/dc.title"))); + } + private Bitstream getBitstream(Item item, String name) throws SQLException { return bitstreamService.getBitstreamByName(item, "ORIGINAL", name); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EpoImportMetadataSourceServiceIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EpoImportMetadataSourceServiceIT.java index d86dd1875d8d..f6b944d34264 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EpoImportMetadataSourceServiceIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EpoImportMetadataSourceServiceIT.java @@ -148,28 +148,35 @@ private ArrayList getRecords() { List metadatums = new ArrayList(); MetadatumDTO identifierOther = createMetadatumDTO("dc", "identifier", "other", "epodoc:ES2902749T"); MetadatumDTO patentno = createMetadatumDTO("dc", "identifier", "patentno", "ES2902749T"); + MetadatumDTO kind = createMetadatumDTO("crispatent", "kind", null, "T3"); MetadatumDTO identifier = createMetadatumDTO("dc", "identifier", "applicationnumber", "18705153"); MetadatumDTO date = createMetadatumDTO("dc", "date", "issued", "2022-03-29"); MetadatumDTO dateSubmitted = createMetadatumDTO("dcterms", "dateSubmitted", null, "2018-02-19"); - MetadatumDTO applicant = createMetadatumDTO("dc", "contributor", null, "PANKA BLOOD TEST GMBH"); - MetadatumDTO applicant2 = createMetadatumDTO("dc", "contributor", null, "Panka Blood Test GmbH"); + MetadatumDTO applicant = createMetadatumDTO("dc", "contributor", null, "Panka Blood Test GmbH"); MetadatumDTO author = createMetadatumDTO("dc", "contributor", "author", "PANTEL, Klaus, "); MetadatumDTO author2 = createMetadatumDTO("dc", "contributor", "author", "BARTKOWIAK, Kai"); MetadatumDTO title = createMetadatumDTO("dc", "title", null, "Método para el diagnóstico del cáncer de mama"); MetadatumDTO subject = createMetadatumDTO("dc", "subject", null, "G01N 33/ 574 A I "); + MetadatumDTO kindCodeInline = createMetadatumDTO("crispatent", "document", "kind", "T3"); + MetadatumDTO issueDateInline = createMetadatumDTO("crispatent", "document", "issueDate", "2022-03-29"); + MetadatumDTO titleInline = createMetadatumDTO("crispatent", "document", "title", + "Método para el diagnóstico del cáncer de mama"); metadatums.add(identifierOther); metadatums.add(patentno); + metadatums.add(kind); metadatums.add(identifier); metadatums.add(date); metadatums.add(dateSubmitted); metadatums.add(applicant); - metadatums.add(applicant2); metadatums.add(author); metadatums.add(author2); metadatums.add(title); metadatums.add(subject); + metadatums.add(kindCodeInline); + metadatums.add(issueDateInline); + metadatums.add(titleInline); ImportRecord firstrRecord = new ImportRecord(metadatums); @@ -177,11 +184,11 @@ private ArrayList getRecords() { List metadatums2 = new ArrayList(); MetadatumDTO identifierOther2 = createMetadatumDTO("dc", "identifier", "other", "epodoc:TW202202864"); MetadatumDTO patentno2 = createMetadatumDTO("dc", "identifier", "patentno", "TW202202864"); + MetadatumDTO kind2 = createMetadatumDTO("crispatent", "kind", null, "A"); MetadatumDTO identifier2 = createMetadatumDTO("dc", "identifier", "applicationnumber", "109122801"); MetadatumDTO date2 = createMetadatumDTO("dc", "date", "issued", "2022-01-16"); MetadatumDTO dateSubmitted2 = createMetadatumDTO("dcterms", "dateSubmitted", null, "2020-07-06"); - MetadatumDTO applicant3 = createMetadatumDTO("dc", "contributor", null, "ADVANTEST CORP [JP]"); - MetadatumDTO applicant4 = createMetadatumDTO("dc", "contributor", null, "ADVANTEST CORPORATION"); + MetadatumDTO applicant2 = createMetadatumDTO("dc", "contributor", null, "ADVANTEST CORPORATION"); MetadatumDTO author5 = createMetadatumDTO("dc", "contributor", "author", "POEPPE, OLAF, "); MetadatumDTO author6 = createMetadatumDTO("dc", "contributor", "author", "HILLIGES, KLAUS-DIETER, "); MetadatumDTO author7 = createMetadatumDTO("dc", "contributor", "author", "KRECH, ALAN"); @@ -192,19 +199,29 @@ private ArrayList getRecords() { "G01R 31/ 319 A I "); MetadatumDTO subject3 = createMetadatumDTO("dc", "subject", null, "G01R 31/ 3193 A I "); + MetadatumDTO kindCodeInline2 = createMetadatumDTO("crispatent", "document", "kind", "A"); + MetadatumDTO issueDateInline2 = createMetadatumDTO("crispatent", "document", "issueDate", "2022-01-16"); + MetadatumDTO titleInline2 = createMetadatumDTO("crispatent", "document", "title", + "Automated test equipment for testing one or more devices under test," + + " method for automated testing of one or more devices under test," + + " and computer program using a buffer memory"); + metadatums2.add(identifierOther2); metadatums2.add(patentno2); + metadatums2.add(kind2); metadatums2.add(identifier2); metadatums2.add(date2); metadatums2.add(dateSubmitted2); - metadatums2.add(applicant3); - metadatums2.add(applicant4); + metadatums2.add(applicant2); metadatums2.add(author5); metadatums2.add(author6); metadatums2.add(author7); metadatums2.add(title2); metadatums2.add(subject2); metadatums2.add(subject3); + metadatums2.add(kindCodeInline2); + metadatums2.add(issueDateInline2); + metadatums2.add(titleInline2); ImportRecord secondRecord = new ImportRecord(metadatums2); records.add(firstrRecord); 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 99d51aa16af6..565a4d003f78 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 @@ -57,8 +57,7 @@ public void findAllExternalSources() throws Exception { ExternalSourceMatcher.matchExternalSource("sherpaJournalIssn", "sherpaJournalIssn", false), ExternalSourceMatcher.matchExternalSource("sherpaJournal", "sherpaJournal", false), ExternalSourceMatcher.matchExternalSource("sherpaPublisher", "sherpaPublisher", false), - ExternalSourceMatcher.matchExternalSource("pubmed", "pubmed", false)))) - .andExpect(jsonPath("$.page.totalElements", Matchers.is(16))); + ExternalSourceMatcher.matchExternalSource("pubmed", "pubmed", false)))); } @Test @@ -310,23 +309,19 @@ public void findExternalSourcesByEntityTypeTest() throws Exception { 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()))); } @@ -350,10 +345,9 @@ public void findExternalSourcesByEntityTypePaginationTest() throws Exception { .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))); @@ -362,10 +356,12 @@ public void findExternalSourcesByEntityTypePaginationTest() throws Exception { .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/IdentifierRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/IdentifierRestControllerIT.java deleted file mode 100644 index 70148c7ec8df..000000000000 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/IdentifierRestControllerIT.java +++ /dev/null @@ -1,126 +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.delete; -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 testIdentifierGoneItem() 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(); - String token = getAuthToken(admin.getEmail(), password); - - Item item = ItemBuilder.createItem(context, owningCollection).withTitle("Test item").build(); - - context.restoreAuthSystemState(); - - // Check that the item has been actually created - getClient(token).perform(get("/api/pid/find?id={handle}", item.getHandle())).andExpect(status().isFound()); - - getClient(token).perform(delete("/api/core/items/" + item.getID())); - - // The item has been deleted but the record is found in the DB (with a null dso) - getClient(token).perform(get("/api/pid/find?id={handle}", item.getHandle())).andExpect(status().isGone()); - } - - @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 000000000000..27e21e47760d --- /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 981e8af9d6e7..70b76e1afd6d 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 @@ -1057,7 +1057,8 @@ public void findAllPaginationTest() throws Exception { // 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/RegistrationRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RegistrationRestRepositoryIT.java index 643100901d7c..d597b68a550f 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; @@ -19,10 +22,8 @@ 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.Iterator; import java.util.List; -import java.util.UUID; import javax.servlet.http.HttpServletResponse; import com.fasterxml.jackson.databind.ObjectMapper; @@ -31,16 +32,14 @@ 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.GroupBuilder; +import org.dspace.builder.EPersonBuilder; import org.dspace.eperson.CaptchaServiceImpl; -import org.dspace.eperson.Group; import org.dspace.eperson.InvalidReCaptchaException; import org.dspace.eperson.RegistrationData; import org.dspace.eperson.dao.RegistrationDataDAO; import org.dspace.eperson.service.CaptchaService; import org.dspace.services.ConfigurationService; import org.hamcrest.Matchers; -import org.junit.Ignore; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -116,27 +115,12 @@ private void createTokenForEmail(String email) throws Exception { 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()); } - private void createTokenWithGroupsForEmail(String email) throws Exception { - List registrationDatas; - ObjectMapper mapper = new ObjectMapper(); - RegistrationRest registrationRest = new RegistrationRest(); - registrationRest.setEmail(email); - context.turnOffAuthorisationSystem(); - Group firstGroup = GroupBuilder.createGroup(context).withName("firstGroup").build(); - List groupList = new ArrayList<>(); - groupList.add(firstGroup.getID()); - registrationRest.setGroups(groupList); - context.restoreAuthSystemState(); - String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(post("/api/eperson/registrations") - .content(mapper.writeValueAsBytes(registrationRest)) - .contentType(contentType)) - .andExpect(status().isCreated()); - } + @Test public void registrationFlowTest() throws Exception { List registrationDataList = registrationDataDAO.findAll(context, RegistrationData.class); @@ -148,6 +132,7 @@ public void registrationFlowTest() throws Exception { try { getClient().perform(post("/api/eperson/registrations") + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .content(mapper.writeValueAsBytes(registrationRest)) .contentType(contentType)) .andExpect(status().isCreated()); @@ -158,6 +143,7 @@ public void registrationFlowTest() throws Exception { 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()); @@ -170,6 +156,7 @@ public void registrationFlowTest() throws Exception { 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)); @@ -187,24 +174,23 @@ public void registrationFlowTest() throws Exception { } @Test - @Ignore - public void forgotPasswordTest() throws Exception { - configurationService.setProperty("user.registration", false); - + public void testRegisterDomainRegistered() throws Exception { List registrationDataList = registrationDataDAO.findAll(context, RegistrationData.class); try { - assertEquals(0, registrationDataList.size()); + configurationService.setProperty("authentication-password.domain.valid", "test.com"); + RegistrationRest registrationRest = new RegistrationRest(); + String email = "testPerson@test.com"; + registrationRest.setEmail(email); ObjectMapper mapper = new ObjectMapper(); - RegistrationRest registrationRest = new RegistrationRest(); - registrationRest.setEmail(eperson.getEmail()); 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(), eperson.getEmail())); + assertTrue(StringUtils.equalsIgnoreCase(registrationDataList.get(0).getEmail(), email)); } finally { Iterator iterator = registrationDataList.iterator(); while (iterator.hasNext()) { @@ -213,36 +199,91 @@ public void forgotPasswordTest() throws Exception { } } } - @Test - public void findByTokenTestExistingUserWithGroupsTest() throws Exception { - String email = eperson.getEmail(); - createTokenWithGroupsForEmail("albaTest@yahoo.com"); - RegistrationData registrationData = registrationDataDAO.findByEmail(context, "albaTest@yahoo.com"); + @Test + public void testRegisterDomainNotRegistered() throws Exception { + List registrationDataList; try { - getClient().perform(get("/api/eperson/registrations/search/findByToken") - .param("token", registrationData.getToken())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.groupNames", Matchers.hasSize(1))) - .andExpect(jsonPath("$.email", Matchers.is("albaTest@yahoo.com"))) - .andExpect(jsonPath("$.groups", Matchers.hasSize(1))) - .andExpect(jsonPath("$.groupNames[0]", Matchers.is("firstGroup"))); - registrationDataDAO.delete(context, registrationData); + 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); + } + } + } - email = "newUser@testnewuser.com"; - createTokenForEmail(email); - registrationData = registrationDataDAO.findByEmail(context, email); - getClient().perform(get("/api/eperson/registrations/search/findByToken") - .param("token", registrationData.getToken())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$", Matchers.is(RegistrationMatcher.matchRegistration(email, null)))); + @Test + public void testRegisterDomainNotRegisteredMailAddressRegistred() 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(); + configurationService.setProperty("authentication-password.domain.valid", "test.com"); + 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().isUnprocessableEntity()); + registrationDataList = registrationDataDAO.findAll(context, RegistrationData.class); + assertEquals(0, registrationDataList.size()); } finally { - registrationDataDAO.delete(context, registrationData); + 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); + + List registrationDataList = registrationDataDAO.findAll(context, RegistrationData.class); + try { + assertEquals(0, registrationDataList.size()); + + ObjectMapper mapper = new ObjectMapper(); + 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()); + registrationDataList = registrationDataDAO.findAll(context, RegistrationData.class); + assertEquals(1, registrationDataList.size()); + assertTrue(StringUtils.equalsIgnoreCase(registrationDataList.get(0).getEmail(), eperson.getEmail())); + } finally { + Iterator iterator = registrationDataList.iterator(); + while (iterator.hasNext()) { + RegistrationData registrationData = iterator.next(); + registrationDataDAO.delete(context, registrationData); + } + } + } @Test public void registrationFlowWithNoHeaderCaptchaTokenTest() throws Exception { @@ -278,6 +319,7 @@ public void registrationFlowWithInvalidCaptchaTokenTest() throws Exception { 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)) @@ -314,12 +356,14 @@ public void registrationFlowWithValidCaptchaTokenTest() throws Exception { 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)) @@ -332,6 +376,7 @@ public void registrationFlowWithValidCaptchaTokenTest() throws Exception { 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)) @@ -347,6 +392,7 @@ public void registrationFlowWithValidCaptchaTokenTest() throws Exception { 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)) @@ -373,4 +419,27 @@ private void reloadCaptchaProperties(String verification, String secret, String 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 6b4292f1bc70..da48a5adf43b 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 @@ -519,43 +519,47 @@ public void testPut() } @Test - public void testPutBadRequest() + public void testPutUnauthenticated() throws Exception { - System.out.println("put bad requests"); - - // Create an item request to approve. + System.out.println("put unauthenticated request"); RequestItem itemRequest = RequestItemBuilder .createRequestItem(context, item, bitstream) .build(); - String authToken; Map parameters; String content; ObjectWriter mapperWriter = new ObjectMapper().writer(); - // Unauthenticated user + // Unauthenticated user should be allowed. parameters = Map.of( "acceptRequest", "true", - "subject", "subject", - "responseMessage", "Request accepted"); + "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().isUnauthorized()); + .andExpect(status().isOk()); + } - // 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 + @Test + public void testPutBadRequest() + throws Exception { + System.out.println("put bad requests"); + + // Create an item request to approve. + RequestItem itemRequest = RequestItemBuilder + .createRequestItem(context, item, bitstream) + .build(); + + String authToken; + Map parameters; + String content; + + ObjectWriter mapperWriter = new ObjectMapper().writer(); // Missing acceptRequest parameters = Map.of( diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ScopusImportMetadataSourceServiceIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ScopusImportMetadataSourceServiceIT.java index 84bf46c92920..879aac2c906a 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ScopusImportMetadataSourceServiceIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ScopusImportMetadataSourceServiceIT.java @@ -184,7 +184,7 @@ private ArrayList getRecords() { MetadatumDTO doi = createMetadatumDTO("dc", "identifier", null, "10.3934/mine.2023004"); MetadatumDTO title = createMetadatumDTO("dc","title", null, "Hardy potential versus lower order terms in Dirichlet problems: regularizing effects"); - MetadatumDTO type = createMetadatumDTO("dc", "type", null, "Journal"); + MetadatumDTO type = createMetadatumDTO("dc", "type", null, "Resource Types::text::journal::journal article"); MetadatumDTO date = createMetadatumDTO("dc", "date", "issued", "2023-01-01"); MetadatumDTO scopusId = createMetadatumDTO("dc", "identifier", "scopus", "2-s2.0-85124241875"); MetadatumDTO citationVolume = createMetadatumDTO("oaire", "citation", "volume", "5"); @@ -206,7 +206,6 @@ private ArrayList getRecords() { metadatums.add(doi); metadatums.add(title); - metadatums.add(type); metadatums.add(date); metadatums.add(scopusId); metadatums.add(citationVolume); @@ -224,6 +223,7 @@ private ArrayList getRecords() { metadatums.add(author3); metadatums.add(scopusAuthorId3); metadatums.add(orgunit3); + metadatums.add(type); ImportRecord firstrRecord = new ImportRecord(metadatums); //define second record @@ -233,7 +233,7 @@ private ArrayList getRecords() { "Large deviations for a binary collision model: energy evaporation"); MetadatumDTO date2 = createMetadatumDTO("dc", "date", "issued", "2023-01-01"); MetadatumDTO scopusId2 = createMetadatumDTO("dc", "identifier", "scopus", "2-s2.0-85124226483"); - MetadatumDTO type2 = createMetadatumDTO("dc", "type", null, "Journal"); + MetadatumDTO type2 = createMetadatumDTO("dc", "type", null, "Resource Types::text::journal::journal article"); MetadatumDTO citationVolume2 = createMetadatumDTO("oaire", "citation", "volume", "5"); MetadatumDTO citationIssue2 = createMetadatumDTO("oaire", "citation", "issue", "1"); @@ -258,7 +258,6 @@ private ArrayList getRecords() { "Mathematics In Engineering"); metadatums2.add(doi2); metadatums2.add(title2); - metadatums2.add(type2); metadatums2.add(date2); metadatums2.add(scopusId2); metadatums2.add(citationVolume2); @@ -279,6 +278,7 @@ private ArrayList getRecords() { metadatums2.add(author7); metadatums2.add(scopusAuthorId7); metadatums2.add(orgunit7); + metadatums2.add(type2); ImportRecord secondRecord = new ImportRecord(metadatums2); records.add(firstrRecord); records.add(secondRecord); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/StatisticsRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/StatisticsRestRepositoryIT.java index 0f7996a765f3..68dee1555f72 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/StatisticsRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/StatisticsRestRepositoryIT.java @@ -25,24 +25,27 @@ import static org.dspace.app.rest.utils.UsageReportUtils.TOP_DOWNLOAD_CITIES_REPORT_ID; import static org.dspace.app.rest.utils.UsageReportUtils.TOP_DOWNLOAD_CONTINENTS_REPORT_ID; import static org.dspace.app.rest.utils.UsageReportUtils.TOP_DOWNLOAD_COUNTRIES_REPORT_ID; +import static org.dspace.app.rest.utils.UsageReportUtils.TOP_ITEMS_CATEGORIES_REPORT_ID; +import static org.dspace.app.rest.utils.UsageReportUtils.TOP_ITEMS_CITIES_REPORT_ID; +import static org.dspace.app.rest.utils.UsageReportUtils.TOP_ITEMS_CONTINENTS_REPORT_ID; +import static org.dspace.app.rest.utils.UsageReportUtils.TOP_ITEMS_COUNTRIES_REPORT_ID; import static org.dspace.app.rest.utils.UsageReportUtils.TOP_ITEMS_REPORT_ID; import static org.dspace.app.rest.utils.UsageReportUtils.TOP_ITEMS_REPORT_RELATION_ORGUNIT_RP_RESEARCHOUTPUTS; import static org.dspace.app.rest.utils.UsageReportUtils.TOTAL_DOWNLOADS_REPORT_ID; import static org.dspace.app.rest.utils.UsageReportUtils.TOTAL_DOWNLOADS_REPORT_ID_RELATION_ORGUNIT_RP_RESEARCHOUTPUTS; import static org.dspace.app.rest.utils.UsageReportUtils.TOTAL_DOWNLOAD_PER_MONTH_REPORT_ID; -//import static org.dspace.app.rest.utils.UsageReportUtils.TOTAL_DOWNLOADS_REPORT_ID_RELATION_PERSON_RESEARCHOUTPUTS; +import static org.dspace.app.rest.utils.UsageReportUtils.TOTAL_ITEMS_VISITS_PER_MONTH_REPORT_ID; +import static org.dspace.app.rest.utils.UsageReportUtils.TOTAL_ITEMS_VISITS_REPORT_ID; import static org.dspace.app.rest.utils.UsageReportUtils.TOTAL_VISITS_PER_MONTH_REPORT_ID; import static org.dspace.app.rest.utils.UsageReportUtils.TOTAL_VISITS_PER_MONTH_REPORT_ID_RELATION_ORGUNIT_RP_RESEARCHOUTPUTS; import static org.dspace.app.rest.utils.UsageReportUtils.TOTAL_VISITS_PER_MONTH_REPORT_ID_RELATION_PERSON_PROJECTS; import static org.dspace.app.rest.utils.UsageReportUtils.TOTAL_VISITS_PER_MONTH_REPORT_ID_RELATION_PERSON_RESEARCHOUTPUTS; import static org.dspace.app.rest.utils.UsageReportUtils.TOTAL_VISITS_REPORT_ID; -//import static org.dspace.app.rest.utils.UsageReportUtils.TOTAL_VISITS_REPORT_ID_RELATION_ORGUNIT_PROJECTS; import static org.dspace.app.rest.utils.UsageReportUtils.TOTAL_VISITS_REPORT_ID_RELATION_ORGUNIT_RP_RESEARCHOUTPUTS; import static org.dspace.app.rest.utils.UsageReportUtils.TOTAL_VISITS_REPORT_ID_RELATION_PERSON_PROJECTS; import static org.dspace.app.rest.utils.UsageReportUtils.TOTAL_VISITS_REPORT_ID_RELATION_PERSON_RESEARCHOUTPUTS; import static org.dspace.app.rest.utils.UsageReportUtils.TOTAL_VISITS_TOTAL_DOWNLOADS; import static org.dspace.app.rest.utils.UsageReportUtils.TOTAL_VISITS_TOTAL_DOWNLOADS_RELATION_ORGUNIT_RP_RESEARCHOUTPUTS; -//import static org.dspace.app.rest.utils.UsageReportUtils.TOTAL_VISITS_TOTAL_DOWNLOADS_RELATION_PERSON_RESEARCHOUTPUTS; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.not; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; @@ -921,10 +924,10 @@ public void TotalDownloadsReport_Item_NotVisited() throws Exception { } @Test - public void TotalDownloadsReport_NotSupportedDSO_Collection() throws Exception { + public void TotalDownloadsReport_SupportedDSO_Collection() throws Exception { getClient(adminToken) .perform(get("/api/statistics/usagereports/" + collectionVisited.getID() + "_" + TOTAL_DOWNLOADS_REPORT_ID)) - .andExpect(status().isNotFound()); + .andExpect(status().isOk()); } /** @@ -1575,7 +1578,8 @@ public void usageReportsSearch_Community_Visited() throws Exception { // And request the community usage reports getClient(adminToken) - .perform(get("/api/statistics/usagereports/search/object?uri=http://localhost:8080/server/api/core" + + .perform(get("/api/statistics/usagereports/search/object?category=community-mainReports" + + "&uri=http://localhost:8080/server/api/core" + "/communities/" + communityVisited.getID())) // ** THEN ** .andExpect(status().isOk()) @@ -1616,7 +1620,8 @@ public void usageReportsSearch_Collection_NotVisited() throws Exception { // Collection is not visited // And request the collection's usage reports getClient(adminToken) - .perform(get("/api/statistics/usagereports/search/object?uri=http://localhost:8080/server/api/core" + + .perform(get("/api/statistics/usagereports/search/object?category=collection-mainReports" + + "&uri=http://localhost:8080/server/api/core" + "/collections/" + collectionNotVisited.getID())) // ** THEN ** .andExpect(status().isOk()) @@ -2035,7 +2040,8 @@ public void usageReportsSearch_Community_VisitedAtTime() throws Exception { String endDate = dateFormat.format(cal.getTime()); // And request the community usage reports getClient(adminToken) - .perform(get("/api/statistics/usagereports/search/object?uri=http://localhost:8080/server/api/core" + + .perform(get("/api/statistics/usagereports/search/object?category=community-mainReports" + + "&uri=http://localhost:8080/server/api/core" + "/communities/" + communityVisited.getID() + "&startDate=2019-06-01&endDate=" + endDate)) // ** THEN ** .andExpect(status().isOk()) @@ -2435,6 +2441,515 @@ public void usageReportsSearch_OrgUnitWithPublicationVisited() throws Exception ))); } + @Test + public void usageReportsSearch_Collection_ItemReports() throws Exception { + context.turnOffAuthorisationSystem(); + + Item item = ItemBuilder.createItem(context, collectionNotVisited) + .withTitle("My item") + .withType("Controlled Vocabulary for Resource Type Genres::image") + .build(); + Item item2 = ItemBuilder.createItem(context, collectionNotVisited) + .withTitle("My item 2") + .withType("Controlled Vocabulary for Resource Type Genres::thesis") + .build(); + Item item3 = ItemBuilder.createItem(context, collectionNotVisited) + .withTitle("My item 3") + .withType("Controlled Vocabulary for Resource Type Genres::thesis::bachelor thesis") + .build(); + Item item4 = ItemBuilder.createItem(context, collectionNotVisited) + .withTitle("My item 4") + .withType("Controlled Vocabulary for Resource Type Genres::text::periodical::" + + "journal::contribution to journal::journal article") + .build(); + context.restoreAuthSystemState(); + + ObjectMapper mapper = new ObjectMapper(); + + ViewEventRest viewEventRest = new ViewEventRest(); + viewEventRest.setTargetType("item"); + viewEventRest.setTargetId(item.getID()); + + getClient().perform(post("/api/statistics/viewevents") + .content(mapper.writeValueAsBytes(viewEventRest)) + .contentType(contentType)) + .andExpect(status().isCreated()); + + ViewEventRest viewEventRest2 = new ViewEventRest(); + viewEventRest2.setTargetType("item"); + viewEventRest2.setTargetId(item2.getID()); + + getClient().perform(post("/api/statistics/viewevents") + .content(mapper.writeValueAsBytes(viewEventRest2)) + .contentType(contentType)) + .andExpect(status().isCreated()); + + getClient().perform(post("/api/statistics/viewevents") + .content(mapper.writeValueAsBytes(viewEventRest2)) + .contentType(contentType)) + .andExpect(status().isCreated()); + + ViewEventRest viewEventRest3 = new ViewEventRest(); + viewEventRest3.setTargetType("item"); + viewEventRest3.setTargetId(item3.getID()); + + getClient().perform(post("/api/statistics/viewevents") + .content(mapper.writeValueAsBytes(viewEventRest3)) + .contentType(contentType)) + .andExpect(status().isCreated()); + + ViewEventRest viewEventRest4 = new ViewEventRest(); + viewEventRest4.setTargetType("item"); + viewEventRest4.setTargetId(item4.getID()); + + getClient().perform(post("/api/statistics/viewevents") + .content(mapper.writeValueAsBytes(viewEventRest4)) + .contentType(contentType)) + .andExpect(status().isCreated()); + + UsageReportPointDsoTotalVisitsRest expectedPoint1 = new UsageReportPointDsoTotalVisitsRest(); + expectedPoint1.addValue("views", 1); + expectedPoint1.setType("item"); + expectedPoint1.setLabel("My item"); + expectedPoint1.setId(item.getID().toString()); + + UsageReportPointDsoTotalVisitsRest expectedPoint2 = new UsageReportPointDsoTotalVisitsRest(); + expectedPoint2.addValue("views", 2); + expectedPoint2.setType("item"); + expectedPoint2.setLabel("My item 2"); + expectedPoint2.setId(item2.getID().toString()); + + UsageReportPointDsoTotalVisitsRest expectedPoint3 = new UsageReportPointDsoTotalVisitsRest(); + expectedPoint3.addValue("views", 1); + expectedPoint3.setType("item"); + expectedPoint3.setLabel("My item 3"); + expectedPoint3.setId(item3.getID().toString()); + + UsageReportPointDsoTotalVisitsRest expectedPoint4 = new UsageReportPointDsoTotalVisitsRest(); + expectedPoint4.addValue("views", 1); + expectedPoint4.setType("item"); + expectedPoint4.setLabel("My item 4"); + expectedPoint4.setId(item4.getID().toString()); + + List points = List.of(expectedPoint1, expectedPoint2, expectedPoint3, expectedPoint4); + + UsageReportPointCityRest pointCity = new UsageReportPointCityRest(); + pointCity.addValue("views", 5); + pointCity.setId("New York"); + + UsageReportPointContinentRest pointContinent = new UsageReportPointContinentRest(); + pointContinent.addValue("views", 5); + pointContinent.setId("North America"); + + UsageReportPointCountryRest pointCountry = new UsageReportPointCountryRest(); + pointCountry.addValue("views", 5); + pointCountry.setIdAndLabel(Locale.US.getCountry(), Locale.US.getDisplayCountry(context.getCurrentLocale())); + + UsageReportPointCategoryRest articleCategory = new UsageReportPointCategoryRest(); + articleCategory.addValue("views", 1); + articleCategory.setId("article"); + + UsageReportPointCategoryRest thesisCategory = new UsageReportPointCategoryRest(); + thesisCategory.addValue("views", 3); + thesisCategory.setId("thesis"); + + UsageReportPointCategoryRest otherCategory = new UsageReportPointCategoryRest(); + otherCategory.addValue("views", 1); + otherCategory.setId("other"); + + UsageReportPointCategoryRest bookCategory = new UsageReportPointCategoryRest(); + bookCategory.addValue("views", 0); + bookCategory.setId("book"); + + UsageReportPointCategoryRest bookChapterCategory = new UsageReportPointCategoryRest(); + bookChapterCategory.addValue("views", 0); + bookChapterCategory.setId("bookChapter"); + + UsageReportPointCategoryRest datasetCategory = new UsageReportPointCategoryRest(); + datasetCategory.addValue("views", 0); + datasetCategory.setId("dataset"); + + List categories = List.of(articleCategory, thesisCategory, otherCategory, bookCategory, + bookChapterCategory, datasetCategory); + + // And request the collections global usage report (show top most popular items) + getClient(adminToken) + .perform(get("/api/statistics/usagereports/search/object") + .param("category", "collection-itemReports") + .param("uri", "http://localhost:8080/server/api/core/collections/" + collectionNotVisited.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.usagereports", not(empty()))) + .andExpect(jsonPath("$._embedded.usagereports", Matchers.containsInAnyOrder( + matchUsageReport(collectionNotVisited.getID() + "_" + TOTAL_ITEMS_VISITS_REPORT_ID, + TOP_ITEMS_REPORT_ID, points), + matchUsageReport(collectionNotVisited.getID() + "_" + TOP_ITEMS_CITIES_REPORT_ID, + TOP_CITIES_REPORT_ID, List.of(pointCity)), + matchUsageReport(collectionNotVisited.getID() + "_" + TOTAL_ITEMS_VISITS_PER_MONTH_REPORT_ID, + TOTAL_VISITS_PER_MONTH_REPORT_ID, getLastMonthVisitPoints(5)), + matchUsageReport(collectionNotVisited.getID() + "_" + TOP_ITEMS_CONTINENTS_REPORT_ID, + TOP_CONTINENTS_REPORT_ID, List.of(pointContinent)), + matchUsageReport(collectionNotVisited.getID() + "_" + TOP_ITEMS_CATEGORIES_REPORT_ID, + TOP_CATEGORIES_REPORT_ID, categories), + matchUsageReport(collectionNotVisited.getID() + "_" + TOP_ITEMS_COUNTRIES_REPORT_ID, + TOP_COUNTRIES_REPORT_ID, List.of(pointCountry))))); + } + + @Test + public void usageReportsSearch_Collection_DownloadReports() throws Exception { + + context.turnOffAuthorisationSystem(); + + Item item1 = ItemBuilder.createItem(context, collectionNotVisited) + .withTitle("Item 1") + .build(); + + Item item2 = ItemBuilder.createItem(context, collectionNotVisited) + .withTitle("Item 2") + .build(); + + Item item3 = ItemBuilder.createItem(context, collectionNotVisited) + .withTitle("Item 3") + .build(); + + Bitstream bitstream1 = createBitstream(item1, "Bitstream 1"); + Bitstream bitstream2 = createBitstream(item1, "Bitstream 2"); + Bitstream bitstream3 = createBitstream(item2, "Bitstream 3"); + Bitstream bitstream4 = createBitstream(item3, "Bitstream 4"); + + getClient().perform(get("/api/core/bitstreams/" + bitstream1.getID() + "/content")) + .andExpect(status().isOk()); + + getClient().perform(get("/api/core/bitstreams/" + bitstream1.getID() + "/content")) + .andExpect(status().isOk()); + + getClient().perform(get("/api/core/bitstreams/" + bitstream2.getID() + "/content")) + .andExpect(status().isOk()); + + getClient().perform(get("/api/core/bitstreams/" + bitstream3.getID() + "/content")) + .andExpect(status().isOk()); + + getClient().perform(get("/api/core/bitstreams/" + bitstream3.getID() + "/content")) + .andExpect(status().isOk()); + + getClient().perform(get("/api/core/bitstreams/" + bitstream3.getID() + "/content")) + .andExpect(status().isOk()); + + getClient().perform(get("/api/core/bitstreams/" + bitstream4.getID() + "/content")) + .andExpect(status().isOk()); + + getClient().perform(get("/api/core/bitstreams/" + bitstream4.getID() + "/content")) + .andExpect(status().isOk()); + + context.restoreAuthSystemState(); + + UsageReportPointDsoTotalVisitsRest expectedPoint1 = new UsageReportPointDsoTotalVisitsRest(); + expectedPoint1.addValue("views", 3); + expectedPoint1.setType("item"); + expectedPoint1.setLabel("Item 1"); + expectedPoint1.setId(item1.getID().toString()); + + UsageReportPointDsoTotalVisitsRest expectedPoint2 = new UsageReportPointDsoTotalVisitsRest(); + expectedPoint2.addValue("views", 3); + expectedPoint2.setType("item"); + expectedPoint2.setLabel("Item 2"); + expectedPoint2.setId(item2.getID().toString()); + + UsageReportPointDsoTotalVisitsRest expectedPoint3 = new UsageReportPointDsoTotalVisitsRest(); + expectedPoint3.addValue("views", 2); + expectedPoint3.setType("item"); + expectedPoint3.setLabel("Item 3"); + expectedPoint3.setId(item3.getID().toString()); + + List points = List.of(expectedPoint1, expectedPoint2, expectedPoint3); + + UsageReportPointCityRest pointCity = new UsageReportPointCityRest(); + pointCity.addValue("views", 8); + pointCity.setId("New York"); + + UsageReportPointContinentRest pointContinent = new UsageReportPointContinentRest(); + pointContinent.addValue("views", 8); + pointContinent.setId("North America"); + + UsageReportPointCountryRest pointCountry = new UsageReportPointCountryRest(); + pointCountry.addValue("views", 8); + pointCountry.setIdAndLabel(Locale.US.getCountry(), Locale.US.getDisplayCountry(context.getCurrentLocale())); + + getClient(adminToken) + .perform(get("/api/statistics/usagereports/search/object") + .param("category", "collection-downloadReports") + .param("uri", "http://localhost:8080/server/api/core/collections/" + collectionNotVisited.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.usagereports", not(empty()))) + .andExpect(jsonPath("$._embedded.usagereports", Matchers.containsInAnyOrder( + matchUsageReport(collectionNotVisited.getID() + "_" + TOTAL_DOWNLOADS_REPORT_ID, + TOP_ITEMS_REPORT_ID, points), + matchUsageReport(collectionNotVisited.getID() + "_" + TOP_DOWNLOAD_CITIES_REPORT_ID, + TOP_CITIES_REPORT_ID, List.of(pointCity)), + matchUsageReport(collectionNotVisited.getID() + "_" + TOTAL_DOWNLOAD_PER_MONTH_REPORT_ID, + TOTAL_VISITS_PER_MONTH_REPORT_ID, getLastMonthVisitPoints(8)), + matchUsageReport(collectionNotVisited.getID() + "_" + TOP_DOWNLOAD_CONTINENTS_REPORT_ID, + TOP_CONTINENTS_REPORT_ID, List.of(pointContinent)), + matchUsageReport(collectionNotVisited.getID() + "_" + TOP_DOWNLOAD_COUNTRIES_REPORT_ID, + TOP_COUNTRIES_REPORT_ID, List.of(pointCountry))))); + } + + @Test + public void usageReportsSearch_Community_ItemReports() throws Exception { + context.turnOffAuthorisationSystem(); + + Community community = CommunityBuilder.createCommunity(context).build(); + collectionNotVisited = CollectionBuilder.createCollection(context, community).build(); + + Item item = ItemBuilder.createItem(context, collectionNotVisited) + .withTitle("My item") + .withType("Controlled Vocabulary for Resource Type Genres::image") + .build(); + Item item2 = ItemBuilder.createItem(context, collectionNotVisited) + .withTitle("My item 2") + .withType("Controlled Vocabulary for Resource Type Genres::thesis") + .build(); + Item item3 = ItemBuilder.createItem(context, collectionNotVisited) + .withTitle("My item 3") + .withType("Controlled Vocabulary for Resource Type Genres::thesis::bachelor thesis") + .build(); + Item item4 = ItemBuilder.createItem(context, collectionNotVisited) + .withTitle("My item 4") + .withType("Controlled Vocabulary for Resource Type Genres::text::periodical::" + + "journal::contribution to journal::journal article") + .build(); + context.restoreAuthSystemState(); + + ObjectMapper mapper = new ObjectMapper(); + + ViewEventRest viewEventRest = new ViewEventRest(); + viewEventRest.setTargetType("item"); + viewEventRest.setTargetId(item.getID()); + + getClient().perform(post("/api/statistics/viewevents") + .content(mapper.writeValueAsBytes(viewEventRest)) + .contentType(contentType)) + .andExpect(status().isCreated()); + + ViewEventRest viewEventRest2 = new ViewEventRest(); + viewEventRest2.setTargetType("item"); + viewEventRest2.setTargetId(item2.getID()); + + getClient().perform(post("/api/statistics/viewevents") + .content(mapper.writeValueAsBytes(viewEventRest2)) + .contentType(contentType)) + .andExpect(status().isCreated()); + + getClient().perform(post("/api/statistics/viewevents") + .content(mapper.writeValueAsBytes(viewEventRest2)) + .contentType(contentType)) + .andExpect(status().isCreated()); + + ViewEventRest viewEventRest3 = new ViewEventRest(); + viewEventRest3.setTargetType("item"); + viewEventRest3.setTargetId(item3.getID()); + + getClient().perform(post("/api/statistics/viewevents") + .content(mapper.writeValueAsBytes(viewEventRest3)) + .contentType(contentType)) + .andExpect(status().isCreated()); + + ViewEventRest viewEventRest4 = new ViewEventRest(); + viewEventRest4.setTargetType("item"); + viewEventRest4.setTargetId(item4.getID()); + + getClient().perform(post("/api/statistics/viewevents") + .content(mapper.writeValueAsBytes(viewEventRest4)) + .contentType(contentType)) + .andExpect(status().isCreated()); + + UsageReportPointDsoTotalVisitsRest expectedPoint1 = new UsageReportPointDsoTotalVisitsRest(); + expectedPoint1.addValue("views", 1); + expectedPoint1.setType("item"); + expectedPoint1.setLabel("My item"); + expectedPoint1.setId(item.getID().toString()); + + UsageReportPointDsoTotalVisitsRest expectedPoint2 = new UsageReportPointDsoTotalVisitsRest(); + expectedPoint2.addValue("views", 2); + expectedPoint2.setType("item"); + expectedPoint2.setLabel("My item 2"); + expectedPoint2.setId(item2.getID().toString()); + + UsageReportPointDsoTotalVisitsRest expectedPoint3 = new UsageReportPointDsoTotalVisitsRest(); + expectedPoint3.addValue("views", 1); + expectedPoint3.setType("item"); + expectedPoint3.setLabel("My item 3"); + expectedPoint3.setId(item3.getID().toString()); + + UsageReportPointDsoTotalVisitsRest expectedPoint4 = new UsageReportPointDsoTotalVisitsRest(); + expectedPoint4.addValue("views", 1); + expectedPoint4.setType("item"); + expectedPoint4.setLabel("My item 4"); + expectedPoint4.setId(item4.getID().toString()); + + List points = List.of(expectedPoint1, expectedPoint2, expectedPoint3, expectedPoint4); + + UsageReportPointCityRest pointCity = new UsageReportPointCityRest(); + pointCity.addValue("views", 5); + pointCity.setId("New York"); + + UsageReportPointContinentRest pointContinent = new UsageReportPointContinentRest(); + pointContinent.addValue("views", 5); + pointContinent.setId("North America"); + + UsageReportPointCountryRest pointCountry = new UsageReportPointCountryRest(); + pointCountry.addValue("views", 5); + pointCountry.setIdAndLabel(Locale.US.getCountry(), Locale.US.getDisplayCountry(context.getCurrentLocale())); + + UsageReportPointCategoryRest articleCategory = new UsageReportPointCategoryRest(); + articleCategory.addValue("views", 1); + articleCategory.setId("article"); + + UsageReportPointCategoryRest thesisCategory = new UsageReportPointCategoryRest(); + thesisCategory.addValue("views", 3); + thesisCategory.setId("thesis"); + + UsageReportPointCategoryRest otherCategory = new UsageReportPointCategoryRest(); + otherCategory.addValue("views", 1); + otherCategory.setId("other"); + + UsageReportPointCategoryRest bookCategory = new UsageReportPointCategoryRest(); + bookCategory.addValue("views", 0); + bookCategory.setId("book"); + + UsageReportPointCategoryRest bookChapterCategory = new UsageReportPointCategoryRest(); + bookChapterCategory.addValue("views", 0); + bookChapterCategory.setId("bookChapter"); + + UsageReportPointCategoryRest datasetCategory = new UsageReportPointCategoryRest(); + datasetCategory.addValue("views", 0); + datasetCategory.setId("dataset"); + + List categories = List.of(articleCategory, thesisCategory, otherCategory, bookCategory, + bookChapterCategory, datasetCategory); + + // And request the collections global usage report (show top most popular items) + getClient(adminToken) + .perform(get("/api/statistics/usagereports/search/object") + .param("category", "community-itemReports") + .param("uri", "http://localhost:8080/server/api/core/communities/" + community.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.usagereports", not(empty()))) + .andExpect(jsonPath("$._embedded.usagereports", Matchers.containsInAnyOrder( + matchUsageReport(community.getID() + "_" + TOTAL_ITEMS_VISITS_REPORT_ID, + TOP_ITEMS_REPORT_ID, points), + matchUsageReport(community.getID() + "_" + TOP_ITEMS_CITIES_REPORT_ID, + TOP_CITIES_REPORT_ID, List.of(pointCity)), + matchUsageReport(community.getID() + "_" + TOTAL_ITEMS_VISITS_PER_MONTH_REPORT_ID, + TOTAL_VISITS_PER_MONTH_REPORT_ID, getLastMonthVisitPoints(5)), + matchUsageReport(community.getID() + "_" + TOP_ITEMS_CONTINENTS_REPORT_ID, + TOP_CONTINENTS_REPORT_ID, List.of(pointContinent)), + matchUsageReport(community.getID() + "_" + TOP_ITEMS_CATEGORIES_REPORT_ID, + TOP_CATEGORIES_REPORT_ID, categories), + matchUsageReport(community.getID() + "_" + TOP_ITEMS_COUNTRIES_REPORT_ID, + TOP_COUNTRIES_REPORT_ID, List.of(pointCountry))))); + } + + @Test + public void usageReportsSearch_Community_DownloadReports() throws Exception { + + context.turnOffAuthorisationSystem(); + + Community community = CommunityBuilder.createCommunity(context).build(); + collectionNotVisited = CollectionBuilder.createCollection(context, community).build(); + + Item item1 = ItemBuilder.createItem(context, collectionNotVisited) + .withTitle("Item 1") + .build(); + + Item item2 = ItemBuilder.createItem(context, collectionNotVisited) + .withTitle("Item 2") + .build(); + + Item item3 = ItemBuilder.createItem(context, collectionNotVisited) + .withTitle("Item 3") + .build(); + + Bitstream bitstream1 = createBitstream(item1, "Bitstream 1"); + Bitstream bitstream2 = createBitstream(item1, "Bitstream 2"); + Bitstream bitstream3 = createBitstream(item2, "Bitstream 3"); + Bitstream bitstream4 = createBitstream(item3, "Bitstream 4"); + + getClient().perform(get("/api/core/bitstreams/" + bitstream1.getID() + "/content")) + .andExpect(status().isOk()); + + getClient().perform(get("/api/core/bitstreams/" + bitstream1.getID() + "/content")) + .andExpect(status().isOk()); + + getClient().perform(get("/api/core/bitstreams/" + bitstream2.getID() + "/content")) + .andExpect(status().isOk()); + + getClient().perform(get("/api/core/bitstreams/" + bitstream3.getID() + "/content")) + .andExpect(status().isOk()); + + getClient().perform(get("/api/core/bitstreams/" + bitstream3.getID() + "/content")) + .andExpect(status().isOk()); + + getClient().perform(get("/api/core/bitstreams/" + bitstream3.getID() + "/content")) + .andExpect(status().isOk()); + + getClient().perform(get("/api/core/bitstreams/" + bitstream4.getID() + "/content")) + .andExpect(status().isOk()); + + getClient().perform(get("/api/core/bitstreams/" + bitstream4.getID() + "/content")) + .andExpect(status().isOk()); + + context.restoreAuthSystemState(); + + UsageReportPointDsoTotalVisitsRest expectedPoint1 = new UsageReportPointDsoTotalVisitsRest(); + expectedPoint1.addValue("views", 3); + expectedPoint1.setType("item"); + expectedPoint1.setLabel("Item 1"); + expectedPoint1.setId(item1.getID().toString()); + + UsageReportPointDsoTotalVisitsRest expectedPoint2 = new UsageReportPointDsoTotalVisitsRest(); + expectedPoint2.addValue("views", 3); + expectedPoint2.setType("item"); + expectedPoint2.setLabel("Item 2"); + expectedPoint2.setId(item2.getID().toString()); + + UsageReportPointDsoTotalVisitsRest expectedPoint3 = new UsageReportPointDsoTotalVisitsRest(); + expectedPoint3.addValue("views", 2); + expectedPoint3.setType("item"); + expectedPoint3.setLabel("Item 3"); + expectedPoint3.setId(item3.getID().toString()); + + List points = List.of(expectedPoint1, expectedPoint2, expectedPoint3); + + UsageReportPointCityRest pointCity = new UsageReportPointCityRest(); + pointCity.addValue("views", 8); + pointCity.setId("New York"); + + UsageReportPointContinentRest pointContinent = new UsageReportPointContinentRest(); + pointContinent.addValue("views", 8); + pointContinent.setId("North America"); + + UsageReportPointCountryRest pointCountry = new UsageReportPointCountryRest(); + pointCountry.addValue("views", 8); + pointCountry.setIdAndLabel(Locale.US.getCountry(), Locale.US.getDisplayCountry(context.getCurrentLocale())); + + getClient(adminToken) + .perform(get("/api/statistics/usagereports/search/object") + .param("category", "community-downloadReports") + .param("uri", "http://localhost:8080/server/api/core/communities/" + community.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.usagereports", not(empty()))) + .andExpect(jsonPath("$._embedded.usagereports", Matchers.containsInAnyOrder( + matchUsageReport(community.getID() + "_" + TOTAL_DOWNLOADS_REPORT_ID, TOP_ITEMS_REPORT_ID, points), + matchUsageReport(community.getID() + "_" + TOP_DOWNLOAD_CITIES_REPORT_ID, + TOP_CITIES_REPORT_ID, List.of(pointCity)), + matchUsageReport(community.getID() + "_" + TOTAL_DOWNLOAD_PER_MONTH_REPORT_ID, + TOTAL_VISITS_PER_MONTH_REPORT_ID, getLastMonthVisitPoints(8)), + matchUsageReport(community.getID() + "_" + TOP_DOWNLOAD_CONTINENTS_REPORT_ID, + TOP_CONTINENTS_REPORT_ID, List.of(pointContinent)), + matchUsageReport(community.getID() + "_" + TOP_DOWNLOAD_COUNTRIES_REPORT_ID, + TOP_COUNTRIES_REPORT_ID, List.of(pointCountry))))); + } + private List getLastMonthVisitPoints(int viewsLastMonth) { return getListOfVisitsPerMonthsPoints(viewsLastMonth, 0); } 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 5a467ce3a3ce..a0343d67e93d 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 @@ -218,7 +218,7 @@ public void findSections() throws Exception { // 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(9))) + .andExpect(jsonPath("$._embedded.submissionsections", hasSize(10))) .andExpect(jsonPath("$._embedded.submissionsections", Matchers.hasItem( allOf( @@ -310,7 +310,7 @@ public void findAllPaginationTest() throws Exception { .param("page", "0")) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) - .andExpect(jsonPath("$._embedded.submissiondefinitions[0].id", is("patent"))) + .andExpect(jsonPath("$._embedded.submissiondefinitions[0].id", is("test-hidden"))) .andExpect(jsonPath("$._links.first.href", Matchers.allOf( Matchers.containsString("/api/config/submissiondefinitions?"), Matchers.containsString("page=0"), Matchers.containsString("size=1")))) @@ -322,10 +322,10 @@ public void findAllPaginationTest() throws Exception { Matchers.containsString("page=1"), Matchers.containsString("size=1")))) .andExpect(jsonPath("$._links.last.href", Matchers.allOf( Matchers.containsString("/api/config/submissiondefinitions?"), - Matchers.containsString("page=15"), Matchers.containsString("size=1")))) + Matchers.containsString("page=16"), Matchers.containsString("size=1")))) .andExpect(jsonPath("$.page.size", is(1))) - .andExpect(jsonPath("$.page.totalElements", is(16))) - .andExpect(jsonPath("$.page.totalPages", is(16))) + .andExpect(jsonPath("$.page.totalElements", is(17))) + .andExpect(jsonPath("$.page.totalPages", is(17))) .andExpect(jsonPath("$.page.number", is(0))); getClient(tokenAdmin).perform(get("/api/config/submissiondefinitions") @@ -333,7 +333,7 @@ public void findAllPaginationTest() throws Exception { .param("page", "1")) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) - .andExpect(jsonPath("$._embedded.submissiondefinitions[0].id", is("accessConditionNotDiscoverable"))) + .andExpect(jsonPath("$._embedded.submissiondefinitions[0].id", is("patent"))) .andExpect(jsonPath("$._links.first.href", Matchers.allOf( Matchers.containsString("/api/config/submissiondefinitions?"), Matchers.containsString("page=0"), Matchers.containsString("size=1")))) @@ -348,10 +348,10 @@ public void findAllPaginationTest() throws Exception { Matchers.containsString("page=1"), Matchers.containsString("size=1")))) .andExpect(jsonPath("$._links.last.href", Matchers.allOf( Matchers.containsString("/api/config/submissiondefinitions?"), - Matchers.containsString("page=15"), Matchers.containsString("size=1")))) + Matchers.containsString("page=16"), Matchers.containsString("size=1")))) .andExpect(jsonPath("$.page.size", is(1))) - .andExpect(jsonPath("$.page.totalElements", is(16))) - .andExpect(jsonPath("$.page.totalPages", is(16))) + .andExpect(jsonPath("$.page.totalElements", is(17))) + .andExpect(jsonPath("$.page.totalPages", is(17))) .andExpect(jsonPath("$.page.number", is(1))); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionFormsControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionFormsControllerIT.java index 43f80037b16b..9236e7c4ce25 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionFormsControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionFormsControllerIT.java @@ -51,8 +51,8 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe @Autowired private ChoiceAuthorityService cas; - private final static int PAGE_TOTAL_ELEMENTS = 31; - private final static int PAGE_TOTAL_PAGES = 16; + private final static int PAGE_TOTAL_ELEMENTS = 33; + private final static int PAGE_TOTAL_PAGES = 17; @Test public void findAll() throws Exception { diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionSectionsControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionSectionsControllerIT.java index 8ff9ff19217f..5fd5ba9fba50 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionSectionsControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionSectionsControllerIT.java @@ -68,8 +68,7 @@ public void testFindOne() throws Exception { getClient(token).perform(get("/api/config/submissionsections/collection")) .andExpect(status().isOk()) - .andExpect(jsonPath("$", matches("collection", true, "collection", - of("submission", "hidden", "workflow", "hidden", "edit", "hidden")))); + .andExpect(jsonPath("$", matches("collection", true, "collection"))); getClient(token).perform(get("/api/config/submissionsections/traditionalpageone")) .andExpect(status().isOk()) 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 000000000000..8d1912b29759 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionShowIdentifiersRestIT.java @@ -0,0 +1,136 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the 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") + .withSubmissionDefinition("traditional") + .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 index fa45af74f8b6..6a086cc6b75d 100644 --- 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 @@ -7,52 +7,40 @@ */ 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.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; 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.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.apache.logging.log4j.Logger; +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.model.patch.AddOperation; -import org.dspace.app.rest.model.patch.Operation; -import org.dspace.app.rest.model.patch.RemoveOperation; -import org.dspace.app.rest.model.patch.ReplaceOperation; 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.builder.SubscribeBuilder; 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.dspace.eperson.Subscription; import org.dspace.eperson.SubscriptionParameter; import org.hamcrest.Matchers; @@ -60,860 +48,1226 @@ import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; /** - * 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) + * 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 { - private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(SubscriptionRestRepositoryIT.class); - private Collection collection; - private Item publicItem; + @Autowired private ResourcePolicyService resourcePolicyService; - @Override + 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(); - Community community = CommunityBuilder.createSubCommunity(context, parentCommunity).withName("Sub Community") - .build(); - collection = CollectionBuilder.createCollection(context, community).withName("Collection 1").build(); - // creation of the item which will be the DSO related with a subscription - publicItem = ItemBuilder.createItem(context, collection).withTitle("Test").withIssueDate("2010-10-17") - .withAuthor("Smith, Donald").withSubject("ExtraEntry").build(); + + 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(); } - // FIND ALL @Test public void findAll() throws Exception { context.turnOffAuthorisationSystem(); - String token = getAuthToken(admin.getEmail(), password); List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); - subscriptionParameter.setName("Frequency"); - subscriptionParameter.setValue("Daily"); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("D"); subscriptionParameterList.add(subscriptionParameter); - Subscription subscription = SubscribeBuilder - .subscribeBuilder(context, "TypeTest", publicItem, admin, subscriptionParameterList).build(); - subscriptionParameter.setSubscription(subscription); - // When we call the root endpoint + 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(); - getClient(token).perform(get("/api/core/subscriptions")) - // 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)) - // By default we expect at least 1 submission forms so this to be reflected in - // the page object - .andExpect(jsonPath("$.page.size", is(20))) - .andExpect(jsonPath("$.page.totalElements", greaterThanOrEqualTo(1))) - .andExpect(jsonPath("$.page.totalPages", greaterThanOrEqualTo(1))) - .andExpect(jsonPath("$.page.number", is(0))) - .andExpect(jsonPath("$._embedded.subscriptions[0].subscriptionType", is("TypeTest"))) - .andExpect(jsonPath("$._embedded.subscriptions[0]._links.dSpaceObject.href", - Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) - .andExpect(jsonPath("$._embedded.subscriptions[0]._links.dSpaceObject.href", - Matchers.endsWith("dSpaceObject"))) - .andExpect(jsonPath("$._embedded.subscriptions[0]._links.ePerson.href", - Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) - .andExpect(jsonPath("$._embedded.subscriptions[0]._links.ePerson.href", Matchers.endsWith("ePerson"))) - .andExpect(jsonPath("$._embedded.subscriptions[0].subscriptionParameterList[0].name", is("Frequency"))) - .andExpect(jsonPath("$._embedded.subscriptions[0].subscriptionParameterList[0].value", is("Daily"))) - .andExpect(jsonPath("$._links.self.href", Matchers.is(REST_SERVER_URL + "core/subscriptions"))); + + 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 { - // When we call the root endpoint as anonymous user getClient().perform(get("/api/core/subscriptions")) - // The status has to be 401 Not Authorized - .andExpect(status().isUnauthorized()); + .andExpect(status().isUnauthorized()); } @Test public void findAllAsUser() throws Exception { - context.turnOffAuthorisationSystem(); - String token = getAuthToken(eperson.getEmail(), password); - context.restoreAuthSystemState(); - // When we call the root endpoint as simple user - getClient(token).perform(get("/api/core/subscriptions")) - // The status has to be 403 Forbidden - .andExpect(status().isForbidden()); + String tokenEPerson = getAuthToken(eperson.getEmail(), password); + getClient(tokenEPerson).perform(get("/api/core/subscriptions")) + .andExpect(status().isForbidden()); } @Test - public void findAllWithResourceType() throws Exception { + public void findOneWithOwnerTest() throws Exception { context.turnOffAuthorisationSystem(); - // When we call the root endpoint as anonymous user - getClient().perform(get("/api/core/subscriptions")) - // The status has to be 401 Not Authorized - .andExpect(status().isUnauthorized()); - String token = getAuthToken(admin.getEmail(), password); List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); - subscriptionParameter.setName("Frequency"); - subscriptionParameter.setValue("Daily"); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("M"); subscriptionParameterList.add(subscriptionParameter); - Subscription subscription = SubscribeBuilder - .subscribeBuilder(context, "TypeTest", publicItem, admin, subscriptionParameterList).build(); - subscriptionParameter.setSubscription(subscription); - // When we call the root endpoint + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, + "content", collection, eperson, subscriptionParameterList).build(); context.restoreAuthSystemState(); - getClient(token).perform(get("/api/core/subscriptions?resourceType=Item")) - // The status has to be 200 OK + + 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" + //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("$.page.size", is(20))) - .andExpect(jsonPath("$.page.totalElements", greaterThanOrEqualTo(1))) - .andExpect(jsonPath("$.page.totalPages", greaterThanOrEqualTo(1))) - .andExpect(jsonPath("$.page.number", is(0))) - .andExpect(jsonPath("$._embedded.subscriptions[0].subscriptionType", is("TypeTest"))) - .andExpect(jsonPath("$._embedded.subscriptions[0]._links.dSpaceObject.href", - Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) - .andExpect(jsonPath("$._embedded.subscriptions[0]._links.dSpaceObject.href", - Matchers.endsWith("dSpaceObject"))) - .andExpect(jsonPath("$._embedded.subscriptions[0]._links.ePerson.href", - Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) - .andExpect(jsonPath("$._embedded.subscriptions[0]._links.ePerson.href", Matchers.endsWith("ePerson"))) - .andExpect(jsonPath("$._embedded.subscriptions[0].subscriptionParameterList[0].name", is("Frequency"))) - .andExpect(jsonPath("$._embedded.subscriptions[0].subscriptionParameterList[0].value", is("Daily"))) + //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.is(REST_SERVER_URL + "core/subscriptions?resourceType=Item"))); - // search for subscriptions related with collections - getClient(token).perform(get("/api/core/subscriptions?resourceType=Collection")) - // 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)) - // By default we expect at least 1 submission forms so this to be reflected in - // the page object - .andExpect(jsonPath("$.page.size", is(20))) - .andExpect(jsonPath("$.page.totalElements", greaterThanOrEqualTo(0))) - .andExpect(jsonPath("$.page.totalPages", greaterThanOrEqualTo(0))) - .andExpect(jsonPath("$.page.number", is(0))).andExpect(jsonPath("$._links.self.href", - Matchers.is(REST_SERVER_URL + "core/subscriptions?resourceType=Collection"))); + 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"))); } - // FIND BY ID @Test - public void findByIdAsAdministrator() throws Exception { + public void findOneAdminTest() throws Exception { context.turnOffAuthorisationSystem(); - String token = getAuthToken(admin.getEmail(), password); + List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); - subscriptionParameter.setName("Parameter"); - subscriptionParameter.setValue("ValueParameter"); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("W"); subscriptionParameterList.add(subscriptionParameter); - Subscription subscription = SubscribeBuilder - .subscribeBuilder(context, "TestType", publicItem, admin, subscriptionParameterList).build(); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, + "content", collection, admin, subscriptionParameterList).build(); context.restoreAuthSystemState(); - // When we call the root endpoint - getClient(token).perform(get("/api/core/subscriptions/" + subscription.getID())) - // The status has to be 200 OK + + 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" + //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("TestType"))) - .andExpect(jsonPath("$.subscriptionParameterList[0].name", is("Parameter"))) - .andExpect(jsonPath("$.subscriptionParameterList[0].value", is("ValueParameter"))) + //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.dSpaceObject.href", - Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) - .andExpect(jsonPath("$._links.dSpaceObject.href", Matchers.endsWith("/dSpaceObject"))) - .andExpect( - jsonPath("$._links.ePerson.href", Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) - .andExpect(jsonPath("$._links.ePerson.href", Matchers.endsWith("/ePerson"))); - + 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 findByIdAsAnonymous() throws Exception { + public void findOneAnonymousTest() throws Exception { context.turnOffAuthorisationSystem(); List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); - subscriptionParameter.setName("Parameter"); - subscriptionParameter.setValue("ValueParameter"); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("D"); subscriptionParameterList.add(subscriptionParameter); - Subscription subscription = SubscribeBuilder - .subscribeBuilder(context, "TestType", publicItem, admin, subscriptionParameterList).build(); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, + "content", collection, admin, subscriptionParameterList).build(); context.restoreAuthSystemState(); - // When we call the root endpoint + getClient().perform(get("/api/core/subscriptions/" + subscription.getID())) - // The status has to be 401 - .andExpect(status().isUnauthorized()); + .andExpect(status().isUnauthorized()); } @Test - public void findByIdNotAsSubscriberNotAsAdmin() throws Exception { + public void findOneForbiddenTest() throws Exception { context.turnOffAuthorisationSystem(); - String token = getAuthToken(eperson.getEmail(), password); List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); - subscriptionParameter.setName("Parameter"); - subscriptionParameter.setValue("ValueParameter"); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("W"); subscriptionParameterList.add(subscriptionParameter); - Subscription subscription = SubscribeBuilder - .subscribeBuilder(context, "TestType", publicItem, admin, subscriptionParameterList).build(); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, + "content", collection, admin, subscriptionParameterList).build(); context.restoreAuthSystemState(); - // When we call the root endpoint - getClient(token).perform(get("/api/core/subscriptions/" + subscription.getID())) - // The status has to be 500 - .andExpect(status().isInternalServerError()); + + String tokenEPerson = getAuthToken(eperson.getEmail(), password); + getClient(tokenEPerson).perform(get("/api/core/subscriptions/" + subscription.getID())) + .andExpect(status().isForbidden()); } @Test - public void findByIdAsAdministratorEmbedDSO() throws Exception { + 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(); - String token = getAuthToken(admin.getEmail(), password); List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); - subscriptionParameter.setName("Parameter"); - subscriptionParameter.setValue("ValueParameter"); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("D"); subscriptionParameterList.add(subscriptionParameter); - Subscription subscription = SubscribeBuilder.subscribeBuilder(context, "TestType", publicItem, admin, - subscriptionParameterList).build(); + 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(); - //When we call the root endpoint - getClient(token).perform(get("/api/core/subscriptions/" + subscription.getID() + "?embed=dSpaceObject")) - //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)) - //By default we expect at least 1 submission forms so this to be reflected in the page object - .andExpect(jsonPath("$.subscriptionType", is("TestType"))) - .andExpect(jsonPath("$.subscriptionParameterList[0].name", is("Parameter"))) - .andExpect(jsonPath("$.subscriptionParameterList[0].value", is("ValueParameter"))) - .andExpect(jsonPath("$._links.self.href", Matchers.startsWith( - REST_SERVER_URL + "core/subscriptions/" + subscription.getID()))) - .andExpect(jsonPath("$._links.dSpaceObject.href", Matchers.startsWith( - REST_SERVER_URL + "core/subscriptions"))) - .andExpect(jsonPath("$._links.dSpaceObject.href", Matchers.endsWith("/dSpaceObject"))) - .andExpect(jsonPath("$._links.ePerson.href", Matchers.startsWith( - REST_SERVER_URL + "core/subscriptions"))) - .andExpect(jsonPath("$._links.ePerson.href", Matchers.endsWith("/ePerson"))) - .andExpect(jsonPath("$._embedded.dSpaceObject.id", is(publicItem.getID().toString()))) - .andExpect(jsonPath("$._embedded.dSpaceObject.name",is(publicItem.getName()))); + + 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 findByIdAsAdministratorEmbedEPerson() throws Exception { + public void findSubscriptionsByEPersonOwnerTest() throws Exception { context.turnOffAuthorisationSystem(); - String token = getAuthToken(admin.getEmail(), password); List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); - subscriptionParameter.setName("Parameter"); - subscriptionParameter.setValue("ValueParameter"); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("M"); subscriptionParameterList.add(subscriptionParameter); - Subscription subscription = SubscribeBuilder.subscribeBuilder(context, "TestType", publicItem, - admin, subscriptionParameterList).build(); + 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(); - //When we call the root endpoint - getClient(token).perform(get("/api/core/subscriptions/" + subscription.getID() + "?embed=ePerson")) - //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)) - //By default we expect at least 1 submission forms so this to be reflected in the page object - .andExpect(jsonPath("$.subscriptionType", is("TestType"))) - .andExpect(jsonPath("$.subscriptionParameterList[0].name", is("Parameter"))) - .andExpect(jsonPath("$.subscriptionParameterList[0].value", is("ValueParameter"))) - .andExpect(jsonPath("$._links.self.href", Matchers.startsWith( - REST_SERVER_URL + "core/subscriptions/" + subscription.getID()))) - .andExpect(jsonPath("$._links.dSpaceObject.href", Matchers.startsWith( - REST_SERVER_URL + "core/subscriptions"))) - .andExpect(jsonPath("$._links.dSpaceObject.href", Matchers.endsWith("/dSpaceObject"))) - .andExpect(jsonPath("$._links.ePerson.href", Matchers.startsWith( - REST_SERVER_URL + "core/subscriptions"))) - .andExpect(jsonPath("$._links.ePerson.href", Matchers.endsWith("/ePerson"))) - .andExpect(jsonPath("$._embedded.ePerson.id", is(admin.getID().toString()))) - .andExpect(jsonPath("$._embedded.ePerson.name",is(admin.getName()))); + + 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 findByIdAsAdministratorProjectionFull() throws Exception { + public void findSubscriptionsByEPersonUnauthorizedTest() throws Exception { context.turnOffAuthorisationSystem(); - String token = getAuthToken(admin.getEmail(), password); List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); - subscriptionParameter.setName("Parameter"); - subscriptionParameter.setValue("ValueParameter"); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("D"); subscriptionParameterList.add(subscriptionParameter); - Subscription subscription = SubscribeBuilder.subscribeBuilder(context, "TestType", - publicItem, admin, subscriptionParameterList).build(); + SubscribeBuilder.subscribeBuilder(context, "content", collection, eperson, subscriptionParameterList).build(); context.restoreAuthSystemState(); - //When we call the root endpoint - getClient(token).perform(get("/api/core/subscriptions/" + subscription.getID() + "?projection=full")) - //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)) - //By default we expect at least 1 submission forms so this to be reflected in the page object - .andExpect(jsonPath("$.subscriptionType", is("TestType"))) - .andExpect(jsonPath("$.subscriptionParameterList[0].name", is("Parameter"))) - .andExpect(jsonPath("$.subscriptionParameterList[0].value", is("ValueParameter"))) - .andExpect(jsonPath("$._links.self.href", Matchers.startsWith( - REST_SERVER_URL + "core/subscriptions/" + subscription.getID()))) - .andExpect(jsonPath("$._links.dSpaceObject.href", - Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) - .andExpect(jsonPath("$._links.dSpaceObject.href", Matchers.endsWith("/dSpaceObject"))) - .andExpect(jsonPath("$._links.ePerson.href", - Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) - .andExpect(jsonPath("$._links.ePerson.href", Matchers.endsWith("/ePerson"))) - .andExpect(jsonPath("$._embedded.ePerson.id", is(admin.getID().toString()))) - .andExpect(jsonPath("$._embedded.ePerson.name",is(admin.getName()))) - .andExpect(jsonPath("$._embedded.ePerson.type",is("eperson"))) - .andExpect(jsonPath("$._embedded.dSpaceObject.id", is(publicItem.getID().toString()))) - .andExpect(jsonPath("$._embedded.dSpaceObject.name",is(publicItem.getName()))) - .andExpect(jsonPath("$._embedded.dSpaceObject.type",is("item"))); + getClient().perform(get("/api/core/subscriptions/search/findByEPerson") + .param("uuid", eperson.getID().toString())) + .andExpect(status().isUnauthorized()); } - // FIND ALL BY EPERSON/DSO + @Test - public void findAllSubscriptionsByEPerson() throws Exception { + public void findSubscriptionsByEPersonForbiddenTest() throws Exception { context.turnOffAuthorisationSystem(); - EPerson user = EPersonBuilder.createEPerson(context).withEmail("user@test.it").withPassword(password).build(); + EPerson user = EPersonBuilder.createEPerson(context) + .withEmail("user1@mail.com") + .withPassword(password) + .build(); + List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); - subscriptionParameter.setName("Parameter1"); - subscriptionParameter.setValue("ValueParameter1"); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("D"); subscriptionParameterList.add(subscriptionParameter); - Subscription subscription = SubscribeBuilder - .subscribeBuilder(context, "TestType", publicItem, user, subscriptionParameterList).build(); + SubscribeBuilder.subscribeBuilder(context, "content", collection, user, subscriptionParameterList).build(); context.restoreAuthSystemState(); - // When we call the root endpoint - String token = getAuthToken(user.getEmail(), password); - getClient(token).perform(get("/api/core/subscriptions/search/findByEPerson?id=" + user.getID())) - // The status has to be 200 OK - .andExpect(status().isOk()).andExpect(jsonPath("$.page.size", is(20))) - .andExpect(jsonPath("$.page.totalElements", greaterThanOrEqualTo(1))) - .andExpect(jsonPath("$.page.totalPages", greaterThanOrEqualTo(1))) - .andExpect(jsonPath("$.page.number", is(0))) - .andExpect(jsonPath("$._embedded.subscriptions[0].subscriptionType", is("TestType"))) - .andExpect(jsonPath("$._embedded.subscriptions[0]._links.dSpaceObject.href", - Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) - .andExpect(jsonPath("$._embedded.subscriptions[0]._links.dSpaceObject.href", - Matchers.endsWith("dSpaceObject"))) - .andExpect(jsonPath("$._embedded.subscriptions[0]._links.ePerson.href", - Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) - .andExpect(jsonPath("$._embedded.subscriptions[0]._links.ePerson.href", Matchers.endsWith("ePerson"))) - .andExpect(jsonPath("$._embedded.subscriptions[0].subscriptionParameterList[0].name", is("Parameter1"))) - .andExpect(jsonPath("$._embedded.subscriptions[0].subscriptionParameterList[0].value", - is("ValueParameter1"))); + 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 findAllSubscriptionsByEPersonAndDSO() throws Exception { + public void findByEPersonAndDsoAdminTest() throws Exception { context.turnOffAuthorisationSystem(); - EPerson user = EPersonBuilder.createEPerson(context).withEmail("user@test.it").withPassword(password).build(); List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); - subscriptionParameter.setName("Parameter1"); - subscriptionParameter.setValue("ValueParameter1"); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("D"); subscriptionParameterList.add(subscriptionParameter); - List subscriptionParameterList1 = new ArrayList<>(); - SubscriptionParameter subscriptionParameter1 = new SubscriptionParameter(); - subscriptionParameter1.setName("Parameter1"); - subscriptionParameter1.setValue("ValueParameter1"); - subscriptionParameterList1.add(subscriptionParameter1); - Subscription subscription = SubscribeBuilder - .subscribeBuilder(context, "TestType", collection, user, subscriptionParameterList).build(); - Subscription subscription1 = SubscribeBuilder - .subscribeBuilder(context, "Test", collection, user, subscriptionParameterList1).build(); - context.restoreAuthSystemState(); - // When we call the root endpoint - String token = getAuthToken(admin.getEmail(), password); - getClient(token) - .perform(get("/api/core/subscriptions/search/findByEPersonAndDso?dspace_object_id=" + collection.getID() - + "&eperson_id=" + user.getID())) - // The status has to be 200 OK - .andExpect(status().isOk()).andExpect(jsonPath("$.page.size", is(20))) - .andExpect(jsonPath("$.page.totalElements", greaterThanOrEqualTo(2))) - .andExpect(jsonPath("$.page.totalPages", greaterThanOrEqualTo(1))) - .andExpect(jsonPath("$.page.number", is(0))) - .andExpect(jsonPath("$._embedded.subscriptions", Matchers.containsInAnyOrder( - SubscriptionMatcher.matchSubscription(subscription), - SubscriptionMatcher.matchSubscription(subscription1) - ))); - } - - // ADD - @Test - public void addSubscriptionNotLoggedIn() throws Exception { - SubscriptionParameterRest subscriptionParameterRest = new SubscriptionParameterRest(); - subscriptionParameterRest.setValue("nameTest"); - subscriptionParameterRest.setName("valueTest"); - List subscriptionParameterRestList = new ArrayList<>(); - subscriptionParameterRestList.add(subscriptionParameterRest); - SubscriptionRest subscriptionRest = new SubscriptionRest(); - subscriptionRest.setType("testType"); - MultiValueMap params = new LinkedMultiValueMap(); - params.add("dspace_object_id", publicItem.getID().toString()); - params.add("eperson_id", eperson.getID().toString()); - ObjectMapper objectMapper = new ObjectMapper(); - resourcePolicyService.find(context, publicItem); - // remove default anonymous policy in order to test it - //resourcePolicyService.delete(context, resourcePolicyService.find(context, publicItem).get(0)); - getClient() - .perform(post("/api/core/subscriptions?dspace_object_id=" + publicItem.getID() + "&eperson_id=" - + eperson.getID()).content(objectMapper.writeValueAsString(subscriptionRest)) - .contentType(contentType)) - // The status has to be 401 Not Authorized - .andExpect(status().isUnauthorized()); + 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 addSubscriptionAsAdmin() throws Exception { - // When we call the root endpoint as anonymous user + 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("nameTest"); - subscriptionParameterRest.setName("valueTest"); + subscriptionParameterRest.setValue("frequency"); + subscriptionParameterRest.setName("D"); List subscriptionParameterRestList = new ArrayList<>(); subscriptionParameterRestList.add(subscriptionParameterRest); + SubscriptionRest subscriptionRest = new SubscriptionRest(); - subscriptionRest.setType("testType"); -// subscriptionRest.setSubscriptionParameterList(subscriptionParameterRestList); + subscriptionRest.setSubscriptionType("content"); + + context.restoreAuthSystemState(); + ObjectMapper objectMapper = new ObjectMapper(); - String token = getAuthToken(admin.getEmail(), password); + + 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("type", "test"); + map.put("subscriptionType", "content"); List> list = new ArrayList<>(); Map sub_list = new HashMap<>(); sub_list.put("name", "frequency"); - sub_list.put("value", "daily"); + sub_list.put("value", "D"); list.add(sub_list); map.put("subscriptionParameterList", list); - getClient(token).perform( - post("/api/core/subscriptions?dspace_object_id=" + publicItem.getID() + "&eperson_id=" + admin.getID()) - .content(objectMapper.writeValueAsString(map)).contentType(MediaType.APPLICATION_JSON_VALUE)) - // The status has to be 200 OK - .andExpect(status().isCreated()) - // We expect the content type to be "application/hal+json;charset=UTF-8" - // By default we expect at least 1 submission forms so this to be reflected in - // the page object - .andExpect(jsonPath("$.subscriptionType", is("test"))) - .andExpect(jsonPath("$.subscriptionParameterList[0].name", is("frequency"))) - .andExpect(jsonPath("$.subscriptionParameterList[0].value", is("daily"))) - .andExpect(jsonPath("$._links.dSpaceObject.href", - Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) - .andExpect(jsonPath("$._links.dSpaceObject.href", Matchers.endsWith("/dSpaceObject"))) - .andExpect( - jsonPath("$._links.ePerson.href", Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) - .andExpect(jsonPath("$._links.ePerson.href", Matchers.endsWith("/ePerson"))); + + 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 addSubscriptionAsSimpleUser() throws Exception { - // When we call the root endpoint as anonymous user - SubscriptionParameterRest subscriptionParameterRest = new SubscriptionParameterRest(); - subscriptionParameterRest.setValue("nameTest"); - subscriptionParameterRest.setName("valueTest"); - List subscriptionParameterRestList = new ArrayList<>(); - subscriptionParameterRestList.add(subscriptionParameterRest); - SubscriptionRest subscriptionRest = new SubscriptionRest(); - subscriptionRest.setType("testType"); -// subscriptionRest.setSubscriptionParameterList(subscriptionParameterRestList); - ObjectMapper objectMapper = new ObjectMapper(); + public void createSubscriptionByEPersonTest() throws Exception { context.turnOffAuthorisationSystem(); - EPerson ePersonAuthorized = EPersonBuilder.createEPerson(context) - .withCanLogin(true) - .withPassword(password) - .withEmail("test@email.it") - .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 token = getAuthToken(ePersonAuthorized.getEmail(), password); + + 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("type", "test"); + map.put("subscriptionType", "content"); List> list = new ArrayList<>(); Map sub_list = new HashMap<>(); sub_list.put("name", "frequency"); - sub_list.put("value", "daily"); + sub_list.put("value", "W"); list.add(sub_list); map.put("subscriptionParameterList", list); - getClient(token).perform( - post("/api/core/subscriptions?dspace_object_id=" + publicItem.getID() - + "&eperson_id=" + ePersonAuthorized.getID()) - .content(objectMapper.writeValueAsString(map)) - .contentType(MediaType.APPLICATION_JSON_VALUE)) - // The status has to be 200 OK - .andExpect(status().isCreated()) - // We expect the content type to be "application/hal+json;charset=UTF-8" - // By default we expect at least 1 submission forms so this to be reflected in - // the page object - .andExpect(jsonPath("$.subscriptionType", is("test"))) - .andExpect(jsonPath("$.subscriptionParameterList[0].name", is("frequency"))) - .andExpect(jsonPath("$.subscriptionParameterList[0].value", is("daily"))) - .andExpect(jsonPath("$._links.dSpaceObject.href", - Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) - .andExpect(jsonPath("$._links.dSpaceObject.href", Matchers.endsWith("/dSpaceObject"))) - .andExpect( - jsonPath("$._links.ePerson.href", Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) - .andExpect(jsonPath("$._links.ePerson.href", Matchers.endsWith("/ePerson"))); + + context.restoreAuthSystemState(); + + AtomicReference idRef = new AtomicReference(); + String tokenEPerson = getAuthToken(eperson.getEmail(), password); + + try { + 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().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 addSubscriptionAsSimpleUserNoReadAccess() throws Exception { - // When we call the root endpoint as anonymous user - SubscriptionParameterRest subscriptionParameterRest = new SubscriptionParameterRest(); - subscriptionParameterRest.setValue("nameTest"); - subscriptionParameterRest.setName("valueTest"); - List subscriptionParameterRestList = new ArrayList<>(); - subscriptionParameterRestList.add(subscriptionParameterRest); - SubscriptionRest subscriptionRest = new SubscriptionRest(); - subscriptionRest.setType("testType"); -// subscriptionRest.setSubscriptionParameterList(subscriptionParameterRestList); - ObjectMapper objectMapper = new ObjectMapper(); + 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(); + + AtomicReference idRef = new AtomicReference(); + String tokenAdmin = getAuthToken(admin.getEmail(), password); + + try { + 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().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 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(); - // 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(); - EPerson ePersonAuthorized = EPersonBuilder.createEPerson(context) - .withCanLogin(true) - .withPassword(password) - .withEmail("test@email.it") - .build(); - cleanUpPermissions(resourcePolicyService.find(context, publicItem)); - setPermissions(publicItem, groupWithReadPermission, Constants.READ); - context.restoreAuthSystemState(); - String token = getAuthToken(ePersonAuthorized.getEmail(), password); + Map map = new HashMap<>(); - map.put("type", "test"); + 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); - getClient(token).perform( - post("/api/core/subscriptions?dspace_object_id=" - + publicItem.getID() + "&eperson_id=" + ePersonAuthorized.getID()) - .content(objectMapper.writeValueAsString(map)) - .contentType(MediaType.APPLICATION_JSON_VALUE)) - // The status has to be 403 Forbidden - .andExpect(status().isForbidden()); + + 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()); } - // PUT @Test - public void editSubscriptionAnonymous() throws Exception { + 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("Parameter"); - subscriptionParameter.setValue("ValueParameter"); + subscriptionParameter.setName("Parameter1"); + subscriptionParameter.setValue("ValueParameter1"); subscriptionParameterList.add(subscriptionParameter); - Subscription subscription = SubscribeBuilder - .subscribeBuilder(context, "TestType", publicItem, admin, subscriptionParameterList).build(); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, + "content", collection, admin, subscriptionParameterList).build(); + context.restoreAuthSystemState(); + ObjectMapper objectMapper = new ObjectMapper(); - String token = getAuthToken(admin.getEmail(), password); Map newSubscription = new HashMap<>(); - newSubscription.put("type", "test"); + 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(); - // When we call the root endpoint as anonymous user - getClient() - .perform(put("/api/core/subscriptions/" + subscription.getID() + "?dspace_object_id=" - + publicItem.getID() + "&eperson_id=" + admin.getID()) - .content(objectMapper.writeValueAsString(newSubscription)) - .contentType(MediaType.APPLICATION_JSON_VALUE)) - // The status has to be 403 Not Authorized - .andExpect(status().isUnauthorized()); + + 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 editSubscriptionNotAsSubscriberNotAsAdmin() throws Exception { + public void putSubscriptionTest() throws Exception { context.turnOffAuthorisationSystem(); - EPerson epersonIT = EPersonBuilder.createEPerson(context).withEmail("epersonIT@example.com") - .withPassword(password).withLanguage("al").build(); List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); - subscriptionParameter.setName("Parameter1"); - subscriptionParameter.setValue("ValueParameter1"); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("D"); subscriptionParameterList.add(subscriptionParameter); - Subscription subscription = SubscribeBuilder - .subscribeBuilder(context, "TestType", publicItem, eperson, subscriptionParameterList).build(); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, + "content", collection, eperson, subscriptionParameterList).build(); context.restoreAuthSystemState(); + ObjectMapper objectMapper = new ObjectMapper(); - String token = getAuthToken(epersonIT.getEmail(), password); Map newSubscription = new HashMap<>(); - newSubscription.put("type", "test"); + newSubscription.put("subscriptionType", "content"); List> list = new ArrayList<>(); Map sub_list = new HashMap<>(); sub_list.put("name", "frequency"); - sub_list.put("value", "daily"); + sub_list.put("value", "W"); list.add(sub_list); newSubscription.put("subscriptionParameterList", list); - // When we call the root endpoint as anonymous user - getClient(token) - .perform(put("/api/core/subscriptions/" + subscription.getID() + "?dspace_object_id=" - + publicItem.getID() + "&eperson_id=" + admin.getID()) - .content(objectMapper.writeValueAsString(newSubscription)) - .contentType(MediaType.APPLICATION_JSON_VALUE)) - // The status has to be 500 Error - .andExpect(status().isInternalServerError()); + + 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 editSubscriptionAsAdministratorOrSubscriber() throws Exception { + public void putSubscriptionInvalidSubscriptionParameterNameTest() throws Exception { context.turnOffAuthorisationSystem(); List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); - subscriptionParameter.setName("Frequency"); - subscriptionParameter.setValue("Daily"); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("D"); subscriptionParameterList.add(subscriptionParameter); - Subscription subscription = SubscribeBuilder - .subscribeBuilder(context, "TestType", publicItem, eperson, subscriptionParameterList).build(); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, + "content", collection, eperson, subscriptionParameterList).build(); context.restoreAuthSystemState(); + ObjectMapper objectMapper = new ObjectMapper(); Map newSubscription = new HashMap<>(); - newSubscription.put("type", "test"); + 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", "daily"); + 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() + "?dspace_object_id=" - + publicItem.getID() + "&eperson_id=" + eperson.getID()) - // The status has to be 403 Not Authorized - .content(objectMapper.writeValueAsString(newSubscription)) - .contentType(MediaType.APPLICATION_JSON_VALUE)) - .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("test"))) - .andExpect(jsonPath("$.subscriptionParameterList[0].name", is("frequency"))) - .andExpect(jsonPath("$.subscriptionParameterList[0].value", is("daily"))) - .andExpect(jsonPath("$._links.self.href", Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) - .andExpect(jsonPath("$._links.dSpaceObject.href", - Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) - .andExpect(jsonPath("$._links.dSpaceObject.href", Matchers.endsWith("/dSpaceObject"))) - .andExpect( - jsonPath("$._links.ePerson.href", Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) - .andExpect(jsonPath("$._links.ePerson.href", Matchers.endsWith("/ePerson"))); + getClient(tokenSubscriber).perform(put("/api/core/subscriptions/" + subscription.getID()) + .content(objectMapper.writeValueAsString(newSubscription)) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isUnprocessableEntity()); } - // DELETE @Test - public void deleteSubscriptionNotAsSubscriberNotAsAdmin() throws Exception { + public void putSubscriptionInvalidSubscriptionTypeValueTest() throws Exception { context.turnOffAuthorisationSystem(); - EPerson epersonIT = EPersonBuilder.createEPerson(context).withEmail("epersonIT@example.com") - .withPassword(password).withLanguage("al").build(); - String epersonITtoken = getAuthToken(epersonIT.getEmail(), password); List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); - subscriptionParameter.setName("Frequency"); - subscriptionParameter.setValue("Daily"); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("D"); subscriptionParameterList.add(subscriptionParameter); - Subscription subscription = SubscribeBuilder - .subscribeBuilder(context, "Test", publicItem, eperson, subscriptionParameterList).build(); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, + "content", collection, eperson, subscriptionParameterList).build(); context.restoreAuthSystemState(); - getClient(epersonITtoken).perform(delete("/api/core/subscriptions/" + subscription.getID())) - // The status has to be 403 Not Authorized - .andExpect(status().isForbidden()); + + 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 deleteSubscriptionAsAdmin() throws Exception { + public void putSubscriptionAdminTest() throws Exception { context.turnOffAuthorisationSystem(); List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); - subscriptionParameter.setName("Frequency"); - subscriptionParameter.setValue("Daily"); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("D"); subscriptionParameterList.add(subscriptionParameter); - Subscription subscription = SubscribeBuilder - .subscribeBuilder(context, "Test", publicItem, eperson, subscriptionParameterList).build(); - String token = getAuthToken(admin.getEmail(), password); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, + "content", collection, eperson, subscriptionParameterList).build(); context.restoreAuthSystemState(); - getClient(token).perform(delete("/api/core/subscriptions/" + subscription.getID())) - .andExpect(status().isNoContent()); + + 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"))); } - // PATCH @Test - public void patchReplaceSubscriptionParameterAsAdmin() throws Exception { + public void linkedEpersonOfSubscriptionAdminTest() throws Exception { context.turnOffAuthorisationSystem(); List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); - subscriptionParameter.setName("TestName"); - subscriptionParameter.setValue("TestValue"); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("M"); subscriptionParameterList.add(subscriptionParameter); - Subscription subscription = SubscribeBuilder - .subscribeBuilder(context, "Test", publicItem, eperson, subscriptionParameterList).build(); - String token = getAuthToken(admin.getEmail(), password); - List ops = new ArrayList(); - Map value = new HashMap<>(); - value.put("name", "frequency"); - value.put("value", "monthly"); - ReplaceOperation replaceOperation = new ReplaceOperation( - "/subscriptionsParameter/" + subscription.getSubscriptionParameterList().get(0).getId(), value); - ops.add(replaceOperation); - String patchBody = getPatchContent(ops); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, + "content", collection, eperson, subscriptionParameterList).build(); context.restoreAuthSystemState(); - getClient(token) - .perform(patch("/api/core/subscriptions/" + subscription.getID()).contentType(contentType) - .content(patchBody)) - .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("Test"))) - .andExpect(jsonPath("$.subscriptionParameterList[0].name", is("frequency"))) - .andExpect(jsonPath("$.subscriptionParameterList[0].value", is("monthly"))) - .andExpect(jsonPath("$._links.self.href", Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) - .andExpect(jsonPath("$._links.dSpaceObject.href", - Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) - .andExpect(jsonPath("$._links.dSpaceObject.href", Matchers.endsWith("/dSpaceObject"))) - .andExpect( - jsonPath("$._links.ePerson.href", Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) - .andExpect(jsonPath("$._links.ePerson.href", Matchers.endsWith("/ePerson"))); + + 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 patchSubscriptionParameterNotAsAdminNotAsSubscriber() throws Exception { + public void linkedEpersonOfSubscriptionTest() throws Exception { context.turnOffAuthorisationSystem(); List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); - subscriptionParameter.setName("TestName"); - subscriptionParameter.setValue("TestValue"); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("M"); subscriptionParameterList.add(subscriptionParameter); - Subscription subscription = SubscribeBuilder - .subscribeBuilder(context, "Test", publicItem, eperson, subscriptionParameterList).build(); - List ops = new ArrayList(); - Map value = new HashMap<>(); - value.put("name", "frequency"); - value.put("value", "monthly"); - ReplaceOperation replaceOperation = new ReplaceOperation( - "/subscriptionsParameter/" + subscription.getSubscriptionParameterList().get(0).getId(), value); - ops.add(replaceOperation); - String patchBody = getPatchContent(ops); - EPerson epersonIT = EPersonBuilder.createEPerson(context).withEmail("epersonIT@example.com") - .withPassword(password).withLanguage("al").build(); - String epersonITtoken = getAuthToken(epersonIT.getEmail(), password); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, + "content", collection, eperson, subscriptionParameterList).build(); context.restoreAuthSystemState(); - getClient(epersonITtoken) - .perform(patch("/api/core/subscriptions/" + subscription.getID()).contentType(contentType) - .content(patchBody)) - // The status has to be 403 Forbidden - .andExpect(status().isForbidden()); + + 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 patchAddSubscriptionParameter() throws Exception { + public void linkedEpersonOfSubscriptionUnauthorizedTest() throws Exception { context.turnOffAuthorisationSystem(); List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); - subscriptionParameter.setName("TestName"); - subscriptionParameter.setValue("TestValue"); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("M"); subscriptionParameterList.add(subscriptionParameter); - Subscription subscription = SubscribeBuilder - .subscribeBuilder(context, "Test", publicItem, eperson, subscriptionParameterList).build(); - String token = getAuthToken(admin.getEmail(), password); - List ops = new ArrayList(); - Map value = new HashMap<>(); - value.put("name", "frequency"); - value.put("value", "monthly"); - AddOperation addOperation = new AddOperation( - "/subscriptionsParameter/" + subscription.getSubscriptionParameterList().get(0).getId(), value); - ops.add(addOperation); - String patchBody = getPatchContent(ops); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, + "content", collection, eperson, subscriptionParameterList).build(); context.restoreAuthSystemState(); - getClient(token) - .perform(patch("/api/core/subscriptions/" + subscription.getID()).contentType(contentType) - .content(patchBody)) - // The status has to be 403 Not Authorized - .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("Test"))) - .andExpect(jsonPath("$.subscriptionParameterList[0].name", is("TestName"))) - .andExpect(jsonPath("$.subscriptionParameterList[0].value", is("TestValue"))) - .andExpect(jsonPath("$.subscriptionParameterList[1].name", is("frequency"))) - .andExpect(jsonPath("$.subscriptionParameterList[1].value", is("monthly"))) - .andExpect(jsonPath("$._links.self.href", Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) - .andExpect(jsonPath("$._links.dSpaceObject.href", - Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) - .andExpect(jsonPath("$._links.dSpaceObject.href", Matchers.endsWith("/dSpaceObject"))) - .andExpect( - jsonPath("$._links.ePerson.href", Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) - .andExpect(jsonPath("$._links.ePerson.href", Matchers.endsWith("/ePerson"))); - } - - @Test - public void patchRemoveSubscriptionParameter() throws Exception { - context.turnOffAuthorisationSystem(); - List subscriptionParameterList = new ArrayList<>(); - SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); - subscriptionParameter.setName("TestName"); - subscriptionParameter.setValue("TestValue"); - subscriptionParameterList.add(subscriptionParameter); - Subscription subscription = SubscribeBuilder - .subscribeBuilder(context, "Test", publicItem, eperson, subscriptionParameterList).build(); - String token = getAuthToken(admin.getEmail(), password); - List ops = new ArrayList(); - Map value = new HashMap<>(); - value.put("name", "frequency"); - value.put("value", "monthly"); - RemoveOperation removeOperation = new RemoveOperation( - "/subscriptionsParameter/" + subscription.getSubscriptionParameterList().get(0).getId()); - ops.add(removeOperation); - String patchBody = getPatchContent(ops); - context.restoreAuthSystemState(); - getClient(token) - .perform(patch("/api/core/subscriptions/" + subscription.getID()).contentType(contentType) - .content(patchBody)) - // The status has to be 403 Not Authorized - .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("Test"))) - .andExpect(jsonPath("$.subscriptionParameterList", Matchers.hasSize(0))) - .andExpect(jsonPath("$._links.self.href", Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))); + + getClient().perform(get("/api/core/subscriptions/" + subscription.getID() + "/eperson")) + .andExpect(status().isUnauthorized()); } - 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()); - } + + @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()); } - private void cleanUpPermissions(List resourcePolicies) { - try { - for (ResourcePolicy resourcePolicy : resourcePolicies) { - ResourcePolicyBuilder.delete(resourcePolicy.getID()); - } + @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()); + } - } catch (SQLException | SearchServiceException | IOException sqlException) { - log.error(sqlException.getMessage()); - } + @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/SubscriptionsSendEmailNotificationIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionsSendEmailNotificationIT.java deleted file mode 100644 index 68e2ae7ba632..000000000000 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionsSendEmailNotificationIT.java +++ /dev/null @@ -1,665 +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.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.verifyZeroInteractions; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Date; -import java.util.GregorianCalendar; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TimeZone; - -import org.apache.commons.lang3.time.DateUtils; -import org.dspace.app.metrics.CrisMetrics; -import org.dspace.app.metrics.service.CrisMetricsService; -import org.dspace.app.rest.test.AbstractControllerIntegrationTest; -import org.dspace.app.scripts.handler.impl.TestDSpaceRunnableHandler; -import org.dspace.builder.CollectionBuilder; -import org.dspace.builder.CommunityBuilder; -import org.dspace.builder.CrisMetricsBuilder; -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.content.service.CollectionService; -import org.dspace.content.service.CommunityService; -import org.dspace.content.service.ItemService; -import org.dspace.discovery.IndexableObject; -import org.dspace.discovery.SearchService; -import org.dspace.discovery.configuration.DiscoveryConfigurationService; -import org.dspace.discovery.indexobject.IndexableItem; -import org.dspace.eperson.EPerson; -import org.dspace.eperson.Subscription; -import org.dspace.eperson.SubscriptionParameter; -import org.dspace.eperson.service.SubscribeService; -import org.dspace.event.factory.EventServiceFactory; -import org.dspace.event.service.EventService; -import org.dspace.services.ConfigurationService; -import org.dspace.services.factory.DSpaceServicesFactory; -import org.dspace.subscriptions.ContentGenerator; -import org.dspace.subscriptions.StatisticsGenerator; -import org.dspace.subscriptions.SubscriptionEmailNotification; -import org.dspace.subscriptions.SubscriptionEmailNotificationService; -import org.dspace.subscriptions.dSpaceObjectsUpdates.CollectionsUpdates; -import org.dspace.subscriptions.dSpaceObjectsUpdates.CommunityUpdates; -import org.dspace.subscriptions.dSpaceObjectsUpdates.ItemsUpdates; -import org.dspace.subscriptions.service.DSpaceObjectUpdates; -import org.dspace.subscriptions.service.SubscriptionGenerator; -import org.hamcrest.Matchers; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; -import org.mockito.ArgumentCaptor; -import org.mockito.Captor; -import org.springframework.beans.factory.annotation.Autowired; - -/** - * Test for script of sending subscriptions for items/collections/communities - * @author Alba Aliu (alba.aliu at atis.al) - */ - -public class SubscriptionsSendEmailNotificationIT extends AbstractControllerIntegrationTest { - - private static String[] consumers; - - private SubscriptionEmailNotificationService subscriptionEmailNotificationService; - @Autowired - ConfigurationService configurationService; - @Autowired - SubscribeService subscribeService; - @Autowired - CrisMetricsService crisMetricsService; - @Autowired - CollectionService collectionService; - @Autowired - CommunityService communityService; - @Autowired - ItemService itemService; - @Autowired - SearchService searchService; - @Autowired - DiscoveryConfigurationService discoveryConfigurationService; - StatisticsGenerator statisticsGenerator = mock(StatisticsGenerator.class); - ContentGenerator contentGenerator = mock(ContentGenerator.class); - ItemsUpdates itemsUpdates; - CollectionsUpdates collectionsUpdates; - CommunityUpdates communityUpdates; - SubscriptionEmailNotification subscriptionEmailNotification; - - @Captor - private ArgumentCaptor personArgumentCaptor; - - /** - * This method will be run before the first test as per @BeforeClass. It will - * configure the event.dispatcher.default.consumers property to add the - * CrisConsumer. - */ - @BeforeClass - public static void initConsumers() { - ConfigurationService configService = DSpaceServicesFactory.getInstance().getConfigurationService(); - consumers = configService.getArrayProperty("event.dispatcher.default.consumers"); - Set consumersSet = new HashSet(Arrays.asList(consumers)); - consumersSet.remove("itemenhancer"); - configService.setProperty("event.dispatcher.default.consumers", consumersSet.toArray()); - EventService eventService = EventServiceFactory.getInstance().getEventService(); - eventService.reloadConfiguration(); - } - - /** - * Reset the event.dispatcher.default.consumers property value. - */ - @AfterClass - public static void resetDefaultConsumers() { - ConfigurationService configService = DSpaceServicesFactory.getInstance().getConfigurationService(); - configService.setProperty("event.dispatcher.default.consumers", consumers); - EventService eventService = EventServiceFactory.getInstance().getEventService(); - eventService.reloadConfiguration(); - } - - @Before - @Override - public void setUp() throws Exception { - super.setUp(); - itemsUpdates = new ItemsUpdates(collectionService, communityService, - itemService, discoveryConfigurationService, searchService); - collectionsUpdates = new CollectionsUpdates(searchService); - communityUpdates = new CommunityUpdates(searchService); - itemsUpdates = new ItemsUpdates(collectionService, communityService, itemService, - discoveryConfigurationService, searchService); - Map generatorMap = new HashMap<>(); - generatorMap.put("content", contentGenerator); - generatorMap.put("statistics", statisticsGenerator); - Map contentUpdateMap = new HashMap<>(); - contentUpdateMap.put("community", communityUpdates); - contentUpdateMap.put("collection", collectionsUpdates); - contentUpdateMap.put("item", itemsUpdates); - // Explicitly use solr commit in SolrLoggerServiceImpl#postView - configurationService.setProperty("solr-statistics.autoCommit", false); - this.subscriptionEmailNotificationService = new SubscriptionEmailNotificationService( - crisMetricsService, subscribeService, generatorMap, contentUpdateMap); - subscriptionEmailNotification = new SubscriptionEmailNotification(); - } - - - //verify that method that invokes mail send is called correctly for type content and frequence weekly - @Test - public void sendSubscriptionMailTypeContentWeekly() throws Exception { - context.turnOffAuthorisationSystem(); - Community community = CommunityBuilder.createCommunity(context).build(); - parentCommunity = CommunityBuilder.createSubCommunity(context, community).build(); - Collection col1 = CollectionBuilder.createCollection(context, community).build(); - Collection col2 = CollectionBuilder.createCollection(context, community).build(); - Item orgUnit = ItemBuilder.createItem(context, col1) - .withEntityType("OrgUnit").withFullName("4Science") - .withTitle("4Science").buildWithLastModifiedDate( generateTimeOnBasedFrequency("W")); - //person item for relation inverse - //it has as affiliation 4Science - Item person = ItemBuilder.createItem(context, col2) - .withEntityType("Person").withFullName("testPerson") - .withTitle("testPerson") - .withAffiliation(orgUnit.getName(), - orgUnit.getID().toString()) - .buildWithLastModifiedDate(generateTimeOnBasedFrequency("W")); - // subscription with dso of type item - Subscription subscription = SubscribeBuilder - .subscribeBuilder(context, "content", orgUnit, eperson, - generateSubscriptionParameterListFrequency("W")).build(); - Subscription subscriptionComm = SubscribeBuilder - .subscribeBuilder(context, "content", community, - eperson, generateSubscriptionParameterListFrequency("D")).build(); - Subscription subscriptionColl = SubscribeBuilder - .subscribeBuilder(context, "content", col1, eperson, - generateSubscriptionParameterListFrequency("W")).build(); - context.restoreAuthSystemState(); - String[] args = new String[]{"subscription-send", "-t", "content", "-f", "W"}; - TestDSpaceRunnableHandler handler = new TestDSpaceRunnableHandler(); - subscriptionEmailNotification.initialize(args, handler, eperson); - // attach service class - subscriptionEmailNotification.setSubscriptionEmailNotificationService(subscriptionEmailNotificationService); - //run script - subscriptionEmailNotification.run(); - List items = new ArrayList<>(); - IndexableItem indexableObject = new IndexableItem(person); - items.add(indexableObject); - - List collections = new ArrayList<>(); - IndexableItem indexableObject1 = new IndexableItem(orgUnit); - collections.add(indexableObject1); - // verify that method in invoked correctly - verify(contentGenerator).notifyForSubscriptions( - subscriptionEmailNotification.getContext(), eperson, new ArrayList<>(), - collections, items); - verifyNoMoreInteractions(contentGenerator); - } - - - //verify that method that invokes mail send is called correctly for type content and frequence weekly - @Test - public void sendSubscriptionMailTypeContentNoSubscription() throws Exception { - context.turnOffAuthorisationSystem(); - Community community = CommunityBuilder.createCommunity(context).build(); - parentCommunity = CommunityBuilder.createSubCommunity(context, community).build(); - Collection col1 = CollectionBuilder.createCollection(context, community).build(); - Collection col2 = CollectionBuilder.createCollection(context, community).build(); - Item orgUnit = ItemBuilder.createItem(context, col1) - .withEntityType("OrgUnit").withFullName("4Science") - .withTitle("4Science").buildWithLastModifiedDate(generateTimeOnBasedFrequency("W")); - //person item for relation inverse - //it has as affiliation 4Science - Item person = ItemBuilder.createItem(context, col2) - .withEntityType("Person").withFullName("testPerson") - .withTitle("testPerson") - .withAffiliation(orgUnit.getName(), - orgUnit.getID().toString()) - .buildWithLastModifiedDate(generateTimeOnBasedFrequency("W")); - // subscription with dso of type item - Subscription subscription = SubscribeBuilder - .subscribeBuilder(context, "statistics", - orgUnit, eperson, generateSubscriptionParameterListFrequency("W")).build(); - Subscription subscriptionComm = SubscribeBuilder - .subscribeBuilder(context, "statistics", community, - eperson, generateSubscriptionParameterListFrequency("D")).build(); - Subscription subscriptionColl = SubscribeBuilder - .subscribeBuilder(context, "statistics", col1, eperson, - generateSubscriptionParameterListFrequency("W")).build(); - context.restoreAuthSystemState(); - String[] args = new String[]{"subscription-send", "-t", "content", "-f", "W"}; - TestDSpaceRunnableHandler handler = new TestDSpaceRunnableHandler(); - subscriptionEmailNotification.initialize(args, handler, eperson); - // attach service class - subscriptionEmailNotification.setSubscriptionEmailNotificationService(subscriptionEmailNotificationService); - //run script - subscriptionEmailNotification.run(); - verifyZeroInteractions(contentGenerator); - verifyZeroInteractions(statisticsGenerator); - } - - //verify that method that invokes mail send is called correctly for type - // content and frequence weekly for two different users - @Test - public void sendSubscriptionMailTypeContentWeeklyForTwoPersons() throws Exception { - context.turnOffAuthorisationSystem(); - Community community = CommunityBuilder.createCommunity(context).build(); - parentCommunity = CommunityBuilder.createSubCommunity(context, community).build(); - Collection col1 = CollectionBuilder.createCollection(context, community).build(); - Collection col2 = CollectionBuilder.createCollection(context, community).build(); - Item orgUnit = ItemBuilder.createItem(context, col1) - .withEntityType("OrgUnit").withFullName("4Science") - .withTitle("4Science").buildWithLastModifiedDate(generateTimeOnBasedFrequency("W")); - //person item for relation inverse - //it has as affiliation 4Science - Item person = ItemBuilder.createItem(context, col2) - .withEntityType("Person").withFullName("testPerson") - .withTitle("testPerson") - .withAffiliation(orgUnit.getName(), - orgUnit.getID().toString()).buildWithLastModifiedDate( - generateTimeOnBasedFrequency("W")); - // subscription with dso of type item - Subscription subscription = SubscribeBuilder - .subscribeBuilder(context, "content", orgUnit, - admin, generateSubscriptionParameterListFrequency("W")).build(); - Subscription subscriptionComm = SubscribeBuilder - .subscribeBuilder(context, "content", community, eperson, - generateSubscriptionParameterListFrequency("D")).build(); - Subscription subscriptionColl = SubscribeBuilder - .subscribeBuilder(context, "content", orgUnit, eperson, - generateSubscriptionParameterListFrequency("W")).build(); - context.restoreAuthSystemState(); - String[] args = new String[]{"subscription-send", "-t", "content", "-f", "W"}; - TestDSpaceRunnableHandler handler = new TestDSpaceRunnableHandler(); - subscriptionEmailNotification.initialize(args, handler, eperson); - // attach service class - subscriptionEmailNotification.setSubscriptionEmailNotificationService(subscriptionEmailNotificationService); - //run script - subscriptionEmailNotification.run(); - List items = new ArrayList<>(); - IndexableItem indexableObject = new IndexableItem(person); - items.add(indexableObject); - // verify that method is invoked twice for different users - verify(contentGenerator, - times(2)) - .notifyForSubscriptions( - eq(subscriptionEmailNotification.getContext()), - personArgumentCaptor.capture(), eq(new ArrayList<>()), - eq(new ArrayList<>()), eq(items)); - List allValues = personArgumentCaptor.getAllValues(); - List personList = new ArrayList<>(); - personList.add(admin); - personList.add(eperson); - Matchers.containsInAnyOrder(allValues, personList); - } - - - //verify that method that invokes mail send is called correctly for type content and frequence weekly - @Test - public void sendSubscriptionMailTypeContentMonthly() throws Exception { - context.turnOffAuthorisationSystem(); - Community community = CommunityBuilder.createCommunity(context).build(); - parentCommunity = CommunityBuilder.createSubCommunity(context, community).build(); - Collection col1 = CollectionBuilder.createCollection(context, community).build(); - Collection col2 = CollectionBuilder.createCollection(context, community).build(); - Collection col3 = CollectionBuilder.createCollection(context, community).build(); - Item orgUnit = ItemBuilder.createItem(context, col1) - .withEntityType("OrgUnit").withFullName("orgUnit") - .withTitle("orgUnit").buildWithLastModifiedDate(generateTimeOnBasedFrequency("M")); - //person item for relation inverse - //it has as affiliation 4Science - Item person = ItemBuilder.createItem(context, col3) - .withEntityType("Person").withFullName("personTest") - .withTitle("personTest") - .withAffiliation(orgUnit.getName(), - orgUnit.getID().toString()) - .buildWithLastModifiedDate(generateTimeOnBasedFrequency("M")); - - Item itemOfComm = ItemBuilder.createItem(context, col2) - .withEntityType("Equipment").withFullName("testEquipment") - .withTitle("testEquipment") - .buildWithLastModifiedDate(generateTimeOnBasedFrequency("M")); - // subscription with dso of type item - Subscription subscription = SubscribeBuilder - .subscribeBuilder(context, "content", orgUnit, eperson, - generateSubscriptionParameterListFrequency("M")).build(); - Subscription subscriptionComm = SubscribeBuilder - .subscribeBuilder(context, "content", - community, eperson, generateSubscriptionParameterListFrequency("D")).build(); - Subscription subscriptionColl = SubscribeBuilder - .subscribeBuilder(context, "content", col1, eperson, - generateSubscriptionParameterListFrequency("M")).build(); - context.restoreAuthSystemState(); - String[] args = new String[]{"subscription-send", "-t", "content", "-f", "M"}; - TestDSpaceRunnableHandler handler = new TestDSpaceRunnableHandler(); - subscriptionEmailNotification.initialize(args, handler, eperson); - // attach service class - subscriptionEmailNotification.setSubscriptionEmailNotificationService(subscriptionEmailNotificationService); - //run script - subscriptionEmailNotification.run(); - List items = new ArrayList<>(); - IndexableItem indexableObject = new IndexableItem(person); - items.add(indexableObject); - - List collections = new ArrayList<>(); - IndexableItem indexableObject1 = new IndexableItem(orgUnit); - collections.add(indexableObject1); - - List communities = new ArrayList<>(); - - // verify that method in invoked correctly - verify(contentGenerator) - .notifyForSubscriptions(subscriptionEmailNotification.getContext(), - eperson, communities, collections, items); - verifyNoMoreInteractions(contentGenerator); - } - - - //verify that method that invokes mail send is called correctly for type content and frequence weekly - @Test - public void sendSubscriptionMailTypeContentDaily() throws Exception { - context.turnOffAuthorisationSystem(); - Community community = CommunityBuilder.createCommunity(context).build(); - parentCommunity = CommunityBuilder.createSubCommunity(context, community).build(); - Collection col1 = CollectionBuilder.createCollection(context, community).build(); - Collection col2 = CollectionBuilder.createCollection(context, community).build(); - Item orgUnit = ItemBuilder.createItem(context, col1) - .withEntityType("OrgUnit").withFullName("orgUnit") - .withTitle("orgUnit").buildWithLastModifiedDate(generateTimeOnBasedFrequency("D")); - //person item for relation inverse - //it has as affiliation 4Science - Item person = ItemBuilder.createItem(context, col2) - .withEntityType("Person").withFullName("person") - .withTitle("person") - .withAffiliation(orgUnit.getName(), - orgUnit.getID().toString()) - .buildWithLastModifiedDate(generateTimeOnBasedFrequency("D")); - // subscription with dso of type item - Subscription subscription = SubscribeBuilder - .subscribeBuilder(context, "content", orgUnit, - eperson, generateSubscriptionParameterListFrequency("D")).build(); - Subscription subscriptionComm = SubscribeBuilder - .subscribeBuilder(context, "content", community, - eperson, generateSubscriptionParameterListFrequency("W")).build(); - Subscription subscriptionColl = SubscribeBuilder - .subscribeBuilder(context, "content", col1, eperson, - generateSubscriptionParameterListFrequency("D")).build(); - context.restoreAuthSystemState(); - String[] args = new String[]{"subscription-send", "-t", "content", "-f", "D"}; - TestDSpaceRunnableHandler handler = new TestDSpaceRunnableHandler(); - subscriptionEmailNotification.initialize(args, handler, eperson); - // attach service class - subscriptionEmailNotification.setSubscriptionEmailNotificationService(subscriptionEmailNotificationService); - //run script - subscriptionEmailNotification.run(); - List items = new ArrayList<>(); - IndexableItem indexableObject = new IndexableItem(person); - items.add(indexableObject); - List collections = new ArrayList<>(); - IndexableItem indexableObject1 = new IndexableItem(orgUnit); - collections.add(indexableObject1); - // verify that method in invoked correctly - verify(contentGenerator) - .notifyForSubscriptions(subscriptionEmailNotification.getContext(), - eperson, new ArrayList<>(), collections, items); - verifyNoMoreInteractions(contentGenerator); - - } - - //verify that method that invokes mail send is called correctly for type statistics and frequence monthly - @Test - public void sendSubscriptionMailTypeStatisticsWeekly() throws Exception { - context.turnOffAuthorisationSystem(); - Community community = CommunityBuilder.createCommunity(context).build(); - parentCommunity = CommunityBuilder.createSubCommunity(context, community).build(); - Collection col1 = CollectionBuilder.createCollection(context, community).build(); - Item orgUnit = ItemBuilder.createItem(context, col1) - .withEntityType("OrgUnit").withFullName("4Science") - .withTitle("4Science").buildWithLastModifiedDate(generateTimeOnBasedFrequency("D")); - //person item for relation inverse - //it has as affiliation 4Science - Item person = ItemBuilder.createItem(context, col1) - .withEntityType("Person").withFullName("testPerson") - .withTitle("testPerson") - .withAffiliation(orgUnit.getName(), - orgUnit.getID().toString()) - .buildWithLastModifiedDate(generateTimeOnBasedFrequency("D")); - // subscription with dso of type item - Subscription subscription = SubscribeBuilder - .subscribeBuilder(context, "statistics", - orgUnit, eperson, generateSubscriptionParameterListFrequency("D")).build(); - Subscription subscriptionComm = SubscribeBuilder - .subscribeBuilder(context, "statistics", community, eperson, - generateSubscriptionParameterListFrequency("D")).build(); - Subscription subscriptionColl = SubscribeBuilder - .subscribeBuilder(context, "statistics", col1, eperson, - generateSubscriptionParameterListFrequency("D")).build(); - //create cris metrics related with dso - CrisMetrics crisMetricsComm = CrisMetricsBuilder.createCrisMetrics(context, community) - .withMetricType("view") - .withMetricCount(2) - .withAcquisitionDate( - DateUtils.addDays(new Date(), -7)) - .isLast(true).build(); - CrisMetrics crisMetricsColl = CrisMetricsBuilder.createCrisMetrics(context, col1) - .withMetricType("view") - .withMetricCount(2) - .withAcquisitionDate( - DateUtils.addDays(new Date(), -7)) - .isLast(true).build(); - CrisMetrics crisMetricsItem = CrisMetricsBuilder.createCrisMetrics(context, orgUnit) - .withMetricType("view") - .withMetricCount(2) - .withAcquisitionDate( - DateUtils.addDays(new Date(), -7)) - .isLast(true).build(); - // THIS MUST NOT BE SHOWN IN EMAIL - CrisMetrics crisMetricsTest = CrisMetricsBuilder.createCrisMetrics(context, person) - .withMetricType("view") - .withMetricCount(2) - .withAcquisitionDate( - DateUtils.addDays(new Date(), -7)) - .isLast(true).build(); - context.restoreAuthSystemState(); - String[] args = new String[]{"subscription-send", "-t", "statistics", "-f", "D"}; - TestDSpaceRunnableHandler handler = new TestDSpaceRunnableHandler(); - subscriptionEmailNotification.initialize(args, handler, eperson); - // attach service class - subscriptionEmailNotification.setSubscriptionEmailNotificationService(subscriptionEmailNotificationService); - //run script - subscriptionEmailNotification.run(); - List crisMetrics = new ArrayList<>(); - crisMetrics.add(crisMetricsItem); - crisMetrics.add(crisMetricsComm); - crisMetrics.add(crisMetricsColl); - // verify that method in invoked correctly - verify(statisticsGenerator) - .notifyForSubscriptions(subscriptionEmailNotification.getContext(), - eperson, crisMetrics, null, null); - } - - //verify that method that invokes mail send is called correctly for type statistics and frequence monthly - @Test - public void sendSubscriptionMailTypeStatisticsMonthly() throws Exception { - context.turnOffAuthorisationSystem(); - Community community = CommunityBuilder.createCommunity(context).build(); - parentCommunity = CommunityBuilder.createSubCommunity(context, community).build(); - Collection col1 = CollectionBuilder.createCollection(context, community).build(); - Item orgUnit = ItemBuilder.createItem(context, col1) - .withEntityType("OrgUnit").withFullName("4Science") - .withTitle("4Science").buildWithLastModifiedDate(generateTimeOnBasedFrequency("M")); - //person item for relation inverse - //it has as affiliation 4Science - Item person = ItemBuilder.createItem(context, col1) - .withEntityType("Person").withFullName("testPerson") - .withTitle("testPerson") - .withAffiliation(orgUnit.getName(), - orgUnit.getID().toString()).buildWithLastModifiedDate(generateTimeOnBasedFrequency("M")); - // subscription with dso of type item - Subscription subscription = SubscribeBuilder - .subscribeBuilder(context, "statistics", - orgUnit, eperson, generateSubscriptionParameterListFrequency("M")).build(); - Subscription subscriptionComm = SubscribeBuilder - .subscribeBuilder(context, "statistics", community, - eperson, generateSubscriptionParameterListFrequency("D")).build(); - Subscription subscriptionColl = SubscribeBuilder - .subscribeBuilder(context, "statistics", col1, eperson, - generateSubscriptionParameterListFrequency("D")).build(); - //create cris metrics related with dso - CrisMetrics crisMetricsComm = CrisMetricsBuilder.createCrisMetrics(context, community) - .withMetricType("view") - .withMetricCount(2) - .withAcquisitionDate( - DateUtils.addDays(new Date(), -7)) - .isLast(true).build(); - CrisMetrics crisMetricsColl = CrisMetricsBuilder.createCrisMetrics(context, col1) - .withMetricType("view") - .withMetricCount(2) - .withAcquisitionDate( - DateUtils.addDays(new Date(), -7)) - .isLast(true).build(); - CrisMetrics crisMetricsItem = CrisMetricsBuilder.createCrisMetrics(context, orgUnit) - .withMetricType("view") - .withMetricCount(2) - .withAcquisitionDate( - DateUtils.addDays(new Date(), -7)) - .isLast(true).build(); - // THIS MUST NOT BE SHOWN IN EMAIL - CrisMetrics crisMetricsTest = CrisMetricsBuilder.createCrisMetrics(context, person) - .withMetricType("view") - .withMetricCount(2) - .withAcquisitionDate( - DateUtils.addDays(new Date(), -7)) - .isLast(true).build(); - context.restoreAuthSystemState(); - String[] args = new String[]{"subscription-send", "-t", "statistics", "-f", "M"}; - TestDSpaceRunnableHandler handler = new TestDSpaceRunnableHandler(); - subscriptionEmailNotification.initialize(args, handler, eperson); - // attach service class - subscriptionEmailNotification.setSubscriptionEmailNotificationService(subscriptionEmailNotificationService); - //run script - subscriptionEmailNotification.run(); - List crisMetrics = new ArrayList<>(); - crisMetrics.add(crisMetricsItem); - // verify that method in invoked correctly - verify(statisticsGenerator) - .notifyForSubscriptions(subscriptionEmailNotification.getContext(), eperson, crisMetrics, null, null); - } - - //verify that method that invokes mail send is called correctly for type statistics and frequence monthly - @Test - public void sendSubscriptionMailTypeStatisticsDaily() throws Exception { - context.turnOffAuthorisationSystem(); - Community community = CommunityBuilder.createCommunity(context).build(); - parentCommunity = CommunityBuilder.createSubCommunity(context, community).build(); - Collection col1 = CollectionBuilder.createCollection(context, community).build(); - Item orgUnit = ItemBuilder.createItem(context, col1) - .withEntityType("OrgUnit").withFullName("4Science") - .withTitle("4Science").buildWithLastModifiedDate(generateTimeOnBasedFrequency("D")); - //person item for relation inverse - //it has as affiliation 4Science - Item person = ItemBuilder.createItem(context, col1) - .withEntityType("Person").withFullName("testPerson") - .withTitle("testPerson") - .withAffiliation(orgUnit.getName(), - orgUnit.getID().toString()).buildWithLastModifiedDate(generateTimeOnBasedFrequency("D")); - // subscription with dso of type item - Subscription subscription = SubscribeBuilder - .subscribeBuilder(context, "statistics", orgUnit, - eperson, generateSubscriptionParameterListFrequency("D")).build(); - Subscription subscriptionComm = SubscribeBuilder - .subscribeBuilder(context, "statistics", community, - eperson, generateSubscriptionParameterListFrequency("D")).build(); - Subscription subscriptionColl = SubscribeBuilder - .subscribeBuilder(context, "statistics", col1, eperson, - generateSubscriptionParameterListFrequency("D")).build(); - //create cris metrics related with dso - CrisMetrics crisMetricsComm = CrisMetricsBuilder.createCrisMetrics(context, community) - .withMetricType("view") - .withMetricCount(2) - .withAcquisitionDate( - DateUtils.addDays(new Date(), -7)) - .isLast(true).build(); - CrisMetrics crisMetricsColl = CrisMetricsBuilder.createCrisMetrics(context, col1) - .withMetricType("view") - .withMetricCount(2) - .withAcquisitionDate( - DateUtils.addDays(new Date(), -7)) - .isLast(true).build(); - CrisMetrics crisMetricsItem = CrisMetricsBuilder.createCrisMetrics(context, orgUnit) - .withMetricType("view") - .withMetricCount(2) - .withAcquisitionDate( - DateUtils.addDays(new Date(), -7)) - .isLast(true).build(); - // THIS MUST NOT BE SHOWN IN EMAIL - CrisMetrics crisMetricsTest = CrisMetricsBuilder.createCrisMetrics(context, person) - .withMetricType("view") - .withMetricCount(2) - .withAcquisitionDate( - DateUtils.addDays(new Date(), -7)) - .isLast(true).build(); - context.restoreAuthSystemState(); - String[] args = new String[]{"subscription-send", "-t", "statistics", "-f", "D"}; - TestDSpaceRunnableHandler handler = new TestDSpaceRunnableHandler(); - subscriptionEmailNotification.initialize(args, handler, eperson); - // attach service class - subscriptionEmailNotification.setSubscriptionEmailNotificationService(subscriptionEmailNotificationService); - //run script - subscriptionEmailNotification.run(); - List crisMetrics = new ArrayList<>(); - crisMetrics.add(crisMetricsItem); - crisMetrics.add(crisMetricsComm); - crisMetrics.add(crisMetricsColl); - // verify that method in invoked correctly - verify(statisticsGenerator) - .notifyForSubscriptions(subscriptionEmailNotification.getContext(), eperson, crisMetrics, null, null); - } - - private List generateSubscriptionParameterListFrequency(String frequencyValue) { - List subscriptionParameterList = new ArrayList<>(); - SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); - subscriptionParameter.setName("frequency"); - subscriptionParameter.setValue(frequencyValue); - subscriptionParameterList.add(subscriptionParameter); - return subscriptionParameterList; - } - - private Date generateTimeOnBasedFrequency(String frequency) { - GregorianCalendar localCalendar = new GregorianCalendar(); - localCalendar.setTime(new Date()); - TimeZone utcZone = TimeZone.getTimeZone("UTC"); - localCalendar.setTimeZone(utcZone); - // Now set the UTC equivalent. - switch (frequency) { - case "D": - localCalendar.add(GregorianCalendar.DAY_OF_YEAR, -1); - break; - case "M": - localCalendar.add(GregorianCalendar.MONTH, -1); - break; - case "W": - localCalendar.add(GregorianCalendar.WEEK_OF_MONTH, -1); - break; - default: - return null; - } - return localCalendar.getTime(); - } - -} - 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 000000000000..fbc707d6a491 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SupervisionOrderRestRepositoryIT.java @@ -0,0 +1,1503 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the 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") + .withSubmissionDefinition("traditional") + .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") + .withSubmissionDefinition("traditional") + .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 000000000000..522c47670429 --- /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 17bc8edf46da..a9b5c6a582b6 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.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.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 void claimedtaskSerchMethodWithPluralModelTest() throws Exception { @Test public void addReviewerToRunningWorkflowTest() throws Exception { - - GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); - context.turnOffAuthorisationSystem(); EPerson reviewer1 = EPersonBuilder.createEPerson(context) @@ -4329,4 +4330,384 @@ public void addReviewerToRunningWorkflowTest() throws Exception { .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 0c20448775f6..85a6d6a42ac7 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 void createVersionWithVersioningDisabledTest() throws Exception { .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 4962e1aef282..dad6cd8b4605 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 void findOneTest() throws Exception { .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 void findOneTest() throws Exception { } @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 void srscSearchTopTest() throws Exception { .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 void srscSearchTopTest() throws Exception { .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 void srscSearchFirstLevel_MATHEMATICS_Test() throws Exception { .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 void srscSearchTopPaginationTest() throws Exception { .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 void srscSearchTopPaginationTest() throws Exception { .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 void srscSearchTopPaginationTest() throws Exception { .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 void searchTopBadRequestTest() throws Exception { } @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 void srscSearchByParentFirstLevelPaginationTest() throws Exception { .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 void srscSearchByParentFirstLevelPaginationTest() throws Exception { .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 void srscSearchByParentSecondLevel_Applied_mathematics_Test() throws Exce .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 void srscSearchByParentWrongIdTest() throws Exception { } @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 void findParentByChildTest() throws Exception { .andExpect(status().isOk()) .andExpect(jsonPath("$", is( VocabularyEntryDetailsMatcher.matchAuthorityEntry( - "srsc:SCB18", "MEDICINE","Research Subject Categories::MEDICINE") + "srsc:SCB18", "MEDICINE","MEDICINE") ))); } @@ -353,9 +353,9 @@ public void findParentByChildBadRequestTest() throws Exception { } @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 void srscProjectionTest() throws Exception { .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 void srscProjectionTest() throws Exception { .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 void srscProjectionTest() throws Exception { .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 13811c9b2370..1ff29a2ba228 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 @@ -169,8 +169,7 @@ public void findAllTest() throws Exception { @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) @@ -179,8 +178,7 @@ public void findOneSRSC_Test() throws Exception { @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) @@ -197,9 +195,9 @@ public void correctSrscQueryTest() throws Exception { .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))) @@ -568,7 +566,7 @@ public void controlledVocabularyWithHierarchyStoreSetTrueTest() throws Exception .andExpect(status().isOk()) .andExpect(jsonPath("$.metadata", Matchers.allOf( hasJsonPath("$['dc.title'][0].value", is("Test Item A")), - hasJsonPath("$['dc.type'][0].value", is("Resource Types::text::journal::editorial")), + hasJsonPath("$['dc.type'][0].value", is("text::journal::editorial")), hasJsonPath("$['dc.type'][0].authority", is(vocabularyName + ":c_b239")), hasJsonPath("$['dc.type'][0].confidence", is(600)) ))); @@ -658,7 +656,7 @@ public void controlledVocabularyWithHierarchySuggestSetTrueTest() throws Excepti hasJsonPath("$.authority", is(vocabularyName + ":c_b239")), // the display value without suggestions hasJsonPath("$.display", is("editorial")), - hasJsonPath("$.value", is("Resource Types::text::journal::editorial")) + hasJsonPath("$.value", is("text::journal::editorial")) ))); configurationService.setProperty("vocabulary.plugin." + vocabularyName + ".hierarchy.suggest", true); @@ -670,8 +668,8 @@ public void controlledVocabularyWithHierarchySuggestSetTrueTest() throws Excepti .andExpect(jsonPath("$._embedded.entries[0]", Matchers.allOf( hasJsonPath("$.authority", is(vocabularyName + ":c_b239")), // now the display value with suggestions - hasJsonPath("$.display", is("Resource Types::text::journal::editorial")), - hasJsonPath("$.value", is("Resource Types::text::journal::editorial")) + hasJsonPath("$.display", is("text::journal::editorial")), + hasJsonPath("$.value", is("text::journal::editorial")) ))); } 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 de687ebd9d18..ec963fd2f3bb 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.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 @@ 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 void getWorkflowActionByName_ExistentWithOptions_editaction() throws Exce .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 void getWorkflowActionByName_ExistentWithoutOptions_claimaction() throws .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 void getWorkflowActionByName_ExistentWithOptions_NoToken() throws Excepti //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 a6409dec072c..026a8d956544 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 @@ -2649,63 +2649,62 @@ public void whenWorkspaceitemBecomeWorkflowitemWithAccessConditionsTheItemMustBe } @Test - public void testAuthorFindOne() throws Exception { + public void testWorkflowWithHiddenSections() throws Exception { context.turnOffAuthorisationSystem(); - EPerson user = EPersonBuilder.createEPerson(context) - .withCanLogin(true) - .withEmail("user@test.com") - .withPassword(password) - .build(); - - EPerson anotherUser = EPersonBuilder.createEPerson(context) - .withCanLogin(true) - .withEmail("anotheruser@test.com") - .withPassword(password) - .build(); - parentCommunity = CommunityBuilder.createCommunity(context) .withName("Parent Community") .build(); Collection collection = CollectionBuilder.createCollection(context, parentCommunity) .withName("Collection 1") + .withSubmissionDefinition("test-hidden") .withWorkflowGroup(1, eperson) .build(); - Collection personCollection = CollectionBuilder.createCollection(context, parentCommunity) - .withName("Collection 2") - .withEntityType("Person") + WorkflowItem workflowItem = WorkflowItemBuilder.createWorkflowItem(context, collection) + .withTitle("Workflow Item") .build(); - Item userProfile = ItemBuilder.createItem(context, personCollection) - .withTitle("User") - .withDspaceObjectOwner(user) + context.restoreAuthSystemState(); + + getClient(getAuthToken(admin.getEmail(), password)) + .perform(get("/api/workflow/workflowitems/" + workflowItem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.test-outside-workflow-hidden").exists()) + .andExpect(jsonPath("$.sections.test-outside-submission-hidden").doesNotExist()) + .andExpect(jsonPath("$.sections.test-never-hidden").exists()) + .andExpect(jsonPath("$.sections.test-always-hidden").doesNotExist()); + + } + + @Test + public void testValidationWithHiddenSteps() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") .build(); - Item anotherUserProfile = ItemBuilder.createItem(context, personCollection) - .withTitle("User") - .withDspaceObjectOwner(anotherUser) + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .withSubmissionDefinition("test-hidden") + .withWorkflowGroup(1, eperson) .build(); WorkflowItem workflowItem = WorkflowItemBuilder.createWorkflowItem(context, collection) - .withTitle("Workflow Item") - .withIssueDate("2017-10-17") - .withAuthor("Author 1") - .withAuthor("Author 2", userProfile.getID().toString()) .build(); context.restoreAuthSystemState(); - getClient(getAuthToken(anotherUser.getEmail(), password)) + getClient(getAuthToken(admin.getEmail(), password)) .perform(get("/api/workflow/workflowitems/" + workflowItem.getID())) - .andExpect(status().isForbidden()); - - getClient(getAuthToken(user.getEmail(), password)) - .perform(get("/api/workflow/workflowitems/" + workflowItem.getID())) - .andExpect(status().isOk()); - + .andExpect(status().isOk()) + .andExpect(jsonPath("$.errors", hasSize(1))) + .andExpect(jsonPath("$.errors[0].message", is("error.validation.required"))) + .andExpect(jsonPath("$.errors[0].paths", contains("/sections/test-outside-workflow-hidden/dc.title"))); } } 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 f72f1294202e..c7725805687d 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 @@ -12,11 +12,14 @@ import static org.dspace.app.matcher.ResourcePolicyMatcher.matches; 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.dspace.authorize.ResourcePolicy.TYPE_SUBMISSION; import static org.hamcrest.MatcherAssert.assertThat; 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.equalTo; import static org.hamcrest.Matchers.hasItem; @@ -25,7 +28,6 @@ import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.nullValue; import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; import static org.springframework.data.rest.webmvc.RestMediaTypes.TEXT_URI_LIST_VALUE; import static org.springframework.http.MediaType.parseMediaType; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; @@ -53,7 +55,6 @@ import java.util.Set; import java.util.UUID; import java.util.concurrent.atomic.AtomicReference; -import javax.servlet.http.HttpServletRequest; import javax.ws.rs.core.MediaType; import com.fasterxml.jackson.databind.ObjectMapper; @@ -62,13 +63,11 @@ import org.apache.commons.lang3.StringEscapeUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.time.DateUtils; -import org.dspace.app.rest.exception.ExtractMetadataStepException; import org.dspace.app.rest.matcher.CollectionMatcher; import org.dspace.app.rest.matcher.ItemMatcher; import org.dspace.app.rest.matcher.MetadataMatcher; import org.dspace.app.rest.matcher.ResourcePolicyMatcher; import org.dspace.app.rest.matcher.WorkspaceItemMatcher; -import org.dspace.app.rest.model.AInprogressSubmissionRest; import org.dspace.app.rest.model.patch.AddOperation; import org.dspace.app.rest.model.patch.Operation; import org.dspace.app.rest.model.patch.RemoveOperation; @@ -88,13 +87,13 @@ 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; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.EntityType; -import org.dspace.content.InProgressSubmission; import org.dspace.content.Item; import org.dspace.content.MetadataFieldName; import org.dspace.content.Relationship; @@ -105,13 +104,13 @@ import org.dspace.content.service.ItemService; import org.dspace.content.service.WorkspaceItemService; import org.dspace.core.Constants; -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.GroupService; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; +import org.dspace.supervision.SupervisionOrder; import org.dspace.util.UUIDUtils; import org.dspace.versioning.ItemCorrectionProvider; import org.hamcrest.Matchers; @@ -119,7 +118,6 @@ import org.junit.Ignore; import org.junit.Test; import org.mockito.Mock; -import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.mock.web.MockMultipartFile; import org.springframework.test.web.servlet.MvcResult; @@ -9715,136 +9713,332 @@ public void sherpaPolicySectionWithWrongIssnCacheTest() throws Exception { } @Test - public void testAuthorFindOneAndDeposit() throws Exception { + public void supervisionOrdersLinksTest() throws Exception { context.turnOffAuthorisationSystem(); - EPerson user = EPersonBuilder.createEPerson(context) - .withCanLogin(true) - .withEmail("user@test.com") - .withPassword(password) + 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(); - EPerson anotherUser = EPersonBuilder.createEPerson(context) - .withCanLogin(true) - .withEmail("anotheruser@test.com") - .withPassword(password) + //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 testSubmissionWithHiddenSections() throws Exception { + + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) .withName("Parent Community") .build(); Collection collection = CollectionBuilder.createCollection(context, parentCommunity) .withName("Collection 1") - .withWorkflowGroup(1, eperson) + .withSubmissionDefinition("test-hidden") .build(); - Collection personCollection = CollectionBuilder.createCollection(context, parentCommunity) - .withName("Collection 2") - .withEntityType("Person") + WorkspaceItem workspaceItem = WorkspaceItemBuilder.createWorkspaceItem(context, collection) + .withTitle("Workspace Item") + .withIssueDate("2023-01-01") + .withType("Type") .build(); - Item userProfile = ItemBuilder.createItem(context, personCollection) - .withTitle("User") - .withDspaceObjectOwner(user) + context.restoreAuthSystemState(); + + getClient(getAuthToken(admin.getEmail(), password)) + .perform(get("/api/submission/workspaceitems/" + workspaceItem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.test-outside-workflow-hidden").doesNotExist()) + .andExpect(jsonPath("$.sections.test-outside-submission-hidden").exists()) + .andExpect(jsonPath("$.sections.test-never-hidden").exists()) + .andExpect(jsonPath("$.sections.test-always-hidden").doesNotExist()); + } + + @Test + public void testValidationWithHiddenSteps() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") .build(); - Item anotherUserProfile = ItemBuilder.createItem(context, personCollection) - .withTitle("User") - .withDspaceObjectOwner(anotherUser) + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .withSubmissionDefinition("test-hidden") .build(); WorkspaceItem workspaceItem = WorkspaceItemBuilder.createWorkspaceItem(context, collection) - .withTitle("Workspace Item") - .withIssueDate("2017-10-17") - .withAuthor("Author") - .withEditor("Editor", userProfile.getID().toString()) - .grantLicense() - .withFulltext("test.pdf", "source", InputStream.nullInputStream()) .build(); context.restoreAuthSystemState(); - getClient(getAuthToken(anotherUser.getEmail(), password)) - .perform(get("/api/submission/workspaceitems/" + workspaceItem.getID())) - .andExpect(status().isForbidden()); - - getClient(getAuthToken(user.getEmail(), password)) + getClient(getAuthToken(admin.getEmail(), password)) .perform(get("/api/submission/workspaceitems/" + workspaceItem.getID())) - .andExpect(status().isOk()); - - getClient(getAuthToken(user.getEmail(), password)) - .perform(post(BASE_REST_SERVER_URL + "/api/workflow/workflowitems") - .content("/api/submission/workspaceitems/" + workspaceItem.getID()) - .contentType(textUriContentType)) - .andExpect(status().isCreated()); - + .andExpect(status().isOk()) + .andExpect(jsonPath("$.errors", hasSize(1))) + .andExpect(jsonPath("$.errors[0].message", is("error.validation.required"))) + .andExpect(jsonPath("$.errors[0].paths", contains("/sections/test-outside-submission-hidden/dc.type"))); } @Test - public void lookupScopusMetadataWhenHaveExtractMetadataStepExceptionTest() throws Exception { + public void patchBySupervisorTest() throws Exception { context.turnOffAuthorisationSystem(); - //** GIVEN ** - //1. A community-collection structure with one parent community with sub-community and one collection. - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") - .build(); + EPerson userA = + EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withEmail("userA@test.com") + .withPassword(password) + .build(); - Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) - .withName("Sub Community") - .build(); + EPerson userB = + EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withEmail("userB@test.com") + .withPassword(password) + .build(); - Collection col1 = CollectionBuilder.createCollection(context, child1) - .withName("Collection 1") - .build(); + EPerson userC = + EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withEmail("userC@test.com") + .withPassword(password) + .build(); - WorkspaceItem workspaceItem = - WorkspaceItemBuilder.createWorkspaceItem(context, col1) - .withScopusIdentifier("2-s2.0-85009909050") + parentCommunity = + CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection publications = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Publications") + .withEntityType("Publication") + .withSubmissionDefinition("traditional") + .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(); - // try to add the web of science identifier - String patchBody = - getPatchContent( - List.of( - new AddOperation("/sections/traditionalpageone/dc.identifier.scopus", - List.of(Map.of("value", "2-s2.0-85009909030"))) - )); + //disable file upload mandatory + configurationService.setProperty("webui.submit.upload.required", false); + context.restoreAuthSystemState(); - // throw an ExtractMetadataStepException - Mockito.doThrow(new ExtractMetadataStepException("Error extracting metadata")) - .when(mockedSubmissionService) - .evaluatePatchToInprogressSubmission( - any(Context.class), - any(HttpServletRequest.class), - any(InProgressSubmission.class), - any(AInprogressSubmissionRest.class), - any(String.class), any(Operation.class)); + 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()); - try { - // set mocked submissionService to workspaceItemRestRepository - workspaceItemRestRepository.setSubmissionService(mockedSubmissionService); + 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 authToken = getAuthToken(admin.getEmail(), password); - getClient(authToken).perform(patch("/api/submission/workspaceitems/" + workspaceItem.getID()) - .content(patchBody) - .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) - .andExpect(status().isOk()) - // testing lookup still old value for dc.identifier.scopus - .andExpect(jsonPath("$.sections.traditionalpageone['dc.identifier.scopus'][0].value", - is("2-s2.0-85009909050"))); + String authTokenA = getAuthToken(userA.getEmail(), password); + String authTokenB = getAuthToken(userB.getEmail(), password); + String authTokenC = getAuthToken(userC.getEmail(), password); - // verify that the patch changes haven't been persisted - getClient(authToken).perform(get("/api/submission/workspaceitems/" + workspaceItem.getID())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.sections.traditionalpageone['dc.identifier.scopus'][0].value", - is("2-s2.0-85009909050"))); - } finally { - workspaceItemRestRepository.setSubmissionService(submissionService); - } + 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 index fbe3453b35dd..abb52f374ba1 100644 --- 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 @@ -7,8 +7,6 @@ */ 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; @@ -17,19 +15,19 @@ 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.app.rest.utils.Utils; -import org.dspace.app.suggestion.SolrSuggestionProvider; import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.ResourcePolicy; import org.dspace.authorize.service.ResourcePolicyService; @@ -47,289 +45,255 @@ 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 Alba Aliu (alba.aliu at 4science.it) + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.com) */ public class CanSubscribeFeatureIT extends AbstractControllerIntegrationTest { - private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(SolrSuggestionProvider.class); + + private static final Logger log = LogManager.getLogger(CanSubscribeFeatureIT.class); @Autowired private ItemConverter itemConverter; @Autowired private CollectionConverter collectionConverter; @Autowired - private Utils utils; - @Autowired private CommunityConverter communityConverter; - private Collection collectionAuthorized; - private Community communityAuthorized; - private AuthorizationFeature canSubscribeFeature; @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(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Community") + .build(); communityAuthorized = CommunityBuilder.createCommunity(context) - .withName("communityA") - .withAdminGroup(admin).build(); - + .withName("communityA") + .build(); collectionAuthorized = CollectionBuilder.createCollection(context, communityAuthorized) - .withName("Collection A") - .withAdminGroup(admin).build(); + .withName("Collection A") + .build(); context.restoreAuthSystemState(); canSubscribeFeature = authorizationFeatureService.find(CanSubscribeFeature.NAME); } @Test - public void testCanSubscribeCommunity() throws Exception { + public void canSubscribeCommunityAndCollectionTest() throws Exception { context.turnOffAuthorisationSystem(); - EPerson ePersonAuthorized = EPersonBuilder.createEPerson(context) - .withCanLogin(true) - .withPassword(password) - .withEmail("test@email.it") - .build(); - String token = getAuthToken(ePersonAuthorized.getEmail(), password); CommunityRest comRest = communityConverter.convert(parentCommunity, DefaultProjection.DEFAULT); - String comUri = utils.linkToSingleResource(comRest, "self").getHref(); - context.restoreAuthSystemState(); - getClient(token).perform(get("/api/authz/authorizations/search/object") - .param("uri", comUri) - .param("feature", canSubscribeFeature.getName()) - .param("embed", "feature")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded").exists()) - .andExpect(jsonPath("$.page.totalElements", greaterThan(0))); + 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); - @Test - public void anonymousCanSubscribeCommunityTest() throws Exception { - context.turnOffAuthorisationSystem(); - CommunityRest comRest = communityConverter.convert(parentCommunity, DefaultProjection.DEFAULT); - String comUri = utils.linkToSingleResource(comRest, "self").getHref(); + // define authorization that we know not exists + Authorization anonymousToCommunity = new Authorization(null, canSubscribeFeature, comRest); + Authorization anonymousToCollection = new Authorization(null, canSubscribeFeature, colRest); context.restoreAuthSystemState(); - getClient().perform(get("/api/authz/authorizations/search/object") - .param("uri", comUri) - .param("feature", canSubscribeFeature.getName()) - .param("embed", "feature")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded").doesNotExist()) - .andExpect(jsonPath("$.page.totalElements", is(0))); - } - @Test - public void anonymousCanSubscribeCollectionTest() throws Exception { - context.turnOffAuthorisationSystem(); - Collection collectionWithReadPermission = CollectionBuilder.createCollection(context, communityAuthorized) - .build(); - context.restoreAuthSystemState(); - CollectionRest collectionRest = collectionConverter.convert(collectionWithReadPermission, Projection.DEFAULT); - String comUri = utils.linkToSingleResource(collectionRest, "self").getHref(); - context.restoreAuthSystemState(); - getClient().perform(get("/api/authz/authorizations/search/object") - .param("uri", comUri) - .param("feature", canSubscribeFeature.getName()) - .param("embed", "feature")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded").doesNotExist()) - .andExpect(jsonPath("$.page.totalElements", is(0))); - } + String tokenAdmin = getAuthToken(admin.getEmail(), password); + String tokenEPerson = getAuthToken(eperson.getEmail(), password); - @Test - public void anonymousCanSubscribeItemTest() throws Exception { - context.turnOffAuthorisationSystem(); - Item item = ItemBuilder.createItem(context, collectionAuthorized) - .withTitle("Test item") - .build(); - context.restoreAuthSystemState(); - ItemRest itemRest = itemConverter.convert(item, Projection.DEFAULT); - String comUri = utils.linkToSingleResource(itemRest, "self").getHref(); - context.restoreAuthSystemState(); - getClient().perform(get("/api/authz/authorizations/search/object") - .param("uri", comUri) - .param("feature", canSubscribeFeature.getName()) - .param("embed", "feature")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded").doesNotExist()) - .andExpect(jsonPath("$.page.totalElements", is(0))); + 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 testCanNotSubscribeItem() throws Exception { + public void canNotSubscribeItemTest() throws Exception { context.turnOffAuthorisationSystem(); EPerson ePersonNotSubscribePermission = EPersonBuilder.createEPerson(context) - .withCanLogin(true) - .withPassword(password) - .withEmail("test@email.it") - .build(); + .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(admin) - .build(); + .withName("Group A") + .addMember(eperson) + .build(); Item item = ItemBuilder.createItem(context, collectionAuthorized) - .withTitle("Test item") - .build(); + .withTitle("Test item") + .build(); + cleanUpPermissions(resourcePolicyService.find(context, item)); setPermissions(item, groupWithReadPermission, Constants.READ); - item.setSubmitter(eperson); - context.restoreAuthSystemState(); + ItemRest itemRest = itemConverter.convert(item, Projection.DEFAULT); - String token = getAuthToken(ePersonNotSubscribePermission.getEmail(), password); - String comUri = utils.linkToSingleResource(itemRest, "self").getHref(); + + // 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(); - getClient(token).perform(get("/api/authz/authorizations/search/object") - .param("uri", comUri) - .param("feature", canSubscribeFeature.getName()) - .param("embed", "feature")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.page").exists()) - .andExpect(jsonPath("$.page.totalElements", is(0))) - .andExpect(jsonPath("$.page.totalPages", is(0))) - .andExpect(jsonPath("$.page.number", is(0))) - // default value of page size - .andExpect(jsonPath("$.page.size", is(20))); + + 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().isOk()) + .andExpect(jsonPath("$", Matchers.is(AuthorizationMatcher.matchAuthorization(epersonToItem)))); + + getClient(token2).perform(get("/api/authz/authorizations/" + adminToItem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is(AuthorizationMatcher.matchAuthorization(adminToItem)))); + + 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 testCanNotSubscribeCollection() throws Exception { + public void canNotSubscribeCollectionTest() throws Exception { context.turnOffAuthorisationSystem(); - Collection collectionWithReadPermission = CollectionBuilder.createCollection(context, communityAuthorized) - .withAdminGroup(admin).build(); + EPerson ePersonNotSubscribePermission = EPersonBuilder.createEPerson(context) - .withCanLogin(true) - .withPassword(password) - .withEmail("test@email.it") - .build(); + .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, collectionWithReadPermission)); - setPermissions(collectionWithReadPermission, groupWithReadPermission, Constants.READ); + .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(); - CollectionRest collectionRest = collectionConverter.convert(collectionWithReadPermission, Projection.DEFAULT); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + String tokenEPerson = getAuthToken(eperson.getEmail(), password); String token = getAuthToken(ePersonNotSubscribePermission.getEmail(), password); - String comUri = utils.linkToSingleResource(collectionRest, "self").getHref(); - context.restoreAuthSystemState(); - getClient(token).perform(get("/api/authz/authorizations/search/object") - .param("uri", comUri) - .param("feature", canSubscribeFeature.getName()) - .param("embed", "feature")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.page").exists()) - .andExpect(jsonPath("$.page.totalElements", is(0))) - .andExpect(jsonPath("$.page.totalPages", is(0))) - .andExpect(jsonPath("$.page.number", is(0))) - // default value of page size - .andExpect(jsonPath("$.page.size", is(20))); + + 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 testCanNotSubscribeCommunity() throws Exception { + public void canNotSubscribeCommunityTest() throws Exception { context.turnOffAuthorisationSystem(); - Community communityWithReadPermissions = CommunityBuilder.createCommunity(context).build(); + 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(admin) .addMember(eperson) .build(); - cleanUpPermissions(resourcePolicyService.find(context, communityWithReadPermissions)); - setPermissions(communityWithReadPermissions, groupWithReadPermission, Constants.READ); + + 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(); - CommunityRest communityRest = communityConverter.convert(communityWithReadPermissions, Projection.DEFAULT); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + String tokenEPerson = getAuthToken(eperson.getEmail(), password); String token = getAuthToken(ePersonNotSubscribePermission.getEmail(), password); - String comUri = utils.linkToSingleResource(communityRest, "self").getHref(); - context.restoreAuthSystemState(); - getClient(token).perform(get("/api/authz/authorizations/search/object") - .param("uri", comUri) - .param("feature", canSubscribeFeature.getName()) - .param("embed", "feature")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.page").exists()) - .andExpect(jsonPath("$.page.totalElements", is(0))) - .andExpect(jsonPath("$.page.totalPages", is(0))) - .andExpect(jsonPath("$.page.number", is(0))) - // default value of page size - .andExpect(jsonPath("$.page.size", is(20))); - } - @Test - public void testCanSubscribeCollection() throws Exception { - context.turnOffAuthorisationSystem(); - Collection collectionWithReadPermission = CollectionBuilder.createCollection(context, communityAuthorized) - .withAdminGroup(admin).build(); - EPerson ePersonSubscribePermission = EPersonBuilder.createEPerson(context) - .withCanLogin(true) - .withPassword(password) - .withEmail("test@email.it") - .build(); - context.restoreAuthSystemState(); - CollectionRest collectionRest = collectionConverter.convert(collectionWithReadPermission, Projection.DEFAULT); - String token = getAuthToken(ePersonSubscribePermission.getEmail(), password); - String comUri = utils.linkToSingleResource(collectionRest, "self").getHref(); - context.restoreAuthSystemState(); - getClient(token).perform(get("/api/authz/authorizations/search/object") - .param("uri", comUri) - .param("feature", canSubscribeFeature.getName()) - .param("embed", "feature")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded").exists()) - .andExpect(jsonPath("$.page.totalElements", greaterThan(0))); - } + getClient(tokenEPerson).perform(get("/api/authz/authorizations/" + epersonToComm.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is( + AuthorizationMatcher.matchAuthorization(epersonToComm)))); - @Test - public void testCanSubscribeItem() throws Exception { - context.turnOffAuthorisationSystem(); - EPerson ePersonSubscribePermission = EPersonBuilder.createEPerson(context) - .withCanLogin(true) - .withPassword(password) - .withEmail("test@email.it") - .build(); - Item item = ItemBuilder.createItem(context, collectionAuthorized) - .withTitle("Test item") - .build(); - context.restoreAuthSystemState(); - ItemRest itemRest = itemConverter.convert(item, Projection.DEFAULT); - String token = getAuthToken(ePersonSubscribePermission.getEmail(), password); - String comUri = utils.linkToSingleResource(itemRest, "self").getHref(); - context.restoreAuthSystemState(); - getClient(token).perform(get("/api/authz/authorizations/search/object") - .param("uri", comUri) - .param("feature", canSubscribeFeature.getName()) - .param("embed", "feature")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded").exists()) - .andExpect(jsonPath("$.page.totalElements", greaterThan(0))); + 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(); + .withDspaceObject(dSpaceObject) + .withAction(permissions) + .withGroup(group) + .build(); } catch (SQLException | AuthorizeException sqlException) { log.error(sqlException.getMessage()); } @@ -340,9 +304,9 @@ private void cleanUpPermissions(List resourcePolicies) { 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 000000000000..4bdc7743b571 --- /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/GenericAuthorizationFeatureIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/GenericAuthorizationFeatureIT.java index 1d3b5b051605..a93a964d36de 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/GenericAuthorizationFeatureIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/GenericAuthorizationFeatureIT.java @@ -16,7 +16,6 @@ import org.apache.commons.codec.CharEncoding; import org.apache.commons.io.IOUtils; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; -import org.dspace.authorize.ResourcePolicy; import org.dspace.builder.BitstreamBuilder; import org.dspace.builder.BundleBuilder; import org.dspace.builder.CollectionBuilder; @@ -757,7 +756,8 @@ public void testCanMoveAdmin() throws Exception { // Verify the general admin has this feature on item 1 getClient(adminToken).perform( get("/api/authz/authorizations/search/object?embed=feature&uri=" - + "http://localhost/api/core/items/" + item1.getID())) + + "http://localhost/api/core/items/" + item1.getID()) + .param("size", "30")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" + feature + "')]").exists()); @@ -798,7 +798,7 @@ public void testCanMoveAdmin() throws Exception { // grant item 1 admin REMOVE permissions on the item’s owning collection // verify item 1 admin has this feature on item 1 context.turnOffAuthorisationSystem(); - ResourcePolicy removePermission = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context) .withDspaceObject(collectionX) .withAction(Constants.REMOVE) .withUser(item1Writer) @@ -820,7 +820,7 @@ public void testCanMoveWriter() throws Exception { // grant item 1 write REMOVE permissions on the item’s owning collection context.turnOffAuthorisationSystem(); - ResourcePolicy removePermission = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context) .withDspaceObject(collectionX) .withAction(Constants.REMOVE) .withUser(item1Writer) 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 000000000000..bba45ecd229f --- /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 2cfcdbe0c39e..9e345a4b607e 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.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; @@ -86,7 +88,7 @@ public void notFoundTest() throws Exception { } @Test - public void findOneIIIFSearchableEntityTypeWithGlobalConfigIT() throws Exception { + public void findOneIIIFSearchableItemWithDefaultDimensionsIT() throws Exception { context.turnOffAuthorisationSystem(); parentCommunity = CommunityBuilder.createCommunity(context) .withName("Parent Community") @@ -137,10 +139,10 @@ public void findOneIIIFSearchableEntityTypeWithGlobalConfigIT() throws Exception .andExpect(jsonPath("$.metadata[2].value[0]", is("Smith, Donald"))) .andExpect(jsonPath("$.metadata[2].value[1]", is("Doe, John"))) .andExpect(jsonPath("$.sequences[0].canvases[0].@id", - Matchers.containsString("/iiif/" + publicItem1.getID() + "/canvas/" - + bitstream1.getID().toString()))) + Matchers.containsString("/iiif/" + publicItem1.getID() + "/canvas"))) .andExpect(jsonPath("$.sequences[0].canvases[0].label", is("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"))) @@ -1355,4 +1357,45 @@ public void findOneWithCacheEvictionAfterItemUpdate() throws Exception { .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 0d2ba67f16be..4ab812f82cd4 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 @@ private 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/CrisLayoutSectionMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/CrisLayoutSectionMatcher.java index b430004ae5d6..c6648f558560 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/CrisLayoutSectionMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/CrisLayoutSectionMatcher.java @@ -132,7 +132,6 @@ public static Matcher withBrowseComponent(int row, int pos, Stri * Matcher to verify that the current section has a top component at the * position pos of the row row with the given attributes. * - * @param id the section id to match * @param row the row index of the top component to match * @param pos the index of the top component in the given row * @param style the component style @@ -159,7 +158,6 @@ public static Matcher withTopComponent(int row, int pos, String * Matcher to verify that the current section has a search component at the * position pos of the row row with the given attributes. * - * @param id the section id to match * @param row the row index of the top component to match * @param pos the index of the top component in the given row * @param style the component style @@ -178,7 +176,6 @@ public static Matcher withSearchComponent(int row, int pos, Stri * Matcher to verify that the current section has a facet component at the * position pos of the row row with the given attributes. * - * @param id the section id to match * @param row the row index of the top component to match * @param pos the index of the top component in the given row * @param style the component style @@ -201,7 +198,6 @@ public static Matcher withFacetComponent(int row, int pos, Strin * @param row the row index of the top component to match * @param pos the index of the top component in the given row * @param style the component style - * @param discoveryConfig the discovery configuration name of the top component * @return the Matcher instance */ public static Matcher withIdAndTextRowComponent(String id, int row, int pos, String style, @@ -253,7 +249,6 @@ public static Matcher withIdAndCountersComponent(String id, int * Matcher to verify that the section with the given id has a counters component * at the position pos of the row row with the given discovery configurations. * - * @param id section id * @param row row * @param pos position * @param style style to check 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 ce0cda0b584f..f304540eff7a 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 @@ 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 62bf3e8fc778..c1223adf1738 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 @@ -87,6 +87,17 @@ public static Matcher submitterFacet(boolean hasNext) { ); } + 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 ef97b638608b..012c7f8f3eeb 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 @@ -32,6 +32,13 @@ public static Matcher entryAuthor(String label) { ); } + 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)), @@ -49,15 +56,20 @@ public static Matcher entrySubject(String label, int count) { 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")) ); } @@ -137,4 +149,15 @@ public static Matcher entryTypes(String label) { hasJsonPath("$._links.search.href", containsString("api/discover/search/objects"))); } + 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 65fb3c1ba249..bff84684d6f4 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 static Matcher matchFullEmbeds() { return matchEmbeds( "accessStatus", "bundles[]", + "identifiers", "mappedCollections[]", "owningCollection", "version", @@ -69,6 +70,7 @@ public static Matcher matchLinks(UUID uuid) { 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/LoginStatisticsMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/LoginStatisticsMatcher.java index 9f53b044b3ed..f787647b4239 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/LoginStatisticsMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/LoginStatisticsMatcher.java @@ -26,8 +26,6 @@ private LoginStatisticsMatcher() { * Gets a matcher to ensure a given value is present among all values for a * given metadata key. * - * @param key the metadata key. - * @param value the value that must be present. * @return the matcher. */ public static Matcher match(String name, String email, int count) { 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 aeed70e52774..8d43acbb0fd0 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 @@ private 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 index d49c6c7e127d..88349c73a301 100644 --- 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 @@ -6,13 +6,17 @@ * 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 @@ -27,20 +31,31 @@ public static Matcher matchSubscription(Subscription subscriptio return allOf( hasJsonPath("$.id", is(subscription.getID())), hasJsonPath("$.type", is("subscription")), - hasJsonPath("$.subscriptionType", is(subscription.getType())), - + 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, - "dSpaceObject", - "ePerson", + "resource", + "eperson", "self" ); } 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 000000000000..1ba147879d00 --- /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 69f9c501aa51..e727eb8accb9 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 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 static Matcher matchWorkflowActionEntry(WorkflowActionCon 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/iiif/MockIIIFApiQueryServiceImpl.java b/dspace-server-webapp/src/test/java/org/dspace/iiif/MockIIIFApiQueryServiceImpl.java new file mode 100644 index 000000000000..a240e76f9792 --- /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 000000000000..8ede6f29a08e --- /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 01cfe032386a..8cb88f45781d 100644 --- a/dspace-services/pom.xml +++ b/dspace-services/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - cris-2023.01.00-SNAPSHOT + cris-2023.01.01-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 0c0c5fad4508..d28a04ecf623 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 c5f47cbc9479..16f8a396fbee 100644 --- a/dspace-sword/pom.xml +++ b/dspace-sword/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - cris-2023.01.00-SNAPSHOT + cris-2023.01.01-SNAPSHOT .. diff --git a/dspace-swordv2/pom.xml b/dspace-swordv2/pom.xml index 8a8b811a161d..b35fb7388a15 100644 --- a/dspace-swordv2/pom.xml +++ b/dspace-swordv2/pom.xml @@ -13,7 +13,7 @@ org.dspace dspace-parent - cris-2023.01.00-SNAPSHOT + cris-2023.01.01-SNAPSHOT .. diff --git a/dspace/config/crosswalks/mapConverter-datacitePublicationLicense.properties b/dspace/config/crosswalks/mapConverter-datacitePublicationLicense.properties new file mode 100644 index 000000000000..05bcf86c28da --- /dev/null +++ b/dspace/config/crosswalks/mapConverter-datacitePublicationLicense.properties @@ -0,0 +1,8 @@ +CC\ BY = https://creativecommons.org/licenses/by/4.0/legalcode +CC\ BY\-SA = https://creativecommons.org/licenses/by-sa/4.0/legalcode +CC\ BY\-ND = https://creativecommons.org/licenses/by-nd/4.0/legalcode +CC\ BY\-NC = https://creativecommons.org/licenses/by-nc/4.0/legalcode +CC\ BY\-NC\-SA = https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode +CC\ BY\-NC\-ND = https://creativecommons.org/licenses/by-nc-nd/4.0/legalcode +CC0 = https://creativecommons.org/share-your-work/public-domain/cc0/ +PDM = https://creativecommons.org/publicdomain/mark/1.0/ \ No newline at end of file diff --git a/dspace/config/crosswalks/mapConverter-datacitePublicationRights.properties b/dspace/config/crosswalks/mapConverter-datacitePublicationRights.properties new file mode 100644 index 000000000000..049024f7dc56 --- /dev/null +++ b/dspace/config/crosswalks/mapConverter-datacitePublicationRights.properties @@ -0,0 +1,4 @@ +openaccess = http://purl.org/coar/access_right/c_abf2 +embargoed = http://purl.org/coar/access_right/c_f1cf +restricted = http://purl.org/coar/access_right/c_16ec +metadata\ only = http://purl.org/coar/access_right/c_14cb \ No newline at end of file diff --git a/dspace/config/crosswalks/mapConverter-datacitePublicationTypes.properties b/dspace/config/crosswalks/mapConverter-datacitePublicationTypes.properties new file mode 100644 index 000000000000..0ef150d6f8f8 --- /dev/null +++ b/dspace/config/crosswalks/mapConverter-datacitePublicationTypes.properties @@ -0,0 +1,50 @@ +Resource\ Types\:\:text = Text +Resource\ Types\:\:text\:\:annotation = Text +Resource\ Types\:\:text\:\:bibliography = Text +Resource\ Types\:\:text\:\:blog\ post = Text +Resource\ Types\:\:text\:\:book = Book +Resource\ Types\:\:text\:\:book\:\:book\ part = BookChapter +Resource\ Types\:\:text\:\:conference\ output = ConferencePaper +Resource\ Types\:\:text\:\:conference\ output\:\:conference paper not in proceedings = ConferencePaper +Resource\ Types\:\:text\:\:conference\ output\:\:conference poster not in proceedings = ConferencePaper +Resource\ Types\:\:text\:\:conference\ output\:\:conference presentation = ConferenceProceeding +Resource\ Types\:\:text\:\:conference\ output\:\:conference proceedings = ConferenceProceeding +Resource\ Types\:\:text\:\:journal = Journal +Resource\ Types\:\:text\:\:journal\:\:editorial = Journal +Resource\ Types\:\:text\:\:journal\:\:journal\ article = JournalArticle +Resource\ Types\:\:text\:\:journal\:\:journal\ article\:\:corrigendum = JournalArticle +Resource\ Types\:\:text\:\:journal\:\:journal\ article\:\:data\ paper = JournalArticle +Resource\ Types\:\:text\:\:journal\:\:journal\ article\:\:research\ article = JournalArticle +Resource\ Types\:\:text\:\:journal\:\:journal\ article\:\:review\ article = JournalArticle +Resource\ Types\:\:text\:\:journal\:\:journal\ article\:\:software\ paper = JournalArticle +Resource\ Types\:\:text\:\:journal\:\:letter\ to\ the\ editor = Journal +Resource\ Types\:\:text\:\:lecture = Text +Resource\ Types\:\:text\:\:letter = Text +Resource\ Types\:\:text\:\:magazine = Text +Resource\ Types\:\:text\:\:manuscript = Text +Resource\ Types\:\:text\:\:musical\ notation = Sound +Resource\ Types\:\:text\:\:newspaper = Text +Resource\ Types\:\:text\:\:newspaper\:\:newspaper\ article = Text +Resource\ Types\:\:text\:\:other\ periodical = Text +Resource\ Types\:\:text\:\:preprint = Preprint +Resource\ Types\:\:text\:\:report = Report +Resource\ Types\:\:text\:\:report\:\:clinical\ study = Report +Resource\ Types\:\:text\:\:report\:\:data\ management\ plan = OutputManagementPlan +Resource\ Types\:\:text\:\:report\:\:memorandum = Report +Resource\ Types\:\:text\:\:report\:\:policy\ report = Report +Resource\ Types\:\:text\:\:report\:\:project\ deliverable = Report +Resource\ Types\:\:text\:\:report\:\:research\ protocol = Report +Resource\ Types\:\:text\:\:report\:\:research\ report = Report +Resource\ Types\:\:text\:\:report\:\:technical\ report = Report +Resource\ Types\:\:text\:\:research\ proposal = Text +Resource\ Types\:\:text\:\:review = PeerReview +Resource\ Types\:\:text\:\:review\:\:book\ review = PeerReview +Resource\ Types\:\:text\:\:review\:\:commentary = PeerReview +Resource\ Types\:\:text\:\:review\:\:peer\ review = PeerReview +Resource\ Types\:\:text\:\:technical\ documentation = Text +Resource\ Types\:\:text\:\:thesis = Dissertation +Resource\ Types\:\:text\:\:thesis\:\:bachelor\ thesis = Dissertation +Resource\ Types\:\:text\:\:thesis\:\:doctoral\ thesis = Dissertation +Resource\ Types\:\:text\:\:thesis\:\:master\ thesis = Dissertation +Resource\ Types\:\:text\:\:transcription = Text +Resource\ Types\:\:text\:\:working\ paper = Preprint diff --git a/dspace/config/crosswalks/mapConverter-scopusToCoarPublicationTypes.properties b/dspace/config/crosswalks/mapConverter-scopusToCoarPublicationTypes.properties new file mode 100644 index 000000000000..35e759cd6860 --- /dev/null +++ b/dspace/config/crosswalks/mapConverter-scopusToCoarPublicationTypes.properties @@ -0,0 +1,14 @@ +ar = Resource Types::text::journal::journal article +er = Resource Types::text::journal::journal article::corrigendum +re = Resource Types::text::journal::journal article::review article +cp = Resource Types::text::conference outputs::conference proceedings::conference paper +bk = Resource Types::text::book +ch = Resource Types::text::book chapter +ed = Resource Types::text::journal::editorial +le = Resource Types::text::letter +cr = Conference Review +ab = Abstract Report +bz = Business Article +no = Note +pr = Press Release +sh = Short Survey \ No newline at end of file diff --git a/dspace/config/crosswalks/template/patent-datacite-xml.template b/dspace/config/crosswalks/template/patent-datacite-xml.template new file mode 100644 index 000000000000..22b400e96926 --- /dev/null +++ b/dspace/config/crosswalks/template/patent-datacite-xml.template @@ -0,0 +1,43 @@ + + + @dc.identifier.doi@ + + @group.dc-contributor-author.start@ + + @dc.contributor.author@ + @relation.dc-contributor-author.start@ + @person.identifier.orcid@ + @relation.dc-contributor-author.end@ + @oairecerif.author.affiliation@ + + @group.dc-contributor-author.end@ + + + @dc.title@ + + @dc.publisher@ + @virtual.date.dc-date-issued.YYYY@ + + @dc.subject@ + + + @dc.date.issued@ + @datacite.available@ + + @dc.language.iso@ + + + @dc.identifier.uri@ + + @dc.description.version@ + + + @oaire.licenseCondition@ + + @datacite.rights@ + + + @dc.description.abstract@ + @dc.description@ + + \ No newline at end of file diff --git a/dspace/config/crosswalks/template/product-datacite-xml.template b/dspace/config/crosswalks/template/product-datacite-xml.template new file mode 100644 index 000000000000..50224aa35cca --- /dev/null +++ b/dspace/config/crosswalks/template/product-datacite-xml.template @@ -0,0 +1,42 @@ + + + @dc.identifier.doi@ + + @group.dc-contributor-author.start@ + + @dc.contributor.author@ + @relation.dc-contributor-author.start@ + @person.identifier.orcid@ + @relation.dc-contributor-author.end@ + @oairecerif.author.affiliation@ + + @group.dc-contributor-author.end@ + + + @dc.title@ + + @dc.publisher@ + @virtual.date.dc-date-issued.YYYY@ + + @dc.subject@ + + + @dc.date.issued@ + + @dc.language.iso@ + + + @dc.identifier.uri@ + + @dc.description.version@ + + + @oaire.licenseCondition@ + + @datacite.rights@ + + + @dc.description.abstract@ + @dc.description@ + + \ No newline at end of file diff --git a/dspace/config/crosswalks/template/publication-datacite-xml.template b/dspace/config/crosswalks/template/publication-datacite-xml.template new file mode 100644 index 000000000000..22b400e96926 --- /dev/null +++ b/dspace/config/crosswalks/template/publication-datacite-xml.template @@ -0,0 +1,43 @@ + + + @dc.identifier.doi@ + + @group.dc-contributor-author.start@ + + @dc.contributor.author@ + @relation.dc-contributor-author.start@ + @person.identifier.orcid@ + @relation.dc-contributor-author.end@ + @oairecerif.author.affiliation@ + + @group.dc-contributor-author.end@ + + + @dc.title@ + + @dc.publisher@ + @virtual.date.dc-date-issued.YYYY@ + + @dc.subject@ + + + @dc.date.issued@ + @datacite.available@ + + @dc.language.iso@ + + + @dc.identifier.uri@ + + @dc.description.version@ + + + @oaire.licenseCondition@ + + @datacite.rights@ + + + @dc.description.abstract@ + @dc.description@ + + \ No newline at end of file diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 0204d88ed890..7db34a16b978 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -466,6 +466,7 @@ useProxies = true filter.plugins = Text Extractor filter.plugins = JPEG Thumbnail filter.plugins = PDFBox JPEG Thumbnail +filter.plugins = Branded Preview JPEG # [To enable Branded Preview]: uncomment and insert the following into the plugin list @@ -557,6 +558,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) @@ -614,7 +621,7 @@ crosswalk.dissemination.DataCite.preferList = false crosswalk.dissemination.DataCite.publisher = My University #crosswalk.dissemination.DataCite.dataManager = # defaults to publisher #crosswalk.dissemination.DataCite.hostingInstitution = # defaults to publisher -crosswalk.dissemination.DataCite.namespace = http://datacite.org/schema/kernel-3 +crosswalk.dissemination.DataCite.namespace = http://datacite.org/schema/kernel-4 # Crosswalk Plugin Configuration: # The purpose of Crosswalks is to translate an external metadata format to/from @@ -1077,11 +1084,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 @@ -1103,8 +1111,8 @@ thumbnail.hqscaling = true #### Settings for BrandedPreviewJPEGFilter #### # max dimensions of the preview image -webui.preview.maxwidth = 600 -webui.preview.maxheight = 600 +webui.preview.maxwidth = 1000 +webui.preview.maxheight = 1000 # Blur before scaling. A little blur before scaling does wonders for keeping # moire in check. @@ -1341,8 +1349,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 @@ -1672,6 +1678,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 ########################## # METADATA EXTRACTION # @@ -1910,6 +1920,7 @@ include = ${module_dir}/doi-curation.cfg include = ${module_dir}/google-analytics.cfg include = ${module_dir}/google-scholar.cfg include = ${module_dir}/healthcheck.cfg +include = ${module_dir}/identifiers.cfg include = ${module_dir}/irus-statistics.cfg include = ${module_dir}/metadata-security.cfg include = ${module_dir}/cris.cfg diff --git a/dspace/config/emails/subscription b/dspace/config/emails/subscription deleted file mode 100644 index 2879e579075d..000000000000 --- 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 = 'DSpace Subscription') -New items are available in the collections you have subscribed to: - -${params[0]} - -DSpace diff --git a/dspace/config/emails/subscriptions_content b/dspace/config/emails/subscriptions_content index 1602807c029b..fc186abbb0b9 100644 --- a/dspace/config/emails/subscriptions_content +++ b/dspace/config/emails/subscriptions_content @@ -16,4 +16,4 @@ List of changed items : ${params[1]} Items ----- -List of changed items : ${params[2]} +List of changed items : ${params[2]} \ No newline at end of file diff --git a/dspace/config/hibernate.cfg.xml b/dspace/config/hibernate.cfg.xml index b3d44e2cb779..563fd86735bc 100644 --- a/dspace/config/hibernate.cfg.xml +++ b/dspace/config/hibernate.cfg.xml @@ -54,6 +54,7 @@ + @@ -115,6 +116,7 @@ + diff --git a/dspace/config/item-submission.xml b/dspace/config/item-submission.xml index 6c33b1023a28..b3e2f504c9b5 100644 --- a/dspace/config/item-submission.xml +++ b/dspace/config/item-submission.xml @@ -59,7 +59,6 @@ submit.progressbar.collection org.dspace.app.rest.submit.step.CollectionStep collection - submission submit.progressbar.upload diff --git a/dspace/config/launcher.xml b/dspace/config/launcher.xml index ce40c6d0abd7..69e6257ddf6d 100644 --- a/dspace/config/launcher.xml +++ b/dspace/config/launcher.xml @@ -296,13 +296,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 74ad5d5ad33a..78f5bad26770 100644 --- a/dspace/config/local.cfg.EXAMPLE +++ b/dspace/config/local.cfg.EXAMPLE @@ -243,6 +243,8 @@ db.schema = public # avoid trouble for such browsers (i.e. rest.cors.allowed-origins = ${dspace.ui.url}, https://samltest.id ) #rest.cors.allowed-origins = ${dspace.ui.url} +#rest.cors.bitstream-allowed-origins = ${dspace.ui.url} + ################################################# # SPRING BOOT SETTINGS (Used by Server Webapp) # ################################################# @@ -275,19 +277,19 @@ metadata.extraction.grobid.url = # LOOKUP SUBMISSION PROVIDER CONFIGURATION # ############################################ # In order to use the Astrophysics Data System services you need to obtain an API Key from Astrophysics Data System. Once you get it, add it to the following configuration value. -# Note that when apikey is configured by default the service is enabled, see bte.xml for further configuration -submission.lookup.ads.apikey = +# Note that when apikey is configured by default the service is enabled, see spring-dspace-addon-import-services.xml for further configuration +ads.key = # In order to use the EPO services you need to obtain an API Key from https://developers.epo.org. Once you get it, add it to the # following configuration value. -# Note that when apikey is configured by default the service is enabled, see bte.xml for further configuration +# Note that when apikey is configured by default the service is enabled, see spring-dspace-addon-import-services.xml for further configuration epo.consumerKey= epo.consumerSecretKey= # VuFind endpoints (i.e. https://vufind.org/demo/api/v1/...) # please note that VuFind API are IP protected so you need to configure it to grant access from the DSpace Server IP -vufind.api-search = -vufind.api-record = +vufind.url.search = +vufind.url = ########################################## # ORCID Integration # diff --git a/dspace/config/modules/authentication-password.cfg b/dspace/config/modules/authentication-password.cfg index 078da7f8d174..9029e76e2621 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 9e6ec5937276..6174af53a0f3 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 fffc56a6f476..fbd6f406c14d 100644 --- a/dspace/config/modules/external-providers.cfg +++ b/dspace/config/modules/external-providers.cfg @@ -78,7 +78,7 @@ scopus.instToken = # by default we use standart mode, to use complete mode this variable must be valued with 'COMPLETE' scopus.search-api.viewMode = # Number of items to fetch within same request using OR clause concatenation. -scopus.fetchSize = 1 +scopus.fetchSize = 20 ################################################################# #------------------- Web of Science (WOS) ----------------------# #---------------------------------------------------------------# @@ -87,4 +87,10 @@ scopus.fetchSize = 1 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 000000000000..63a9cda30f17 --- /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 df525776f5cc..9d2eb77be2cc 100644 --- a/dspace/config/modules/rest.cfg +++ b/dspace/config/modules/rest.cfg @@ -83,6 +83,9 @@ rest.properties.exposed = bulk-export.limit.admin rest.properties.exposed = bulk-export.limit.loggedIn rest.properties.exposed = bulk-export.limit.notLoggedIn rest.properties.exposed = cris.layout.thumbnail.maxsize +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 # @@ -128,6 +131,11 @@ rest.report-url.item-query = static/reports/query.html # The following configuration setting will construct a SQL regular expression test appropriate to your database engine rest.regex-clause = text_value ~ ? +##### Customize the REST origins allowed to retrieve the bitstreams ##### +##### default is set to pattern * - use this configuration to restrict/modify this behavior +##### This configuration doens't support the wildcard +bitstream.cors.allowed-origins = + ##### Configure REST Report Filters ##### # A filter contains a set of tests that will be applied to an item to determine its inclusion in a particular report. diff --git a/dspace/config/modules/workflow.cfg b/dspace/config/modules/workflow.cfg index 8d11df03d5b9..c77050d719ce 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/crispatent-types.xml b/dspace/config/registries/crispatent-types.xml new file mode 100644 index 000000000000..cd6138d17036 --- /dev/null +++ b/dspace/config/registries/crispatent-types.xml @@ -0,0 +1,46 @@ + + + + DSpace Cris Patent Types + + + + crispatent + http://dspace.org/crispatent + + + + crispatent + kind + + + + + crispatent + document + kind + + + + + crispatent + document + issueDate + + + + + crispatent + document + title + + + + + crispatent + document + description + + + + \ No newline at end of file diff --git a/dspace/config/registries/dspace-types.xml b/dspace/config/registries/dspace-types.xml index eb71b9edd9d6..d768ff41361f 100644 --- a/dspace/config/registries/dspace-types.xml +++ b/dspace/config/registries/dspace-types.xml @@ -13,7 +13,7 @@ dspace process filetype - + @@ -105,7 +105,14 @@ dspace orcid webhook - + + + + + dspace + legacy + oai-identifier + diff --git a/dspace/config/registries/workflow-types.xml b/dspace/config/registries/workflow-types.xml index 3f26849bf297..a6417e3894db 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/core-dao-services.xml b/dspace/config/spring/api/core-dao-services.xml index e5ace338e78e..2f161a4de1db 100644 --- a/dspace/config/spring/api/core-dao-services.xml +++ b/dspace/config/spring/api/core-dao-services.xml @@ -35,6 +35,7 @@ + @@ -83,5 +84,7 @@ + + diff --git a/dspace/config/spring/api/core-factory-services.xml b/dspace/config/spring/api/core-factory-services.xml index cf682edfd804..63f255167575 100644 --- a/dspace/config/spring/api/core-factory-services.xml +++ b/dspace/config/spring/api/core-factory-services.xml @@ -62,5 +62,8 @@ + + + diff --git a/dspace/config/spring/api/core-services.xml b/dspace/config/spring/api/core-services.xml index 3947474d8ae1..a999b7d7bda7 100644 --- a/dspace/config/spring/api/core-services.xml +++ b/dspace/config/spring/api/core-services.xml @@ -48,7 +48,6 @@ - @@ -61,6 +60,8 @@ + + @@ -106,8 +107,9 @@ + - + @@ -177,6 +179,8 @@ + + @@ -191,5 +195,7 @@ + + diff --git a/dspace/config/spring/api/crossref-integration.xml b/dspace/config/spring/api/crossref-integration.xml index e01b613833e4..35712a2983fb 100644 --- a/dspace/config/spring/api/crossref-integration.xml +++ b/dspace/config/spring/api/crossref-integration.xml @@ -107,7 +107,7 @@ - + diff --git a/dspace/config/spring/api/crosswalks.xml b/dspace/config/spring/api/crosswalks.xml index 18d49f3433f4..504645bd83b6 100644 --- a/dspace/config/spring/api/crosswalks.xml +++ b/dspace/config/spring/api/crosswalks.xml @@ -19,6 +19,7 @@ + @@ -59,8 +60,10 @@ + + @@ -283,6 +286,15 @@ + + + + + + + + + @@ -332,6 +344,24 @@ + + + + + + + + + + + + + + + + + + @@ -564,9 +594,13 @@ + + + + @@ -637,6 +671,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dspace/config/spring/api/datacite-integration.xml b/dspace/config/spring/api/datacite-integration.xml new file mode 100644 index 000000000000..236ec0a3bda9 --- /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 12c8eb0c95eb..6e759f05295b 100644 --- a/dspace/config/spring/api/discovery.xml +++ b/dspace/config/spring/api/discovery.xml @@ -34,6 +34,7 @@ + @@ -59,7 +60,9 @@ dc.contributor.author dc.contributor.editor - + + + @@ -93,6 +96,8 @@ + + @@ -128,6 +133,10 @@ + + + + @@ -987,6 +996,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + search.resourcetype:WorkspaceItem OR search.resourcetype:XmlWorkflowItem + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2211,6 +2295,28 @@ + + + + + + + location.coll:{0} + + + + + + + + + + + location.comm:{0} + + + + @@ -3222,6 +3328,18 @@ + + + + + + + + placeholder.placeholder.placeholder + + + diff --git a/dspace/config/spring/api/epo-integration.xml b/dspace/config/spring/api/epo-integration.xml index 57f7ce9919c3..b8b737ba80b8 100644 --- a/dspace/config/spring/api/epo-integration.xml +++ b/dspace/config/spring/api/epo-integration.xml @@ -19,19 +19,78 @@ + - + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + @@ -40,7 +99,7 @@ - + @@ -49,7 +108,7 @@ - + @@ -57,18 +116,22 @@ - + - + + - + - + + + + @@ -77,16 +140,19 @@ - + - + - + + + + @@ -95,7 +161,7 @@ - + @@ -104,7 +170,7 @@ - + @@ -114,13 +180,13 @@ - - + + - + diff --git a/dspace/config/spring/api/identifier-service.xml b/dspace/config/spring/api/identifier-service.xml index e9f08003bd63..39754edce46b 100644 --- a/dspace/config/spring/api/identifier-service.xml +++ b/dspace/config/spring/api/identifier-service.xml @@ -1,8 +1,11 @@ + http://www.springframework.org/schema/beans/spring-beans-2.5.xsd + http://www.springframework.org/schema/util + http://www.springframework.org/schema/util/spring-util.xsd"> @@ -42,7 +45,7 @@ a DOIConnector that handles all API calls to your DOI registration agency. Please configure a DOIConnector as well! --> - - + - - + + + + + + - - - + @@ -226,16 +241,12 @@ - - - + - - - + - @@ -27,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/scopus-integration.xml b/dspace/config/spring/api/scopus-integration.xml index 658d835716a1..3823c2cc5c1a 100644 --- a/dspace/config/spring/api/scopus-integration.xml +++ b/dspace/config/spring/api/scopus-integration.xml @@ -19,7 +19,6 @@ - @@ -31,6 +30,8 @@ + + @@ -198,6 +199,22 @@ + + + + + + + + + + + + + + + + diff --git a/dspace/config/spring/api/scripts.xml b/dspace/config/spring/api/scripts.xml index 07e1210b93eb..a99d580dd901 100644 --- a/dspace/config/spring/api/scripts.xml +++ b/dspace/config/spring/api/scripts.xml @@ -109,11 +109,7 @@ - - - - - + @@ -123,7 +119,12 @@ - + + + + + + @@ -153,4 +154,10 @@ + + + + + + diff --git a/dspace/config/spring/api/sherpa.xml b/dspace/config/spring/api/sherpa.xml index 0414f3f8e4b4..eb299e4e2644 100644 --- a/dspace/config/spring/api/sherpa.xml +++ b/dspace/config/spring/api/sherpa.xml @@ -17,7 +17,7 @@ - dc.identifier.issn + dc.relation.issn diff --git a/dspace/config/spring/api/subscriptions_email_configuration.xml b/dspace/config/spring/api/subscriptions_email_configuration.xml index 0bff95bc413c..c946a29bec7e 100644 --- a/dspace/config/spring/api/subscriptions_email_configuration.xml +++ b/dspace/config/spring/api/subscriptions_email_configuration.xml @@ -1,12 +1,9 @@ - - + + + + @@ -32,54 +34,37 @@ - - - - - - - - - - - - - - - - - - - - dc.title - + + + + - + + - + - - - - + - - - - + - - - - + + + + dc.title + + + + + + + diff --git a/dspace/config/spring/api/workflow-actions.xml b/dspace/config/spring/api/workflow-actions.xml index 3edfe93a0ce7..120b69a9fc87 100644 --- a/dspace/config/spring/api/workflow-actions.xml +++ b/dspace/config/spring/api/workflow-actions.xml @@ -14,9 +14,12 @@ - + + + + - + @@ -68,6 +71,12 @@ + + + + + + diff --git a/dspace/config/spring/api/workflow.xml b/dspace/config/spring/api/workflow.xml index af44e524e8be..2b523ce96972 100644 --- a/dspace/config/spring/api/workflow.xml +++ b/dspace/config/spring/api/workflow.xml @@ -167,6 +167,7 @@ + diff --git a/dspace/config/spring/rest/scripts.xml b/dspace/config/spring/rest/scripts.xml index 4e6a47f64691..6853d7499276 100644 --- a/dspace/config/spring/rest/scripts.xml +++ b/dspace/config/spring/rest/scripts.xml @@ -88,7 +88,12 @@ - + + + + + + diff --git a/dspace/config/spring/rest/statistics.xml b/dspace/config/spring/rest/statistics.xml index 91a970210d34..23b528eddcb5 100644 --- a/dspace/config/spring/rest/statistics.xml +++ b/dspace/config/spring/rest/statistics.xml @@ -417,6 +417,182 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -598,6 +774,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -610,6 +813,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -649,11 +879,15 @@ + + + + diff --git a/dspace/config/submission-forms.xml b/dspace/config/submission-forms.xml index 511b4c3e033c..784b8ee301aa 100644 --- a/dspace/config/submission-forms.xml +++ b/dspace/config/submission-forms.xml @@ -8,33 +8,33 @@ dc title + false onebox - false - You must enter a title for the file. + You must enter a title for the file. dc description + false textarea - false - + dc type + false dropdown - false - Personal picture, logo, main article, etc. + @@ -44,11 +44,11 @@ dc contributor author + false onebox - false - You must enter at least the author. Enter the names of the authors of this item in the form Lastname, Firstname [i.e. Smith, Josh or Smith, J]. + You must enter at least the author. @@ -56,11 +56,11 @@ oairecerif author affiliation + false onebox - false - Enter the affiliation of the author as stated on the publication. + @@ -70,11 +70,11 @@ dc contributor editor + false onebox - false - You must enter at least the author. The editors of this publication. + You must enter at least the author. @@ -82,11 +82,11 @@ oairecerif editor affiliation + false onebox - false - Enter the affiliation of the editor as stated on the publication. + @@ -96,11 +96,11 @@ dc relation funding + false onebox - false - You must enter at least the funding name. Enter the name of funding, if any, that has supported this publication + You must enter at least the funding name. @@ -108,11 +108,11 @@ dc relation grantno + false onebox - false - If the funding is not found in the system please enter the funding identifier / grant no + @@ -122,11 +122,11 @@ dc relation funding + false onebox - false - You must enter at least the funding name. Enter the name of funding, if any, that has supported this product + You must enter at least the funding name. @@ -134,11 +134,11 @@ dc relation grantno + false onebox - false - If the funding is not found in the system please enter the funding identifier / grant no + @@ -148,11 +148,11 @@ dc relation funding + false onebox - false - You must enter at least the funding name. Enter the name of funding, if any, that has supported this patent + You must enter at least the funding name. @@ -160,11 +160,59 @@ dc relation grantno + false onebox + If the funding is not found in the system please enter the funding identifier / grant no + + + + +
+ + + crispatent + document + kind + + onebox + false + You must enter the kind code. + + + + + crispatent + document + issueDate + + onebox + false + You must enter the publication date. + + + + + crispatent + document + title + + onebox false - If the funding is not found in the system please enter the funding identifier / grant no + + + + + crispatent + document + description + + textarea + false + + +
@@ -174,45 +222,45 @@ oairecerif affiliation role - - onebox false - + + onebox + oairecerif person affiliation - - onebox false - You must enter at least the organisation of your affiliation. + + onebox + You must enter at least the organisation of your affiliation. oairecerif affiliation startDate - - date false - + + date + oairecerif affiliation endDate - - date false - + + date + @@ -221,45 +269,45 @@ crisrp qualification - - onebox false - You must enter the organisation + + onebox + You must enter the organisation crisrp qualification role - - onebox false - You must enter the qualification title. + + onebox + You must enter the qualification title. crisrp qualification start - - date false - + + date + crisrp qualification end - - date false - + + date + @@ -269,44 +317,44 @@ crisrp education role - - onebox false - You must enter the degree/title + + onebox + You must enter the degree/title crisrp education - - onebox false - You must enter the organisation + + onebox + You must enter the organisation crisrp education start - - date false - + + date + crisrp education end - - date false - + + date + @@ -316,11 +364,11 @@ dc contributor author + false name - false - You must enter at least the author. Enter the names of the authors of this item in the form Lastname, Firstname [i.e. Smith, Josh or Smith, J]. + You must enter at least the author. @@ -328,11 +376,11 @@ oairecerif author affiliation + false onebox - false - Enter the affiliation of the author as stated on the publication. + @@ -342,11 +390,11 @@ dc contributor author + false name - false - You must enter at least the inventor. Enter the names of the authors of this item in the form Lastname, Firstname [i.e. Smith, Josh or Smith, J]. + You must enter at least the inventor. @@ -354,11 +402,11 @@ oairecerif author affiliation + false onebox - false - Enter the affiliation of the author as stated on the publication. + @@ -368,11 +416,11 @@ oairecerif identifier url + false onebox - false - You must enter at least the site url. + You must enter at least the site url. @@ -380,11 +428,11 @@ crisrp site title + false onebox - false - + @@ -394,55 +442,55 @@ dc identifier issn + false onebox - false - + dc title + false onebox - false - You must enter a main title for this item. + You must enter a main title for this item. dc publisher + false onebox - false - + dc subject + true tag - true - + dc description + false textarea - false - + @@ -451,23 +499,23 @@ dc identifier + true qualdrop_value - true - If the item has any identification numbers or codes associated with it, please enter the types and the actual numbers or codes. + dc title + false onebox - false - You must enter a main title for this item. Enter the main title of the item. + You must enter a main title for this item. @@ -475,11 +523,11 @@ dc title alternative + true onebox - true - If the item has any alternative titles, please enter them here. + @@ -487,13 +535,13 @@ dc date issued + false date - false - You must enter at least the year. Please give the date of previous publication or public distribution. You can leave out the day and/or month if they aren't applicable. + You must enter at least the year. @@ -501,11 +549,11 @@ dc contributor author + true group - true - Enter the names of the authors of this item. + @@ -513,22 +561,22 @@ dc contributor editor + true group - true - The editors of this publication. + dc type + false onebox - false - You must select a publication type Select the type(s) of content of the item. + You must select a publication type publication-coar-types @@ -538,23 +586,23 @@ dc identifier + true qualdrop_value - true - If the item has any identification numbers or codes associated with it, please enter the types and the actual numbers or codes. + dc title + false onebox - false - You must enter a main title for this item. Enter the main title of the item. + You must enter a main title for this item. @@ -562,11 +610,11 @@ dc title alternative + true onebox - true - If the item has any alternative titles, please enter them here. + @@ -574,13 +622,13 @@ dc date issued + false date - false - You must enter at least the year. Please give the date of previous publication or public distribution. You can leave out the day and/or month if they aren't applicable. + You must enter at least the year. @@ -588,11 +636,11 @@ dc contributor author + true group - true - Enter the names of the authors of this item. + @@ -600,22 +648,22 @@ dc contributor group + true onebox - true - The editors of this publication. + dc type + false onebox - false - You must select a publication type Select the type(s) of content of the item. + You must select a publication type publication-coar-types @@ -626,11 +674,11 @@ dc contributor author + false onebox - false - You must enter at least the author. Enter the names of the authors of this item in the form Lastname, Firstname [i.e. Smith, Josh or Smith, J]. + You must enter at least the author. @@ -638,11 +686,11 @@ oairecerif author affiliation + false onebox - false - Enter the affiliation of the author as stated on the publication. + @@ -652,11 +700,11 @@ dc contributor editor + false onebox - false - You must enter at least the author. The editors of this publication. + You must enter at least the author. @@ -664,11 +712,11 @@ oairecerif editor affiliation + false onebox - false - Enter the affiliation of the editor as stated on the publication. + @@ -677,23 +725,23 @@ dc identifier + true qualdrop_value - true - If the item has any identification numbers or codes associated with it, please enter the types and the actual numbers or codes. + dc title + false onebox - false - You must enter a main title for this item. Enter the main title of the item. + You must enter a main title for this item. @@ -701,11 +749,11 @@ dc title alternative + true onebox - true - If the item has any alternative titles, please enter them here. + @@ -713,13 +761,13 @@ dc date issued + false date - false - You must enter at least the year. Please give the date of previous publication or public distribution. You can leave out the day and/or month if they aren't applicable. + You must enter at least the year. @@ -727,11 +775,11 @@ dc contributor author + true group - true - Enter the names of the authors of this item. + @@ -739,22 +787,22 @@ dc contributor group + true onebox - true - The editors of this publication. + dc type + false onebox - false - You must select a publication type Select the type(s) of content of the item. + You must select a publication type publication-coar-types @@ -765,11 +813,11 @@ dc contributor author + false onebox - false - You must enter at least the author. Enter the names of the authors of this item in the form Lastname, Firstname [i.e. Smith, Josh or Smith, J]. + You must enter at least the author. @@ -777,11 +825,11 @@ oairecerif author affiliation + false onebox - false - Enter the affiliation of the author as stated on the publication. + @@ -791,11 +839,11 @@ dc contributor editor + false onebox - false - You must enter at least the author. The editors of this publication. + You must enter at least the author. @@ -803,37 +851,37 @@ oairecerif editor affiliation + false onebox - false - Enter the affiliation of the editor as stated on the publication. + - +
dc language iso + false dropdown - false - Select the language of the main content of the item. If the language does not appear in the list, please select 'Other'. If the content does not really have a language (for example, if it is a dataset or an image) please select 'N/A'. + dc subject + true tag - true - Enter appropriate subject keywords or phrases. + @@ -841,11 +889,11 @@ datacite subject fos + true onebox - true - + oecd @@ -854,11 +902,11 @@ dc description abstract + false textarea - false - Enter the abstract of the item. + @@ -868,12 +916,12 @@ dc relation publication + false + publication-coar-types:c_3248,publication-coar-types:c_5794,publication-coar-types:c_6670 onebox - false - The publication where this publication is included. E.g. a book chapter lists here the book, a contribution to a conference lists here the conference proceeding. - publication-coar-types:c_3248,publication-coar-types:c_5794,publication-coar-types:c_6670 + @@ -881,12 +929,12 @@ dc relation isbn + false + publication-coar-types:c_3248,publication-coar-types:c_5794,publication-coar-types:c_6670 onebox - false - The ISBN of the book/report if it was not found in the system - publication-coar-types:c_3248,publication-coar-types:c_5794,publication-coar-types:c_6670 + @@ -894,12 +942,12 @@ dc relation doi + false + publication-coar-types:c_3248,publication-coar-types:c_5794,publication-coar-types:c_6670 onebox - false - The DOI of the book/report if it was not found in the system - publication-coar-types:c_3248,publication-coar-types:c_5794,publication-coar-types:c_6670 + @@ -907,11 +955,11 @@ dc relation journal + false onebox - false - The journal or Serie where this publication has been published + @@ -919,11 +967,11 @@ dc relation ispartofseries + true series - true - Enter the series and number assigned to this item by your community. + @@ -931,11 +979,11 @@ dc relation issn + false onebox - false - The journal or Serie ISSN if it was not found in the system + @@ -943,12 +991,12 @@ dc coverage publication + false + publication-coar-types:c_efa0,publication-coar-types:c_ba08 onebox - false - The publication object of the review - publication-coar-types:c_efa0,publication-coar-types:c_ba08 + @@ -956,12 +1004,12 @@ dc coverage isbn + false + publication-coar-types:c_efa0,publication-coar-types:c_ba08 onebox - false - The ISBN of the reviewed item if it was not found in the system - publication-coar-types:c_efa0,publication-coar-types:c_ba08 + @@ -969,12 +1017,12 @@ dc coverage doi + false + publication-coar-types:c_efa0,publication-coar-types:c_ba08 onebox - false - The DOI of the reviewed item if it was not found in the system - publication-coar-types:c_efa0,publication-coar-types:c_ba08 + @@ -982,11 +1030,11 @@ dc description sponsorship + true onebox - true - Enter the name of any sponsors. + @@ -994,11 +1042,11 @@ oaire citation volume + false onebox - false - If applicable, the volume of the publishing channel where this publication appeared + @@ -1006,11 +1054,11 @@ oaire citation issue + false onebox - false - If applicable, the issue of the publishing channel where this publication appeared + @@ -1018,11 +1066,11 @@ oaire citation startPage + false onebox - false - If applicable, the page where this publication starts + @@ -1030,11 +1078,11 @@ oaire citation endPage + false onebox - false - If applicable, the page where this publication ends + @@ -1044,11 +1092,11 @@ dc relation funding + true group - true - Acknowledge the funding received for this publication. + @@ -1056,11 +1104,11 @@ dc relation project + true onebox - true - Enter the name of project, if any, that has produced this publication. It is NOT necessary to list the projects connected with an acknowledge funding. + @@ -1068,11 +1116,11 @@ dc relation conference + true onebox - true - Enter the name of the conference where the item has been presented, if any. + @@ -1080,11 +1128,11 @@ dc relation product + true onebox - true - Link the item to one or more existent dataset in the repository used or described by the publication or, put here the dataset citation + @@ -1092,22 +1140,22 @@ dc identifier citation + false onebox - false - Enter the standard citation for the previously issued instance of this item. + dc description + false textarea - false - Enter any other description or comments in this box. + @@ -1115,11 +1163,11 @@ dc description sponsorship + true onebox - true - Enter the name of any sponsors. + @@ -1128,23 +1176,23 @@ dc identifier + true qualdrop_value - true - If the item has any identification numbers or codes associated with it, please enter the types and the actual numbers or codes. + dc title + false onebox - false - You must enter a main title for this item. Enter the main title of the item. + You must enter a main title for this item. @@ -1152,11 +1200,11 @@ dc title alternative + true onebox - true - If the item has any alternative titles, please enter them here. + @@ -1164,11 +1212,11 @@ dc date issued + false date - false - You must enter at least the year. Please give the date of previous publication or public distribution. You can leave out the day and/or month if they aren't applicable. + You must enter at least the year. @@ -1176,11 +1224,11 @@ dc description version + false onebox - false - If applicable, the version of the product + @@ -1188,22 +1236,22 @@ dc contributor author + true group - true - Enter the names of the authors of this item. + dc type + false onebox - false - Nothing to do here. Note for administrators, this metadata could be completely hide using template item + product-coar-types @@ -1214,22 +1262,22 @@ dc language iso + false dropdown - false - Select, if applicable, the language of the main content of the item. If the language does not appear in the list, please select 'Other'. If the content does not really have a language (for example, if it is a dataset or an image) please select 'N/A'. + dc subject + true tag - true - Enter appropriate subject keywords or phrases. + @@ -1237,11 +1285,11 @@ datacite subject fos + true onebox - true - + oecd @@ -1250,11 +1298,11 @@ dc description abstract + false textarea - false - Enter the abstract of the item. + @@ -1263,11 +1311,11 @@ dc publisher + true onebox - true - The publisher or publishers of this product + @@ -1275,11 +1323,11 @@ dc relation ispartofseries + true series - true - Link to the research output of which this product is a part (e.g. a data set collection that contains it). + @@ -1287,11 +1335,11 @@ dc relation issn + false onebox - false - The journal or Serie ISSN if it was not found in the system + @@ -1299,11 +1347,11 @@ dc relation funding + true group - true - Acknowledge the funding received for this product. + @@ -1311,11 +1359,11 @@ dc relation project - - onebox true - + + onebox Enter the name of project, if any, that has produced this product. It is NOT necessary to list the projects connected with an acknowledge funding. + @@ -1323,11 +1371,11 @@ dc relation conference + false onebox - false - The event where this product was presented or that is recorded in the product. + @@ -1335,11 +1383,11 @@ dc relation equipment + true onebox - true - The equipment that generated this product + @@ -1347,11 +1395,11 @@ dc relation references + true onebox - true - Result outputs that are referenced by this product + @@ -1359,11 +1407,11 @@ dc relation publication + true onebox - true - Result outputs that use this product + @@ -1373,66 +1421,73 @@ dc identifier patentno + false onebox - false - The patent number + + + crispatent + kind + + onebox + false + + The kind code + dc identifier applicationnumber + false onebox - false - The application number + - dcterms - dateSubmitted - + dc + date + issued + date - false - Date on which the application was physically received at the Patent Authority. Also named Filling Date + dc title + false onebox - false - You must specify a title for the patent The title of the patent + You must specify a title for the patent dcterms dateAccepted + false date - false - Date on which the application has been granted by the Patent Office. + - dc - date - issued - + dcterms + dateSubmitted + date - false - Date of making available to the public by printing or similar process of a patent document on which grant has taken place on or before the said date + @@ -1440,47 +1495,59 @@ dc contributor author + true group - true - The inventor: The actual devisor of an invention that is the subject of a patent. + dcterms rightsHolder + true onebox - true - The holders of this patent + dc publisher + true onebox - true - The issuer of the patent: the patent office + dc type + false onebox - false - You must select a patent type Select the type of content of the patent. + You must select a patent type patent-coar-types + + + crispatent + document + kind + + inline-group + true + + + +
@@ -1488,22 +1555,22 @@ dc language iso + false dropdown - false - Select the country and its language. + dc subject + true onebox - true - Enter appropriate subject keywords or phrases. + @@ -1511,11 +1578,11 @@ dc description abstract + false textarea - false - Enter the description of the patent. +
@@ -1525,11 +1592,11 @@ dc relation funding + true group - true - Acknowledge the funding received for this patent. + @@ -1537,11 +1604,11 @@ dc relation project + true onebox - true - Enter the name of project, if any, that has produced this patent. It is NOT necessary to list the projects connected with an acknowledge funding. + @@ -1549,11 +1616,11 @@ dc relation patent + true onebox - true - Patents that precede (i.e., have priority over) this patent + @@ -1561,11 +1628,11 @@ dc relation references + true onebox - true - Result outputs that are referenced by this patent + @@ -1574,22 +1641,22 @@ dc title + false name - false - You must enter least at the Surname. + You must enter least at the Surname. crisrp name + false name - false - + @@ -1597,11 +1664,11 @@ crisrp name translated + false name - false - + @@ -1609,84 +1676,84 @@ crisrp name variant + true name - true - + person givenName + false onebox - false - + person familyName + false onebox - false - + person birthDate + false date - false - + oairecerif person gender + false dropdown - false - + person jobTitle + false onebox - false - + person affiliation name + false onebox - false - + crisrp workgroup + true onebox - true - + @@ -1694,33 +1761,33 @@ oairecerif identifier url + true group - true - + person email + false onebox - false - + dc subject + true tag - true - + @@ -1728,11 +1795,11 @@ datacite subject fos + true onebox - true - + oecd @@ -1741,11 +1808,11 @@ person identifier orcid + false onebox - false - Settable by connecting the entity with ORCID + all @@ -1754,11 +1821,11 @@ person identifier scopus-author-id + true onebox - true - + @@ -1766,11 +1833,11 @@ person identifier rid + true onebox - true - + @@ -1778,11 +1845,11 @@ oairecerif person affiliation + true inline-group - true - + @@ -1790,55 +1857,55 @@ dc description abstract + false textarea - false - + crisrp education + true inline-group - true - + crisrp country + false dropdown - false - + crisrp qualification + true inline-group - true - + person knowsLanguage + true dropdown - true - + @@ -1846,11 +1913,11 @@ cris policy eperson + false onebox - false - + @@ -1858,11 +1925,11 @@ cris policy group + false onebox - false - + @@ -1871,77 +1938,77 @@ dc title + false onebox - false - You must enter the oganization name. + You must enter the oganization name. oairecerif acronym + false onebox - false - + organization parentOrganization + false onebox - false - + crisou director + false onebox - false - + organization foundingDate + false date - false - + crisou boards + true onebox - true - + organization identifier + true qualdrop_value - true - + @@ -1949,11 +2016,11 @@ oairecerif identifier url + true onebox - true - + @@ -1961,11 +2028,11 @@ dc description abstract + false textarea - false - + @@ -1973,32 +2040,32 @@ organization address addressLocality + false onebox - false - + organization address addressCountry + false dropdown - false - + dc type + false dropdown - false - You must specify the organisation type + You must specify the organisation type @@ -2007,88 +2074,88 @@ dc title + false onebox - false - You must enter the project name. + You must enter the project name. oairecerif acronym + false onebox - false - + crispj coordinator + true onebox - true - + oairecerif internalid + false onebox - false - + crispj partnerou + true onebox - true - + crispj investigator + false onebox - false - You must enter the project coordinator. + You must enter the project coordinator. crispj openaireid + false onebox - false - + crispj organization + true onebox - true - + @@ -2096,32 +2163,32 @@ oairecerif identifier url + true onebox - true - + oairecerif oamandate + false dropdown - false - + oairecerif oamandate url + false onebox - false - + @@ -2129,21 +2196,21 @@ oairecerif project startDate + false date - false - + oairecerif project endDate + false date - false - + @@ -2151,22 +2218,22 @@ oairecerif project status + false onebox - false - + dc type + false dropdown - false - + @@ -2174,33 +2241,33 @@ dc description abstract + false textarea - false - + crispj coinvestigators + true onebox - true - + dc subject + true tag - true - + @@ -2208,11 +2275,11 @@ datacite subject fos + true onebox - true - + oecd @@ -2221,11 +2288,11 @@ dc relation equipment + true onebox - true - + @@ -2234,31 +2301,31 @@ dc title + false onebox - false - You must enter the equipment name. + You must enter the equipment name. oairecerif acronym + false onebox - false - + oairecerif internalid + false onebox - false - + @@ -2266,31 +2333,31 @@ dc relation project + false onebox - false - + oairecerif funder + false onebox - false - + oairecerif fundingParent + false onebox - false - Link this funding with its upper level if applicable + @@ -2298,53 +2365,53 @@ crisfund award url + false onebox - false - The url preferably on the funder website of the award notice + The url preferably on the funder website of the award notice oairecerif oamandate + false dropdown - false - + oairecerif oamandate url + false onebox - false - + oairecerif amount + false onebox - false - + oairecerif amount currency + false dropdown - false - + @@ -2352,97 +2419,97 @@ oairecerif funding identifier + false onebox - false - + oairecerif funding startDate + false date - false - + oairecerif funding endDate + false date - false - + dc type + false dropdown - false - + dc description + false textarea - false - + crisfund investigators + true onebox - true - + crisfund coinvestigators + true onebox - true - + crisfund leadorganizations + true onebox - true - + crisfund leadcoorganizations + true onebox - true - + @@ -2451,66 +2518,66 @@ dc title + false onebox - false - You must enter the equipment name. + You must enter the equipment name. oairecerif acronym + false onebox - false - + oairecerif internalid + false onebox - false - + crisequipment ownerou + false onebox - false - + crisequipment ownerrp + false onebox - false - + dc description + false textarea - false - + @@ -2519,33 +2586,33 @@ dc title + false onebox - false - + oairecerif acronym + false onebox - false - + dc type + false dropdown - false - + @@ -2553,21 +2620,21 @@ oairecerif event startDate + false date - false - + oairecerif event endDate + false date - false - + @@ -2575,11 +2642,11 @@ oairecerif event place + false onebox - false - + @@ -2587,77 +2654,77 @@ oairecerif event country + false dropdown - false - + crisevent organizerou + true onebox - true - + crisevent organizerpj + true onebox - true - + crisevent sponsorou + true onebox - true - + crisevent sponsorpj + true onebox - true - + crisevent partnerou + true onebox - true - + crisevent partnerpj + true onebox - true - + @@ -2665,22 +2732,22 @@ dc description abstract + false textarea - false - + dc subject + true tag - true - + @@ -2689,11 +2756,11 @@ cris owner + false onebox - false - + @@ -4365,4 +4432,3 @@ - diff --git a/dspace/etc/migration/relations_migrations.ktr b/dspace/etc/migration/relations_migrations.ktr index e2bc269baecc..5b9987492b1b 100644 --- a/dspace/etc/migration/relations_migrations.ktr +++ b/dspace/etc/migration/relations_migrations.ktr @@ -1004,6 +1004,14 @@ WHERE label = ? left_place left_place + + leftward_value + leftward_type + + + rightward_value + rightward_type + diff --git a/dspace/modules/additions/pom.xml b/dspace/modules/additions/pom.xml index 017850cc76ad..63d68c380bf0 100644 --- a/dspace/modules/additions/pom.xml +++ b/dspace/modules/additions/pom.xml @@ -17,7 +17,7 @@ org.dspace modules - cris-2023.01.00-SNAPSHOT + cris-2023.01.01-SNAPSHOT .. @@ -334,13 +334,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 c0d4078e5a75..6c2fd62d85e6 100644 --- a/dspace/modules/pom.xml +++ b/dspace/modules/pom.xml @@ -11,7 +11,7 @@ org.dspace dspace-parent - cris-2023.01.00-SNAPSHOT + cris-2023.01.01-SNAPSHOT ../../pom.xml diff --git a/dspace/modules/rest/pom.xml b/dspace/modules/rest/pom.xml index 73ead2c3911b..eb5975f6323f 100644 --- a/dspace/modules/rest/pom.xml +++ b/dspace/modules/rest/pom.xml @@ -13,7 +13,7 @@ org.dspace modules - cris-2023.01.00-SNAPSHOT + cris-2023.01.01-SNAPSHOT .. diff --git a/dspace/modules/server/pom.xml b/dspace/modules/server/pom.xml index 2d8e4bb53746..08ad7961e48c 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 - cris-2023.01.00-SNAPSHOT + cris-2023.01.01-SNAPSHOT .. @@ -336,13 +336,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 8bc4a76e29e1..e943a66aee09 100644 --- a/dspace/pom.xml +++ b/dspace/pom.xml @@ -16,7 +16,7 @@ org.dspace dspace-parent - cris-2023.01.00-SNAPSHOT + cris-2023.01.01-SNAPSHOT ../pom.xml diff --git a/dspace/solr/authority/conf/solrconfig.xml b/dspace/solr/authority/conf/solrconfig.xml index 373cbb661c6a..21f917ebf8ca 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 02789fa3c2a4..ce8d9ebe2060 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 4ddae3d4a205..5f0149b8bd47 100644 --- a/dspace/solr/search/conf/schema.xml +++ b/dspace/solr/search/conf/schema.xml @@ -280,6 +280,9 @@ + + + @@ -316,6 +319,9 @@ + + diff --git a/dspace/solr/search/conf/solrconfig.xml b/dspace/solr/search/conf/solrconfig.xml index 1ecb8ca15d7a..3da73878ad8a 100644 --- a/dspace/solr/search/conf/solrconfig.xml +++ b/dspace/solr/search/conf/solrconfig.xml @@ -101,6 +101,7 @@ 200 false 2 + 1000 diff --git a/dspace/solr/statistics/conf/solrconfig.xml b/dspace/solr/statistics/conf/solrconfig.xml index 9a6c16146243..2b1cff45373d 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/pom.xml b/pom.xml index 2eac90f0207c..b25632a959ff 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ org.dspace dspace-parent pom - cris-2023.01.00-SNAPSHOT + cris-2023.01.01-SNAPSHOT DSpace Parent Project DSpace-CRIS is an open source extension of DSpace (http://www.dspace.org) providing out of box support for the CRIS / RIMS and moder Institution Repository use cases with advanced features and optimized configurations @@ -24,14 +24,14 @@ 5.6.5 5.6.5.Final 6.0.23.Final - 42.4.1 + 42.4.3 8.11.1 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 @@ -39,7 +39,7 @@ 9.4.48.v20220622 2.17.1 - 2.0.24 + 2.0.27 1.18.0 1.7.25 2.3.0 @@ -958,14 +958,14 @@ org.dspace dspace-rest - cris-2023.01.00-SNAPSHOT + cris-2023.01.01-SNAPSHOT jar classes org.dspace dspace-rest - cris-2023.01.00-SNAPSHOT + cris-2023.01.01-SNAPSHOT war @@ -1116,69 +1116,69 @@ org.dspace dspace-api - cris-2023.01.00-SNAPSHOT + cris-2023.01.01-SNAPSHOT org.dspace dspace-api test-jar - cris-2023.01.00-SNAPSHOT + cris-2023.01.01-SNAPSHOT test org.dspace.modules additions - cris-2023.01.00-SNAPSHOT + cris-2023.01.01-SNAPSHOT org.dspace dspace-sword - cris-2023.01.00-SNAPSHOT + cris-2023.01.01-SNAPSHOT org.dspace dspace-swordv2 - cris-2023.01.00-SNAPSHOT + cris-2023.01.01-SNAPSHOT org.dspace dspace-oai - cris-2023.01.00-SNAPSHOT + cris-2023.01.01-SNAPSHOT org.dspace dspace-services - cris-2023.01.00-SNAPSHOT + cris-2023.01.01-SNAPSHOT org.dspace dspace-server-webapp test-jar - cris-2023.01.00-SNAPSHOT + cris-2023.01.01-SNAPSHOT test org.dspace dspace-rdf - cris-2023.01.00-SNAPSHOT + cris-2023.01.01-SNAPSHOT org.dspace dspace-iiif - cris-2023.01.00-SNAPSHOT + cris-2023.01.01-SNAPSHOT org.dspace dspace-server-webapp - cris-2023.01.00-SNAPSHOT + cris-2023.01.01-SNAPSHOT jar classes org.dspace dspace-server-webapp - cris-2023.01.00-SNAPSHOT + cris-2023.01.01-SNAPSHOT war @@ -1571,7 +1571,7 @@ org.apache.commons commons-dbcp2 - 2.8.0 + 2.9.0 commons-fileupload @@ -1598,7 +1598,12 @@ org.apache.commons commons-pool2 - 2.9.0 + 2.11.1 + + + org.apache.commons + commons-text + 1.10.0 commons-validator