diff --git a/.codecov.yml b/.codecov.yml
new file mode 100644
index 0000000000..3dba42ef37
--- /dev/null
+++ b/.codecov.yml
@@ -0,0 +1,29 @@
+# DSpace configuration for Codecov.io coverage reports
+# These override the default YAML settings at
+# https://docs.codecov.io/docs/codecov-yaml#section-default-yaml
+# Can be validated via instructions at:
+# https://docs.codecov.io/docs/codecov-yaml#validate-your-repository-yaml
+
+# Settings related to code coverage analysis
+coverage:
+ status:
+ # Configuration for project-level checks. This checks how the PR changes overall coverage.
+ project:
+ default:
+ # For each PR, auto compare coverage to previous commit.
+ # Require that overall (project) coverage does NOT drop more than 0.5%
+ target: auto
+ threshold: 0.5%
+ # Configuration for patch-level checks. This checks the relative coverage of the new PR code ONLY.
+ patch:
+ default:
+ # For each PR, make sure the coverage of the new code is within 1% of current overall coverage.
+ # We let 'patch' be more lenient as we only require *project* coverage to not drop significantly.
+ target: auto
+ threshold: 1%
+
+# Turn PR comments "off". This feature adds the code coverage summary as a
+# comment on each PR. See https://docs.codecov.io/docs/pull-request-comments
+# However, this same info is available from the Codecov checks in the PR's
+# "Checks" tab in GitHub. So, the comment is unnecessary.
+comment: false
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 0000000000..9893d233e1
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,22 @@
+---
+name: Bug report
+about: Create a report to help us improve
+title: ''
+labels: bug, needs triage
+assignees: ''
+
+---
+
+**Describe the bug**
+A clear and concise description of what the bug is. Include the version(s) of DSpace where you've seen this problem. Link to examples if they are public.
+
+**To Reproduce**
+Steps to reproduce the behavior:
+1. Do this
+2. Then this...
+
+**Expected behavior**
+A clear and concise description of what you expected to happen.
+
+**Related work**
+Link to any related tickets or PRs here.
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 0000000000..34cc2c9e4f
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,20 @@
+---
+name: Feature request
+about: Suggest a new feature for this project
+title: ''
+labels: new feature, needs triage
+assignees: ''
+
+---
+
+**Is your feature request related to a problem? Please describe.**
+A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
+
+**Describe the solution you'd like**
+A clear and concise description of what you want to happen.
+
+**Describe alternatives or workarounds you've considered**
+A clear and concise description of any alternative solutions or features you've considered.
+
+**Additional context**
+Add any other context or screenshots about the feature request here.
diff --git a/.github/disabled-workflows/pull_request_opened.yml b/.github/disabled-workflows/pull_request_opened.yml
new file mode 100644
index 0000000000..0dc718c0b9
--- /dev/null
+++ b/.github/disabled-workflows/pull_request_opened.yml
@@ -0,0 +1,26 @@
+# This workflow runs whenever a new pull request is created
+# TEMPORARILY DISABLED. Unfortunately this doesn't work for PRs created from forked repositories (which is how we tend to create PRs).
+# There is no known workaround yet. See https://github.community/t/how-to-use-github-token-for-prs-from-forks/16818
+name: Pull Request opened
+
+# Only run for newly opened PRs against the "main" branch
+on:
+ pull_request:
+ types: [opened]
+ branches:
+ - main
+
+jobs:
+ automation:
+ runs-on: ubuntu-latest
+ steps:
+ # Assign the PR to whomever created it. This is useful for visualizing assignments on project boards
+ # See https://github.com/marketplace/actions/pull-request-assigner
+ - name: Assign PR to creator
+ uses: thomaseizinger/assign-pr-creator-action@v1.0.0
+ # Note, this authentication token is created automatically
+ # See: https://docs.github.com/en/actions/configuring-and-managing-workflows/authenticating-with-the-github_token
+ with:
+ repo-token: ${{ secrets.GITHUB_TOKEN }}
+ # Ignore errors. It is possible the PR was created by someone who cannot be assigned
+ continue-on-error: true
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index 849fbf93be..b11e3cd531 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -1,8 +1,7 @@
## References
-_Add references/links to any related tickets or PRs. These may include:_
-* Link to [JIRA](https://jira.lyrasis.org/projects/DS/summary) ticket(s), if any
-* Link to [REST Contract](https://github.com/DSpace/Rest7Contract) or an open REST Contract PR, if any
-* Link to [Angular issue or PR](https://github.com/DSpace/dspace-angular/issues) related to this PR, if any
+_Add references/links to any related issues or PRs. These may include:_
+* Fixes #[issue-number]
+* Related to [REST Contract](https://github.com/DSpace/Rest7Contract)
## Description
Short summary of changes (1-2 sentences).
@@ -20,11 +19,8 @@ List of changes in this PR:
_This checklist provides a reminder of what we are going to look for when reviewing your PR. You need not complete this checklist prior to creating your PR (draft PRs are always welcome). If you are unsure about an item in the checklist, don't hesitate to ask. We're here to help!_
- [ ] My PR is small in size (e.g. less than 1,000 lines of code, not including comments & integration tests). Exceptions may be made if previously agreed upon.
-- [ ] My PR passes Checkstyle validation based on the [Code Style Guide](https://wiki.lyrasis.org/display/DSPACE/Code+Style+Guide)
+- [ ] My PR 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 for any bug fixes, improvements or new features. A few reminders about what constitutes good tests:
- * Include tests for different user types, including: (1) Anonymous user, (2) Logged in user (non-admin), and (3) Administrator.
- * Include tests for known error scenarios and error codes (e.g. `400 Bad Request`, `401 Unauthorized`, `403 Forbidden`, `404 Not Found`, etc)
- * For bug fixes, include a test that reproduces the bug and proves it is fixed. For clarity, it may be useful to provide the test in a separate commit from the bug fix.
-- [ ] 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/master/LICENSE) based on the [Licensing of Contributions](https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines#CodeContributionGuidelines-LicensingofContributions) documentation.
+- [ ] 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.
diff --git a/.github/workflows/issue_opened.yml b/.github/workflows/issue_opened.yml
new file mode 100644
index 0000000000..3ccdd22a0d
--- /dev/null
+++ b/.github/workflows/issue_opened.yml
@@ -0,0 +1,29 @@
+# This workflow runs whenever a new issue is created
+name: Issue opened
+
+on:
+ issues:
+ types: [opened]
+
+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
+ # 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
+ # 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
+ # 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
diff --git a/.github/workflows/label_merge_conflicts.yml b/.github/workflows/label_merge_conflicts.yml
new file mode 100644
index 0000000000..dcbab18f1b
--- /dev/null
+++ b/.github/workflows/label_merge_conflicts.yml
@@ -0,0 +1,25 @@
+# This workflow checks open PRs for merge conflicts and labels them when conflicts are found
+name: Check for merge conflicts
+
+# Run whenever the "main" branch is updated
+# NOTE: This means merge conflicts are only checked for when a PR is merged to main.
+on:
+ push:
+ branches:
+ - main
+
+jobs:
+ triage:
+ runs-on: ubuntu-latest
+ steps:
+ # See: https://github.com/mschilde/auto-label-merge-conflicts/
+ - name: Auto-label PRs with merge conflicts
+ uses: mschilde/auto-label-merge-conflicts@v2.0
+ # 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
diff --git a/.lgtm.yml b/.lgtm.yml
new file mode 100644
index 0000000000..132de8a6de
--- /dev/null
+++ b/.lgtm.yml
@@ -0,0 +1,9 @@
+# LGTM Settings (https://lgtm.com/)
+# For reference, see https://lgtm.com/help/lgtm/lgtm.yml-configuration-file
+# or template at https://lgtm.com/static/downloads/lgtm.template.yml
+
+extraction:
+ java:
+ index:
+ # Specify the Java version required to build the project
+ java_version: 11
diff --git a/.travis.yml b/.travis.yml
index dfc4c31799..89cb443597 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,46 +1,55 @@
+# DSpace's Travis CI Configuration
+# Builds: https://travis-ci.com/github/DSpace/DSpace
+# Travis configuration guide/validation: https://config.travis-ci.com/explore
language: java
-sudo: false
+# TODO: Upgrade to Bionic
dist: trusty
-
-env:
- # Give Maven 1GB of memory to work with
- - MAVEN_OPTS=-Xmx1024M
+os: linux
jdk:
# DS-3384 Oracle JDK has DocLint enabled by default.
# Let's use this to catch any newly introduced DocLint issues.
- oraclejdk11
-## Should we run into any problems with oraclejdk8 on Travis, we may try the following workaround.
-## https://docs.travis-ci.com/user/languages/java#Testing-Against-Multiple-JDKs
-## https://github.com/travis-ci/travis-ci/issues/3259#issuecomment-130860338
-#addons:
-# apt:
-# packages:
-# - oracle-java8-installer
+# Define global environment variables (shared across all jobs)
+env:
+ global:
+ # Suppress all Maven "downloading" messages in Travis logs (see https://stackoverflow.com/a/35653426)
+ # This also slightly speeds builds in Travis, as there is less logging
+ - HIDE_MAVEN_DOWNLOADS="-Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn"
+ # Give Maven 1GB of memory to work with
+ - MAVEN_OPTS="-Xmx1024M $HIDE_MAVEN_DOWNLOADS"
+ # Maven options which will skip ALL code validation checks. Includes skipping:
+ # - enforcer.skip => Skip maven-enforcer-plugin rules
+ # - checkstyle.skip => Skip all checkstyle checks by maven-checkstyle-plugin
+ # - license.skip => Skip all license header checks by license-maven-plugin
+ # - xml.skip => Skip all XML/XSLT validation by xml-maven-plugin
+ # (Useful for builds which don't need to repeat code checks)
+ - SKIP_CODE_CHECKS="-Denforcer.skip=true -Dcheckstyle.skip=true -Dlicense.skip=true -Dxml.skip=true"
-before_install:
- # Remove outdated settings.xml from Travis builds. Workaround for https://github.com/travis-ci/travis-ci/issues/4629
- - rm ~/.m2/settings.xml
+# Create two jobs to run Unit & Integration tests in parallel.
+# These jobs only differ in the TEST_FLAGS defined below,
+# and otherwise share all the other configs in this file
+jobs:
+ include:
+ - name: "Run Unit Tests & Check Code"
+ # NOTE: unit tests include deprecated REST API v6 (as it has unit tests)
+ env: TEST_FLAGS="-DskipUnitTests=false -Pdspace-rest"
+ - name: "Run Integration Tests"
+ # NOTE: skips code checks, as they are already done by Unit Test job
+ env: TEST_FLAGS="-DskipIntegrationTests=false $SKIP_CODE_CHECKS"
-# Skip install stage, as we'll do it below
-install: "echo 'Skipping install stage, dependencies will be downloaded during build and test stages.'"
+# Skip 'install' process to save time. We build/install/test all at once in "script" below.
+install: skip
-# Build DSpace and run both Unit and Integration Tests
-script:
- # Summary of flags used (below):
- # license:check => Validate all source code license headers
- # -Dmaven.test.skip=false => Enable DSpace Unit Tests
- # -DskipITs=false => Enable DSpace Integration Tests
- # -Pdspace-rest => Enable optional dspace-rest module as part of build
- # -P !assembly => Skip assembly of "dspace-installer" directory (as it can be memory intensive)
- # -B => Maven batch/non-interactive mode (recommended for CI)
- # -V => Display Maven version info before build
- # -Dsurefire.rerunFailingTestsCount=2 => try again for flakey tests, and keep track of/report on number of retries
- - "mvn clean install license:check -Dmaven.test.skip=false -DskipITs=false -Pdspace-rest -P !assembly -B -V -Dsurefire.rerunFailingTestsCount=2"
+# Build DSpace and run configured tests (see 'jobs' above)
+# Notes on flags used:
+# -B => Maven batch/non-interactive mode (recommended for CI)
+# -V => Display Maven version info before build
+# -P-assembly => Disable build of dspace-installer in [src]/dspace/, as it can be memory intensive
+# -Pcoverage-report => Enable aggregate code coverage report (across all modules) via JaCoCo
+script: mvn install -B -V -P-assembly -Pcoverage-report $TEST_FLAGS
-# After a successful build and test (see 'script'), send code coverage reports to coveralls.io
-# These code coverage reports are generated by jacoco-maven-plugin (during test process above).
-after_success:
- # Run "verify", enabling the "coveralls" profile. This sends our reports to coveralls.io (see coveralls-maven-plugin)
- - "cd dspace && mvn verify -P coveralls"
+# After a successful build and test (see 'script'), send aggregate code coverage reports
+# (generated by -Pcoverage-report above) to CodeCov.io
+after_success: bash <(curl -s https://codecov.io/bash)
diff --git a/Dockerfile b/Dockerfile
index 006f32f28e..2dc3ee9bda 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,5 +1,5 @@
# This image will be published as dspace/dspace
-# See https://github.com/DSpace/DSpace/tree/master/dspace/src/main/docker for usage details
+# See https://github.com/DSpace/DSpace/tree/main/dspace/src/main/docker for usage details
#
# This version is JDK11 compatible
# - tomcat:8-jdk11
diff --git a/Dockerfile.cli b/Dockerfile.cli
index 116b251f2d..d4204ebdd0 100644
--- a/Dockerfile.cli
+++ b/Dockerfile.cli
@@ -1,5 +1,5 @@
# This image will be published as dspace/dspace-cli
-# See https://github.com/DSpace/DSpace/tree/master/dspace/src/main/docker for usage details
+# See https://github.com/DSpace/DSpace/tree/main/dspace/src/main/docker for usage details
#
# This version is JDK11 compatible
# - openjdk:11
diff --git a/Dockerfile.test b/Dockerfile.test
index 090f714e28..82ffdef177 100644
--- a/Dockerfile.test
+++ b/Dockerfile.test
@@ -1,5 +1,5 @@
# This image will be published as dspace/dspace
-# See https://github.com/DSpace/DSpace/tree/master/dspace/src/main/docker for usage details
+# See https://github.com/DSpace/DSpace/tree/main/dspace/src/main/docker for usage details
#
# This version is JDK11 compatible
# - tomcat:8-jdk11
diff --git a/README.md b/README.md
index 0e701bf140..2e6c0ad54e 100644
--- a/README.md
+++ b/README.md
@@ -1,24 +1,24 @@
# DSpace
-[](https://travis-ci.org/DSpace/DSpace)
+[](https://travis-ci.com/DSpace/DSpace)
-[DSpace Documentation](https://wiki.duraspace.org/display/DSDOC/) |
+[DSpace Documentation](https://wiki.lyrasis.org/display/DSDOC/) |
[DSpace Releases](https://github.com/DSpace/DSpace/releases) |
-[DSpace Wiki](https://wiki.duraspace.org/display/DSPACE/Home) |
-[Support](https://wiki.duraspace.org/display/DSPACE/Support)
+[DSpace Wiki](https://wiki.lyrasis.org/display/DSPACE/Home) |
+[Support](https://wiki.lyrasis.org/display/DSPACE/Support)
DSpace open source software is a turnkey repository application used by more than
2,000 organizations and institutions worldwide to provide durable access to digital resources.
For more information, visit http://www.dspace.org/
***
-:warning: **Work on DSpace 7 has begun on our `master` branch.** This means that there is temporarily NO user interface on this `master` branch. DSpace 7 will feature a new, unified [Angular](https://angular.io/) user interface, along with an enhanced, rebuilt REST API. The latest status of this work can be found on the [DSpace 7 UI Working Group](https://wiki.duraspace.org/display/DSPACE/DSpace+7+UI+Working+Group) page. Additionally, the codebases can be found in the following places:
- * DSpace 7 REST API work is occurring on the [`master` branch](https://github.com/DSpace/DSpace/tree/master/dspace-server-webapp) of this repository.
- * The REST Contract is being documented at https://github.com/DSpace/Rest7Contract
+:warning: **Work on DSpace 7 has begun on our `main` branch.** This means that there is NO user interface on this `main` branch. DSpace 7 will feature a new, unified [Angular](https://angular.io/) user interface, along with an enhanced, rebuilt REST API. The latest status of this work can be found on the [DSpace 7 Working Group](https://wiki.lyrasis.org/display/DSPACE/DSpace+7+Working+Group) page. Additionally, the codebases can be found in the following places:
+ * DSpace 7 REST API work is occurring on the [`main` branch](https://github.com/DSpace/DSpace/tree/main/dspace-server-webapp) of this repository.
+ * The REST Contract is at https://github.com/DSpace/Rest7Contract
* DSpace 7 Angular UI work is occurring at https://github.com/DSpace/dspace-angular
-**If you would like to get involved in our DSpace 7 development effort, we welcome new contributors.** Just join one of our meetings or get in touch via Slack. See the [DSpace 7 UI Working Group](https://wiki.duraspace.org/display/DSPACE/DSpace+7+UI+Working+Group) wiki page for more info.
+**If you would like to get involved in our DSpace 7 development effort, we welcome new contributors.** Just join one of our meetings or get in touch via Slack. See the [DSpace 7 Working Group](https://wiki.lyrasis.org/display/DSPACE/DSpace+7+Working+Group) wiki page for more info.
**If you are looking for the ongoing maintenance work for DSpace 6 (or prior releases)**, you can find that work on the corresponding maintenance branch (e.g. [`dspace-6_x`](https://github.com/DSpace/DSpace/tree/dspace-6_x)) in this repository.
***
@@ -31,10 +31,10 @@ Past releases are all available via GitHub at https://github.com/DSpace/DSpace/r
## Documentation / Installation
-Documentation for each release may be viewed online or downloaded via our [Documentation Wiki](https://wiki.duraspace.org/display/DSDOC/).
+Documentation for each release may be viewed online or downloaded via our [Documentation Wiki](https://wiki.lyrasis.org/display/DSDOC/).
The latest DSpace Installation instructions are available at:
-https://wiki.duraspace.org/display/DSDOC6x/Installing+DSpace
+https://wiki.lyrasis.org/display/DSDOC6x/Installing+DSpace
Please be aware that, as a Java web application, DSpace requires a database (PostgreSQL or Oracle)
and a servlet container (usually Tomcat) in order to function.
@@ -49,14 +49,14 @@ DSpace is a community built and supported project. We do not have a centralized
but have a dedicated group of volunteers who help us improve the software, documentation, resources, etc.
We welcome contributions of any type. Here's a few basic guides that provide suggestions for contributing to DSpace:
-* [How to Contribute to DSpace](https://wiki.duraspace.org/display/DSPACE/How+to+Contribute+to+DSpace): How to contribute in general (via code, documentation, bug reports, expertise, etc)
-* [Code Contribution Guidelines](https://wiki.duraspace.org/display/DSPACE/Code+Contribution+Guidelines): How to give back code or contribute features, bug fixes, etc.
-* [DSpace Community Advisory Team (DCAT)](https://wiki.duraspace.org/display/cmtygp/DSpace+Community+Advisory+Team): If you are not a developer, we also have an interest group specifically for repository managers. The DCAT group meets virtually, once a month, and sends open invitations to join their meetings via the [DCAT mailing list](https://groups.google.com/d/forum/DSpaceCommunityAdvisoryTeam).
+* [How to Contribute to DSpace](https://wiki.lyrasis.org/display/DSPACE/How+to+Contribute+to+DSpace): How to contribute in general (via code, documentation, bug reports, expertise, etc)
+* [Code Contribution Guidelines](https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines): How to give back code or contribute features, bug fixes, etc.
+* [DSpace Community Advisory Team (DCAT)](https://wiki.lyrasis.org/display/cmtygp/DSpace+Community+Advisory+Team): If you are not a developer, we also have an interest group specifically for repository managers. The DCAT group meets virtually, once a month, and sends open invitations to join their meetings via the [DCAT mailing list](https://groups.google.com/d/forum/DSpaceCommunityAdvisoryTeam).
-We also encourage GitHub Pull Requests (PRs) at any time. Please see our [Development with Git](https://wiki.duraspace.org/display/DSPACE/Development+with+Git) guide for more info.
+We also encourage GitHub Pull Requests (PRs) at any time. Please see our [Development with Git](https://wiki.lyrasis.org/display/DSPACE/Development+with+Git) guide for more info.
In addition, a listing of all known contributors to DSpace software can be
-found online at: https://wiki.duraspace.org/display/DSPACE/DSpaceContributors
+found online at: https://wiki.lyrasis.org/display/DSPACE/DSpaceContributors
## Getting Help
@@ -64,12 +64,12 @@ DSpace provides public mailing lists where you can post questions or raise topic
We welcome everyone to participate in these lists:
* [dspace-community@googlegroups.com](https://groups.google.com/d/forum/dspace-community) : General discussion about DSpace platform, announcements, sharing of best practices
-* [dspace-tech@googlegroups.com](https://groups.google.com/d/forum/dspace-tech) : Technical support mailing list. See also our guide for [How to troubleshoot an error](https://wiki.duraspace.org/display/DSPACE/Troubleshoot+an+error).
+* [dspace-tech@googlegroups.com](https://groups.google.com/d/forum/dspace-tech) : Technical support mailing list. See also our guide for [How to troubleshoot an error](https://wiki.lyrasis.org/display/DSPACE/Troubleshoot+an+error).
* [dspace-devel@googlegroups.com](https://groups.google.com/d/forum/dspace-devel) : Developers / Development mailing list
Great Q&A is also available under the [DSpace tag on Stackoverflow](http://stackoverflow.com/questions/tagged/dspace)
-Additional support options are listed at https://wiki.duraspace.org/display/DSPACE/Support
+Additional support options are at https://wiki.lyrasis.org/display/DSPACE/Support
DSpace also has an active service provider network. If you'd rather hire a service provider to
install, upgrade, customize or host DSpace, then we recommend getting in touch with one of our
@@ -77,47 +77,46 @@ install, upgrade, customize or host DSpace, then we recommend getting in touch w
## Issue Tracker
-The DSpace Issue Tracker can be found at: https://jira.duraspace.org/projects/DS/summary
+DSpace uses GitHub to track issues:
+* Backend (REST API) issues: https://github.com/DSpace/DSpace/issues
+* Frontend (User Interface) issues: https://github.com/DSpace/dspace-angular/issues
## Testing
### Running Tests
By default, in DSpace, Unit Tests and Integration Tests are disabled. However, they are
-run automatically by [Travis CI](https://travis-ci.org/DSpace/DSpace/) for all Pull Requests and code commits.
+run automatically by [Travis CI](https://travis-ci.com/DSpace/DSpace/) for all Pull Requests and code commits.
* How to run both Unit Tests (via `maven-surefire-plugin`) and Integration Tests (via `maven-failsafe-plugin`):
```
- # NOTE: while "mvn test" runs Unit Tests,
- # Integration Tests only run for "verify" or "install" phases
- mvn clean install -Dmaven.test.skip=false -DskipITs=false
+ mvn install -DskipUnitTests=false -DskipIntegrationTests=false
```
-* How to run just Unit Tests:
+* How to run _only_ Unit Tests:
```
- mvn clean test -Dmaven.test.skip=false
+ mvn test -DskipUnitTests=false
```
* How to run a *single* Unit Test
```
# Run all tests in a specific test class
- # NOTE: testClassName is just the class name, do not include package
- mvn clean test -Dmaven.test.skip=false -Dtest=[testClassName]
+ # NOTE: failIfNoTests=false is required to skip tests in other modules
+ mvn test -DskipUnitTests=false -Dtest=[full.package.testClassName] -DfailIfNoTests=false
# Run one test method in a specific test class
- mvn clean test -Dmaven.test.skip=false -Dtest=[testClassName]#[testMethodName]
+ mvn test -DskipUnitTests=false -Dtest=[full.package.testClassName]#[testMethodName] -DfailIfNoTests=false
```
-* How to run Integration Tests (requires running Unit tests too)
+* How to run _only_ Integration Tests
```
- mvn clean verify -Dmaven.test.skip=false -DskipITs=false
+ mvn install -DskipIntegrationTests=false
```
-* How to run a *single* Integration Test (requires running Unit tests too)
+* How to run a *single* Integration Test
```
# Run all integration tests in a specific test class
- # NOTE: Integration Tests only run for "verify" or "install" phases
- # NOTE: testClassName is just the class name, do not include package
- mvn clean verify -Dmaven.test.skip=false -DskipITs=false -Dit.test=[testClassName]
+ # NOTE: failIfNoTests=false is required to skip tests in other modules
+ mvn install -DskipIntegrationTests=false -Dit.test=[full.package.testClassName] -DfailIfNoTests=false
# Run one test method in a specific test class
- mvn clean verify -Dmaven.test.skip=false -DskipITs=false -Dit.test=[testClassName]#[testMethodName]
+ mvn install -DskipIntegrationTests=false -Dit.test=[full.package.testClassName]#[testMethodName] -DfailIfNoTests=false
```
* How to run only tests of a specific DSpace module
```
@@ -133,4 +132,4 @@ run automatically by [Travis CI](https://travis-ci.org/DSpace/DSpace/) for all P
## License
DSpace source code is freely available under a standard [BSD 3-Clause license](https://opensource.org/licenses/BSD-3-Clause).
-The full license is available at http://www.dspace.org/license/
+The full license is available in the [LICENSE](LICENSE) file or online at http://www.dspace.org/license/
diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml
index b016f1bff6..ced0f562bf 100644
--- a/dspace-api/pom.xml
+++ b/dspace-api/pom.xml
@@ -12,7 +12,7 @@
org.dspacedspace-parent
- 7.0-SNAPSHOT
+ 7.0-beta5-SNAPSHOT..
@@ -98,20 +98,6 @@
-
-
- com.mycila
- license-maven-plugin
-
-
- **/src/test/resources/**
- **/src/test/data/**
- **/.gitignore
- **/src/main/resources/rebel.xml
- src/test/data/dspaceFolder/config/spiders/**
-
-
- org.codehaus.mojo
@@ -141,44 +127,82 @@
+
+
+ org.codehaus.gmaven
+ groovy-maven-plugin
+
+
+ setproperty
+ initialize
+
+ execute
+
+
+
+ project.properties['agnostic.build.dir'] = project.build.directory.replace(File.separator, '/');
+ log.info("Initializing Maven property 'agnostic.build.dir' to: {}", project.properties['agnostic.build.dir']);
+
+
+
+
+
+
+
+ com.mycila
+ license-maven-plugin
+
+
+ src/test/resources/**
+ src/test/data/**
+
+ src/main/resources/org/dspace/storage/rdbms/flywayupgrade/**
+
+
+
+
- findbugs
+ spotbugsfalse
-
- org.codehaus.mojo
- findbugs-maven-plugin
+ com.github.spotbugs
+ spotbugs-maven-plugin
-
+
- test-environment
+ unit-test-environmentfalse
- maven.test.skip
+ skipUnitTestsfalse
-
@@ -198,53 +222,16 @@
- setupTestEnvironment
+ setupUnitTestEnvironmentgenerate-test-resourcesunpack
-
- setupIntegrationTestEnvironment
- pre-integration-test
-
- unpack
-
-
-
-
- org.codehaus.gmaven
- groovy-maven-plugin
-
-
- setproperty
- initialize
-
- execute
-
-
-
- project.properties['agnostic.build.dir'] = project.build.directory.replace(File.separator, '/');
- log.info("Initializing Maven property 'agnostic.build.dir' to: {}", project.properties['agnostic.build.dir']);
-
-
-
-
-
-
-
+
maven-surefire-plugin
@@ -255,11 +242,56 @@
${agnostic.build.dir}/testing/dspace/true
+ ${agnostic.build.dir}/testing/dspace/solr/
+
+
+
-
+
+
+ integration-test-environment
+
+ false
+
+ skipIntegrationTests
+ false
+
+
+
+
+
+
+ maven-dependency-plugin
+
+ ${project.build.directory}/testing
+
+
+ org.dspace
+ dspace-parent
+ ${project.version}
+ zip
+ testEnvironment
+
+
+
+
+
+ setupIntegrationTestEnvironment
+ pre-integration-test
+
+ unpack
+
+
+
+
+
+
maven-failsafe-plugin
@@ -269,12 +301,12 @@
${agnostic.build.dir}/testing/dspace/true
+ ${agnostic.build.dir}/testing/dspace/solr/
-
@@ -305,19 +337,25 @@
- org.dspace
+ net.handlehandle
- org.eclipse.jetty.aggregate
- jetty-all
+ net.cnri
+ cnri-servlet-container
+
- javax.servlet
- org.eclipse.jetty.orbit
+ org.ow2.asm
+ asm-commons
+
+
+ org.eclipse.jetty
+ jetty-server
+ org.dspacejargon
@@ -331,6 +369,18 @@
apache-jena-libspom
+
+
+
+ org.glassfish.jersey.inject
+ jersey-hk2
+ ${jersey.version}
+
+
+
+ commons-cli
+ commons-cli
+ commons-codeccommons-codec
@@ -487,9 +537,164 @@
org.apache.solr
- solr-cell
+ solr-solrj${solr.client.version}
+
+
+
+ org.apache.solr
+ solr-core
+ test
+ ${solr.client.version}
+
+
+ commons-cli
+ commons-cli
+
+
+ org.eclipse.jetty
+ jetty-continuation
+
+
+ org.eclipse.jetty
+ jetty-deploy
+
+
+ org.eclipse.jetty
+ jetty-http
+
+
+ org.eclipse.jetty
+ jetty-io
+
+
+ org.eclipse.jetty
+ jetty-jmx
+
+
+ org.eclipse.jetty
+ jetty-rewrite
+
+
+ org.eclipse.jetty
+ jetty-security
+
+
+ org.eclipse.jetty
+ jetty-server
+
+
+ org.eclipse.jetty
+ jetty-servlet
+
+
+ org.eclipse.jetty
+ jetty-servlets
+
+
+ org.eclipse.jetty
+ jetty-util
+
+
+ org.eclipse.jetty
+ jetty-webapp
+
+
+ org.eclipse.jetty
+ jetty-xml
+
+
+
+
+ org.apache.solr
+ solr-cell
+
+
+
+ commons-cli
+ commons-cli
+
+
+ org.ow2.asm
+ asm-commons
+
+
+ org.bouncycastle
+ bcpkix-jdk15on
+
+
+ org.bouncycastle
+ bcprov-jdk15on
+
+
+ org.eclipse.jetty
+ jetty-xml
+
+
+ org.eclipse.jetty
+ jetty-http
+
+
+ org.eclipse.jetty
+ jetty-servlet
+
+
+ org.eclipse.jetty
+ jetty-webapp
+
+
+ org.eclipse.jetty
+ jetty-util
+
+
+ org.eclipse.jetty
+ jetty-deploy
+
+
+ org.eclipse.jetty
+ jetty-continuation
+
+
+ org.eclipse.jetty
+ jetty-servlets
+
+
+ org.eclipse.jetty
+ jetty-io
+
+
+ org.eclipse.jetty
+ jetty-security
+
+
+
+
+ org.apache.lucene
+ lucene-core
+
+
+
+ org.apache.lucene
+ lucene-analyzers-icu
+ test
+
+
+ org.apache.lucene
+ lucene-analyzers-smartcn
+ test
+
+
+ org.apache.lucene
+ lucene-analyzers-stempel
+ test
+
+
+ org.apache.xmlbeans
+ xmlbeans
+ 2.6.0
+ com.maxmind.geoip2
@@ -547,7 +752,7 @@
org.flywaydbflyway-core
- 4.0.3
+ 6.5.5
@@ -571,6 +776,7 @@
com.google.oauth-clientgoogle-oauth-client
+
com.google.code.findbugs
@@ -580,6 +786,7 @@
com.google.code.findbugsannotations
+
joda-timejoda-time
@@ -670,7 +877,7 @@
org.xmlunit
- xmlunit-matchers
+ xmlunit-core2.6.3test
diff --git a/dspace-api/src/main/java/org/dspace/administer/CreateAdministrator.java b/dspace-api/src/main/java/org/dspace/administer/CreateAdministrator.java
index a58691e251..983038c812 100644
--- a/dspace-api/src/main/java/org/dspace/administer/CreateAdministrator.java
+++ b/dspace-api/src/main/java/org/dspace/administer/CreateAdministrator.java
@@ -115,7 +115,7 @@ public final class CreateAdministrator {
String lastName = null;
char[] password1 = null;
char[] password2 = null;
- String language = I18nUtil.DEFAULTLOCALE.getLanguage();
+ String language = I18nUtil.getDefaultLocale().getLanguage();
while (!dataOK) {
System.out.print("E-mail address: ");
diff --git a/dspace-api/src/main/java/org/dspace/app/bulkedit/DSpaceCSV.java b/dspace-api/src/main/java/org/dspace/app/bulkedit/DSpaceCSV.java
index 55bb3fed4b..ad7824bebf 100644
--- a/dspace-api/src/main/java/org/dspace/app/bulkedit/DSpaceCSV.java
+++ b/dspace-api/src/main/java/org/dspace/app/bulkedit/DSpaceCSV.java
@@ -8,14 +8,10 @@
package org.dspace.app.bulkedit;
import java.io.BufferedReader;
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
+import java.io.InputStream;
import java.io.InputStreamReader;
-import java.io.OutputStreamWriter;
import java.io.Serializable;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -27,6 +23,7 @@ import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.dspace.authority.AuthorityValue;
import org.dspace.authority.factory.AuthorityServiceFactory;
@@ -141,18 +138,18 @@ public class DSpaceCSV implements Serializable {
/**
* Create a new instance, reading the lines in from file
*
- * @param f The file to read from
+ * @param inputStream the inputstream to read from
* @param c The DSpace Context
* @throws Exception thrown if there is an error reading or processing the file
*/
- public DSpaceCSV(File f, Context c) throws Exception {
+ public DSpaceCSV(InputStream inputStream, Context c) throws Exception {
// Initialise the class
init();
// Open the CSV file
BufferedReader input = null;
try {
- input = new BufferedReader(new InputStreamReader(new FileInputStream(f), "UTF-8"));
+ input = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
// Read the heading line
String head = input.readLine();
@@ -623,21 +620,15 @@ public class DSpaceCSV implements Serializable {
}
/**
- * Save the CSV file to the given filename
- *
- * @param filename The filename to save the CSV file to
- * @throws IOException Thrown if an error occurs when writing the file
+ * Creates and returns an InputStream from the CSV Lines in this DSpaceCSV
+ * @return The InputStream created from the CSVLines in this DSpaceCSV
*/
- public final void save(String filename) throws IOException {
- // Save the file
- BufferedWriter out = new BufferedWriter(
- new OutputStreamWriter(
- new FileOutputStream(filename), "UTF-8"));
+ public InputStream getInputStream() {
+ StringBuilder stringBuilder = new StringBuilder();
for (String csvLine : getCSVLinesAsStringArray()) {
- out.write(csvLine + "\n");
+ stringBuilder.append(csvLine + "\n");
}
- out.flush();
- out.close();
+ return IOUtils.toInputStream(stringBuilder.toString(), StandardCharsets.UTF_8);
}
/**
diff --git a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExport.java b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExport.java
index bc015ef5e0..3332440f06 100644
--- a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExport.java
+++ b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExport.java
@@ -8,271 +8,107 @@
package org.dspace.app.bulkedit;
import java.sql.SQLException;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-import com.google.common.collect.Iterators;
-import org.apache.commons.cli.CommandLine;
-import org.apache.commons.cli.CommandLineParser;
-import org.apache.commons.cli.HelpFormatter;
-import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
-import org.apache.commons.cli.PosixParser;
-import org.dspace.content.Collection;
-import org.dspace.content.Community;
+import org.apache.commons.lang3.StringUtils;
import org.dspace.content.DSpaceObject;
-import org.dspace.content.Item;
import org.dspace.content.factory.ContentServiceFactory;
-import org.dspace.content.service.ItemService;
-import org.dspace.core.Constants;
+import org.dspace.content.service.MetadataDSpaceCsvExportService;
import org.dspace.core.Context;
+import org.dspace.eperson.factory.EPersonServiceFactory;
+import org.dspace.eperson.service.EPersonService;
import org.dspace.handle.factory.HandleServiceFactory;
+import org.dspace.scripts.DSpaceRunnable;
+import org.dspace.utils.DSpace;
/**
* Metadata exporter to allow the batch export of metadata into a file
*
* @author Stuart Lewis
*/
-public class MetadataExport {
- /**
- * The items to export
- */
- protected Iterator toExport;
+public class MetadataExport extends DSpaceRunnable {
- protected ItemService itemService;
+ private boolean help = false;
+ private String filename = null;
+ private String handle = null;
+ private boolean exportAllMetadata = false;
+ private boolean exportAllItems = false;
- protected Context context;
+ private static final String EXPORT_CSV = "exportCSV";
- /**
- * Whether to export all metadata, or just normally edited metadata
- */
- protected boolean exportAll;
+ private MetadataDSpaceCsvExportService metadataDSpaceCsvExportService = new DSpace().getServiceManager()
+ .getServicesByType(MetadataDSpaceCsvExportService.class).get(0);
- protected MetadataExport() {
- itemService = ContentServiceFactory.getInstance().getItemService();
- }
+ private EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService();
- /**
- * Set up a new metadata export
- *
- * @param c The Context
- * @param toExport The ItemIterator of items to export
- * @param exportAll whether to export all metadata or not (include handle, provenance etc)
- */
- public MetadataExport(Context c, Iterator toExport, boolean exportAll) {
- itemService = ContentServiceFactory.getInstance().getItemService();
-
- // Store the export settings
- this.toExport = toExport;
- this.exportAll = exportAll;
- this.context = c;
- }
-
- /**
- * Method to export a community (and sub-communities and collections)
- *
- * @param c The Context
- * @param toExport The Community to export
- * @param exportAll whether to export all metadata or not (include handle, provenance etc)
- */
- public MetadataExport(Context c, Community toExport, boolean exportAll) {
- itemService = ContentServiceFactory.getInstance().getItemService();
+ @Override
+ public void internalRun() throws Exception {
+ if (help) {
+ logHelpInfo();
+ printHelp();
+ return;
+ }
+ Context context = new Context();
+ context.turnOffAuthorisationSystem();
try {
- // Try to export the community
- this.toExport = buildFromCommunity(c, toExport, 0);
- this.exportAll = exportAll;
- this.context = c;
- } catch (SQLException sqle) {
- // Something went wrong...
- System.err.println("Error running exporter:");
- sqle.printStackTrace(System.err);
- System.exit(1);
+ context.setCurrentUser(ePersonService.find(context, this.getEpersonIdentifier()));
+ } catch (SQLException e) {
+ handler.handleException(e);
}
+ DSpaceCSV dSpaceCSV = metadataDSpaceCsvExportService
+ .handleExport(context, exportAllItems, exportAllMetadata, handle,
+ handler);
+ handler.writeFilestream(context, filename, dSpaceCSV.getInputStream(), EXPORT_CSV);
+ context.restoreAuthSystemState();
+ context.complete();
}
- /**
- * Build an array list of item ids that are in a community (include sub-communities and collections)
- *
- * @param context DSpace context
- * @param community The community to build from
- * @param indent How many spaces to use when writing out the names of items added
- * @return The list of item ids
- * @throws SQLException if database error
- */
- protected Iterator buildFromCommunity(Context context, Community community, int indent)
- throws SQLException {
- // Add all the collections
- List collections = community.getCollections();
- Iterator result = null;
- for (Collection collection : collections) {
- for (int i = 0; i < indent; i++) {
- System.out.print(" ");
- }
-
- Iterator items = itemService.findByCollection(context, collection);
- result = addItemsToResult(result, items);
-
- }
- // Add all the sub-communities
- List communities = community.getSubcommunities();
- for (Community subCommunity : communities) {
- for (int i = 0; i < indent; i++) {
- System.out.print(" ");
- }
- Iterator items = buildFromCommunity(context, subCommunity, indent + 1);
- result = addItemsToResult(result, items);
- }
-
- return result;
+ protected void logHelpInfo() {
+ handler.logInfo("\nfull export: metadata-export");
+ handler.logInfo("partial export: metadata-export -i handle");
}
- private Iterator addItemsToResult(Iterator result, Iterator items) {
- if (result == null) {
- result = items;
- } else {
- result = Iterators.concat(result, items);
- }
-
- return result;
+ @Override
+ public MetadataExportScriptConfiguration getScriptConfiguration() {
+ return new DSpace().getServiceManager().getServiceByName("metadata-export",
+ MetadataExportScriptConfiguration.class);
}
- /**
- * Run the export
- *
- * @return the exported CSV lines
- */
- public DSpaceCSV export() {
+ @Override
+ public void setup() throws ParseException {
+
+ if (commandLine.hasOption('h')) {
+ help = true;
+ return;
+ }
+
+ if (!commandLine.hasOption('i')) {
+ exportAllItems = true;
+ }
+ handle = commandLine.getOptionValue('i');
+ filename = getFileNameForExportFile();
+
+ exportAllMetadata = commandLine.hasOption('a');
+
+ }
+
+ protected String getFileNameForExportFile() throws ParseException {
+ Context context = new Context();
try {
- Context.Mode originalMode = context.getCurrentMode();
- context.setMode(Context.Mode.READ_ONLY);
-
- // Process each item
- DSpaceCSV csv = new DSpaceCSV(exportAll);
- while (toExport.hasNext()) {
- Item item = toExport.next();
- csv.addItem(item);
- context.uncacheEntity(item);
- }
-
- context.setMode(originalMode);
- // Return the results
- return csv;
- } catch (Exception e) {
- // Something went wrong...
- System.err.println("Error exporting to CSV:");
- e.printStackTrace();
- return null;
- }
- }
-
- /**
- * Print the help message
- *
- * @param options The command line options the user gave
- * @param exitCode the system exit code to use
- */
- private static void printHelp(Options options, int exitCode) {
- // print the help message
- HelpFormatter myhelp = new HelpFormatter();
- myhelp.printHelp("MetadataExport\n", options);
- System.out.println("\nfull export: metadataexport -f filename");
- System.out.println("partial export: metadataexport -i handle -f filename");
- System.exit(exitCode);
- }
-
- /**
- * main method to run the metadata exporter
- *
- * @param argv the command line arguments given
- * @throws Exception if error occurs
- */
- public static void main(String[] argv) throws Exception {
- // Create an options object and populate it
- CommandLineParser parser = new PosixParser();
-
- Options options = new Options();
-
- options.addOption("i", "id", true, "ID or handle of thing to export (item, collection, or community)");
- options.addOption("f", "file", true, "destination where you want file written");
- options.addOption("a", "all", false,
- "include all metadata fields that are not normally changed (e.g. provenance)");
- options.addOption("h", "help", false, "help");
-
- CommandLine line = null;
-
- try {
- line = parser.parse(options, argv);
- } catch (ParseException pe) {
- System.err.println("Error with commands.");
- printHelp(options, 1);
- System.exit(0);
- }
-
- if (line.hasOption('h')) {
- printHelp(options, 0);
- }
-
- // Check a filename is given
- if (!line.hasOption('f')) {
- System.err.println("Required parameter -f missing!");
- printHelp(options, 1);
- }
- String filename = line.getOptionValue('f');
-
- // Create a context
- Context c = new Context(Context.Mode.READ_ONLY);
- c.turnOffAuthorisationSystem();
-
- // The things we'll export
- Iterator toExport = null;
- MetadataExport exporter = null;
-
- // Export everything?
- boolean exportAll = line.hasOption('a');
-
- ContentServiceFactory contentServiceFactory = ContentServiceFactory.getInstance();
- // Check we have an item OK
- ItemService itemService = contentServiceFactory.getItemService();
- if (!line.hasOption('i')) {
- System.out.println("Exporting whole repository WARNING: May take some time!");
- exporter = new MetadataExport(c, itemService.findAll(c), exportAll);
- } else {
- String handle = line.getOptionValue('i');
- DSpaceObject dso = HandleServiceFactory.getInstance().getHandleService().resolveToObject(c, handle);
- if (dso == null) {
- System.err.println("Item '" + handle + "' does not resolve to an item in your repository!");
- printHelp(options, 1);
- }
-
- if (dso.getType() == Constants.ITEM) {
- System.out.println("Exporting item '" + dso.getName() + "' (" + handle + ")");
- List item = new ArrayList<>();
- item.add((Item) dso);
- exporter = new MetadataExport(c, item.iterator(), exportAll);
- } else if (dso.getType() == Constants.COLLECTION) {
- System.out.println("Exporting collection '" + dso.getName() + "' (" + handle + ")");
- Collection collection = (Collection) dso;
- toExport = itemService.findByCollection(c, collection);
- exporter = new MetadataExport(c, toExport, exportAll);
- } else if (dso.getType() == Constants.COMMUNITY) {
- System.out.println("Exporting community '" + dso.getName() + "' (" + handle + ")");
- exporter = new MetadataExport(c, (Community) dso, exportAll);
+ DSpaceObject dso = null;
+ if (StringUtils.isNotBlank(handle)) {
+ dso = HandleServiceFactory.getInstance().getHandleService().resolveToObject(context, handle);
} else {
- System.err.println("Error identifying '" + handle + "'");
- System.exit(1);
+ dso = ContentServiceFactory.getInstance().getSiteService().findSite(context);
}
+ if (dso == null) {
+ throw new ParseException("A handle got given that wasn't able to be parsed to a DSpaceObject");
+ }
+ return dso.getID().toString() + ".csv";
+ } catch (SQLException e) {
+ handler.handleException("Something went wrong trying to retrieve DSO for handle: " + handle, e);
}
-
- // Perform the export
- DSpaceCSV csv = exporter.export();
-
- // Save the files to the file
- csv.save(filename);
-
- // Finish off and tidy up
- c.restoreAuthSystemState();
- c.complete();
+ return null;
}
}
diff --git a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExportCli.java b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExportCli.java
new file mode 100644
index 0000000000..88ef66cbf6
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExportCli.java
@@ -0,0 +1,33 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.app.bulkedit;
+
+import org.apache.commons.cli.ParseException;
+
+public class MetadataExportCli extends MetadataExport {
+
+ @Override
+ protected String getFileNameForExportFile() {
+ return commandLine.getOptionValue('f');
+ }
+
+ @Override
+ public void setup() throws ParseException {
+ super.setup();
+ // Check a filename is given
+ if (!commandLine.hasOption('f')) {
+ throw new ParseException("Required parameter -f missing!");
+ }
+ }
+
+ @Override
+ protected void logHelpInfo() {
+ handler.logInfo("\nfull export: metadata-export -f filename");
+ handler.logInfo("partial export: metadata-export -i handle -f filename");
+ }
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExportCliScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExportCliScriptConfiguration.java
new file mode 100644
index 0000000000..a7699fe9a4
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExportCliScriptConfiguration.java
@@ -0,0 +1,26 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.app.bulkedit;
+
+import java.io.OutputStream;
+
+import org.apache.commons.cli.Options;
+
+public class MetadataExportCliScriptConfiguration extends MetadataExportScriptConfiguration {
+
+
+ @Override
+ public Options getOptions() {
+ Options options = super.getOptions();
+ options.addOption("f", "file", true, "destination where you want file written");
+ options.getOption("f").setType(OutputStream .class);
+ options.getOption("f").setRequired(true);
+ super.options = options;
+ return options;
+ }
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExportScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExportScriptConfiguration.java
new file mode 100644
index 0000000000..0c513c4667
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExportScriptConfiguration.java
@@ -0,0 +1,70 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.app.bulkedit;
+
+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;
+
+/**
+ * The {@link ScriptConfiguration} for the {@link MetadataExport} script
+ */
+public class MetadataExportScriptConfiguration extends ScriptConfiguration {
+
+ @Autowired
+ private AuthorizeService authorizeService;
+
+ private Class dspaceRunnableClass;
+
+ @Override
+ public Class getDspaceRunnableClass() {
+ return dspaceRunnableClass;
+ }
+
+ /**
+ * Generic setter for the dspaceRunnableClass
+ * @param dspaceRunnableClass The dspaceRunnableClass to be set on this MetadataExportScriptConfiguration
+ */
+ @Override
+ public void setDspaceRunnableClass(Class dspaceRunnableClass) {
+ this.dspaceRunnableClass = dspaceRunnableClass;
+ }
+
+ @Override
+ public boolean isAllowedToExecute(Context context) {
+ try {
+ return authorizeService.isAdmin(context);
+ } catch (SQLException e) {
+ throw new RuntimeException("SQLException occurred when checking if the current user is an admin", e);
+ }
+ }
+
+ @Override
+ public Options getOptions() {
+ if (options == null) {
+ Options options = new Options();
+
+ options.addOption("i", "id", true, "ID or handle of thing to export (item, collection, or community)");
+ options.getOption("i").setType(String.class);
+ options.addOption("a", "all", false,
+ "include all metadata fields that are not normally changed (e.g. provenance)");
+ options.getOption("a").setType(boolean.class);
+ options.addOption("h", "help", false, "help");
+ options.getOption("h").setType(boolean.class);
+
+
+ super.options = options;
+ }
+ return options;
+ }
+
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java
index e8fff71cf4..67086c1536 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
@@ -7,10 +7,8 @@
*/
package org.dspace.app.bulkedit;
-import java.io.BufferedReader;
-import java.io.File;
import java.io.IOException;
-import java.io.InputStreamReader;
+import java.io.InputStream;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Enumeration;
@@ -19,16 +17,12 @@ import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import javax.annotation.Nullable;
-import org.apache.commons.cli.CommandLine;
-import org.apache.commons.cli.CommandLineParser;
-import org.apache.commons.cli.HelpFormatter;
-import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
-import org.apache.commons.cli.PosixParser;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.Logger;
import org.dspace.authority.AuthorityValue;
@@ -65,6 +59,10 @@ import org.dspace.eperson.EPerson;
import org.dspace.eperson.factory.EPersonServiceFactory;
import org.dspace.handle.factory.HandleServiceFactory;
import org.dspace.handle.service.HandleService;
+import org.dspace.scripts.DSpaceRunnable;
+import org.dspace.scripts.handler.DSpaceRunnableHandler;
+import org.dspace.utils.DSpace;
+import org.dspace.workflow.WorkflowException;
import org.dspace.workflow.WorkflowItem;
import org.dspace.workflow.WorkflowService;
import org.dspace.workflow.factory.WorkflowServiceFactory;
@@ -74,11 +72,7 @@ import org.dspace.workflow.factory.WorkflowServiceFactory;
*
* @author Stuart Lewis
*/
-public class MetadataImport {
- /**
- * The Context
- */
- Context c;
+public class MetadataImport extends DSpaceRunnable {
/**
* The DSpaceCSV object we're processing
@@ -95,10 +89,6 @@ public class MetadataImport {
*/
protected static Set authorityControlled;
- static {
- setAuthorizedMetadataFields();
- }
-
/**
* The prefix of the authority controlled field
*/
@@ -143,45 +133,200 @@ public class MetadataImport {
*/
protected Integer rowCount = 1;
+ private boolean useTemplate = false;
+ private String filename = null;
+ private boolean useWorkflow = false;
+ private boolean workflowNotify = false;
+ private boolean change = false;
+ private boolean help = false;
+ protected boolean validateOnly;
+
/**
* Logger
*/
protected static final Logger log = org.apache.logging.log4j.LogManager.getLogger(MetadataImport.class);
- protected final AuthorityValueService authorityValueService;
-
- protected final ItemService itemService;
- protected final InstallItemService installItemService;
- protected final CollectionService collectionService;
- protected final HandleService handleService;
- protected final WorkspaceItemService workspaceItemService;
- protected final RelationshipTypeService relationshipTypeService;
- protected final RelationshipService relationshipService;
- protected final EntityTypeService entityTypeService;
- protected final EntityService entityService;
+ protected ItemService itemService = ContentServiceFactory.getInstance().getItemService();
+ protected InstallItemService installItemService = ContentServiceFactory.getInstance().getInstallItemService();
+ protected CollectionService collectionService = ContentServiceFactory.getInstance().getCollectionService();
+ protected HandleService handleService = HandleServiceFactory.getInstance().getHandleService();
+ protected WorkspaceItemService workspaceItemService = ContentServiceFactory.getInstance().getWorkspaceItemService();
+ protected RelationshipTypeService relationshipTypeService = ContentServiceFactory.getInstance()
+ .getRelationshipTypeService();
+ protected RelationshipService relationshipService = ContentServiceFactory.getInstance().getRelationshipService();
+ protected EntityTypeService entityTypeService = ContentServiceFactory.getInstance().getEntityTypeService();
+ protected EntityService entityService = ContentServiceFactory.getInstance().getEntityService();
+ protected AuthorityValueService authorityValueService = AuthorityServiceFactory.getInstance()
+ .getAuthorityValueService();
/**
* Create an instance of the metadata importer. Requires a context and an array of CSV lines
* to examine.
*
- * @param c The context
* @param toImport An array of CSV lines to examine
*/
- public MetadataImport(Context c, DSpaceCSV toImport) {
+ public void initMetadataImport(DSpaceCSV toImport) {
// Store the import settings
- this.c = c;
- csv = toImport;
this.toImport = toImport.getCSVLines();
- installItemService = ContentServiceFactory.getInstance().getInstallItemService();
- itemService = ContentServiceFactory.getInstance().getItemService();
- collectionService = ContentServiceFactory.getInstance().getCollectionService();
- handleService = HandleServiceFactory.getInstance().getHandleService();
- authorityValueService = AuthorityServiceFactory.getInstance().getAuthorityValueService();
- workspaceItemService = ContentServiceFactory.getInstance().getWorkspaceItemService();
- relationshipService = ContentServiceFactory.getInstance().getRelationshipService();
- relationshipTypeService = ContentServiceFactory.getInstance().getRelationshipTypeService();
- entityTypeService = ContentServiceFactory.getInstance().getEntityTypeService();
- entityService = ContentServiceFactory.getInstance().getEntityService();
+ }
+
+ @Override
+ public void internalRun() throws Exception {
+ if (help) {
+ printHelp();
+ return;
+ }
+ // Create a context
+ Context c = null;
+ c = new Context();
+ c.turnOffAuthorisationSystem();
+
+ // Find the EPerson, assign to context
+ assignCurrentUserInContext(c);
+
+ if (authorityControlled == null) {
+ setAuthorizedMetadataFields();
+ }
+ // Read commandLines from the CSV file
+ try {
+
+ Optional optionalFileStream = handler.getFileStream(c, filename);
+ if (optionalFileStream.isPresent()) {
+ csv = new DSpaceCSV(optionalFileStream.get(), c);
+ } else {
+ throw new IllegalArgumentException("Error reading file, the file couldn't be found for filename: " +
+ filename);
+ }
+ } catch (MetadataImportInvalidHeadingException miihe) {
+ throw miihe;
+ } catch (Exception e) {
+ throw new Exception("Error reading file: " + e.getMessage(), e);
+ }
+
+ // Perform the first import - just highlight differences
+ initMetadataImport(csv);
+ List changes;
+
+ if (!commandLine.hasOption('s') || validateOnly) {
+ // See what has changed
+ try {
+ changes = runImport(c, false, useWorkflow, workflowNotify, useTemplate);
+ } catch (MetadataImportException mie) {
+ throw mie;
+ }
+
+ // Display the changes
+ int changeCounter = displayChanges(changes, false);
+
+ // If there were changes, ask if we should execute them
+ if (!validateOnly && changeCounter > 0) {
+ try {
+ // Ask the user if they want to make the changes
+ handler.logInfo("\n" + changeCounter + " item(s) will be changed\n");
+ change = determineChange(handler);
+
+ } catch (IOException ioe) {
+ throw new IOException("Error: " + ioe.getMessage() + ", No changes have been made", ioe);
+ }
+ } else {
+ handler.logInfo("There were no changes detected");
+ }
+ } else {
+ change = true;
+ }
+
+ try {
+ // If required, make the change
+ if (change && !validateOnly) {
+ try {
+ // Make the changes
+ changes = runImport(c, true, useWorkflow, workflowNotify, useTemplate);
+ } catch (MetadataImportException mie) {
+ throw mie;
+ }
+
+ // Display the changes
+ displayChanges(changes, true);
+ }
+
+ // Finsh off and tidy up
+ c.restoreAuthSystemState();
+ c.complete();
+ } catch (Exception e) {
+ c.abort();
+ throw new Exception(
+ "Error committing changes to database: " + e.getMessage() + ", aborting most recent changes", e);
+ }
+
+ }
+
+ protected void assignCurrentUserInContext(Context context) throws ParseException {
+ UUID uuid = getEpersonIdentifier();
+ if (uuid != null) {
+ try {
+ EPerson ePerson = EPersonServiceFactory.getInstance().getEPersonService().find(context, uuid);
+ context.setCurrentUser(ePerson);
+ } catch (SQLException e) {
+ log.error("Something went wrong trying to fetch the eperson for uuid: " + uuid, e);
+ }
+ }
+ }
+
+ /**
+ * This method determines whether the changes should be applied or not. This is default set to true for the REST
+ * script as we don't want to interact with the caller. This will be overwritten in the CLI script to ask for
+ * confirmation
+ * @param handler Applicable DSpaceRunnableHandler
+ * @return boolean indicating the value
+ * @throws IOException If something goes wrong
+ */
+ protected boolean determineChange(DSpaceRunnableHandler handler) throws IOException {
+ return true;
+ }
+
+ @Override
+ public MetadataImportScriptConfiguration getScriptConfiguration() {
+ return new DSpace().getServiceManager().getServiceByName("metadata-import",
+ MetadataImportScriptConfiguration.class);
+ }
+
+
+ public void setup() throws ParseException {
+ useTemplate = false;
+ filename = null;
+ useWorkflow = false;
+ workflowNotify = false;
+
+ if (commandLine.hasOption('h')) {
+ help = true;
+ return;
+ }
+
+ // Check a filename is given
+ if (!commandLine.hasOption('f')) {
+ throw new ParseException("Required parameter -f missing!");
+ }
+ filename = commandLine.getOptionValue('f');
+
+ // Option to apply template to new items
+ if (commandLine.hasOption('t')) {
+ useTemplate = true;
+ }
+
+ // Options for workflows, and workflow notifications for new items
+ if (commandLine.hasOption('w')) {
+ useWorkflow = true;
+ if (commandLine.hasOption('n')) {
+ workflowNotify = true;
+ }
+ } else if (commandLine.hasOption('n')) {
+ throw new ParseException(
+ "Invalid option 'n': (notify) can only be specified with the 'w' (workflow) option.");
+ }
+ validateOnly = commandLine.hasOption('v');
+
+ // Is this a silent run?
+ change = false;
}
/**
@@ -195,281 +340,277 @@ public class MetadataImport {
* @return An array of BulkEditChange elements representing the items that have changed
* @throws MetadataImportException if something goes wrong
*/
- public List runImport(boolean change,
+ public List runImport(Context c, boolean change,
boolean useWorkflow,
boolean workflowNotify,
- boolean useTemplate) throws MetadataImportException {
+ boolean useTemplate)
+ throws MetadataImportException, SQLException, AuthorizeException, WorkflowException, IOException {
// Store the changes
ArrayList changes = new ArrayList();
// Make the changes
- try {
- Context.Mode originalMode = c.getCurrentMode();
- c.setMode(Context.Mode.BATCH_EDIT);
+ Context.Mode originalMode = c.getCurrentMode();
+ c.setMode(Context.Mode.BATCH_EDIT);
- // Process each change
- rowCount = 1;
- for (DSpaceCSVLine line : toImport) {
- // Resolve target references to other items
- populateRefAndRowMap(line, line.getID());
- line = resolveEntityRefs(line);
- // Get the DSpace item to compare with
- UUID id = line.getID();
+ // Process each change
+ rowCount = 1;
+ for (DSpaceCSVLine line : toImport) {
+ // Resolve target references to other items
+ populateRefAndRowMap(line, line.getID());
+ line = resolveEntityRefs(c, line);
+ // Get the DSpace item to compare with
+ UUID id = line.getID();
- // Is there an action column?
- if (csv.hasActions() && (!"".equals(line.getAction())) && (id == null)) {
- throw new MetadataImportException("'action' not allowed for new items!");
- }
-
- WorkspaceItem wsItem = null;
- WorkflowItem wfItem = null;
- Item item = null;
-
- // Is this an existing item?
- if (id != null) {
- // Get the item
- item = itemService.find(c, id);
- if (item == null) {
- throw new MetadataImportException("Unknown item ID " + id);
- }
-
- // Record changes
- BulkEditChange whatHasChanged = new BulkEditChange(item);
-
- // Has it moved collection?
- List collections = line.get("collection");
- if (collections != null) {
- // Sanity check we're not orphaning it
- if (collections.size() == 0) {
- throw new MetadataImportException("Missing collection from item " + item.getHandle());
- }
- List actualCollections = item.getCollections();
- compare(item, collections, actualCollections, whatHasChanged, change);
- }
-
- // Iterate through each metadata element in the csv line
- for (String md : line.keys()) {
- // Get the values we already have
- if (!"id".equals(md)) {
- // Get the values from the CSV
- String[] fromCSV = line.get(md).toArray(new String[line.get(md).size()]);
- // Remove authority unless the md is not authority controlled
- if (!isAuthorityControlledField(md)) {
- for (int i = 0; i < fromCSV.length; i++) {
- int pos = fromCSV[i].indexOf(csv.getAuthoritySeparator());
- if (pos > -1) {
- fromCSV[i] = fromCSV[i].substring(0, pos);
- }
- }
- }
- // Compare
- compareAndUpdate(item, fromCSV, change, md, whatHasChanged, line);
- }
- }
-
- if (csv.hasActions()) {
- // Perform the action
- String action = line.getAction();
- if ("".equals(action)) {
- // Do nothing
- } else if ("expunge".equals(action)) {
- // Does the configuration allow deletes?
- if (!ConfigurationManager.getBooleanProperty("bulkedit", "allowexpunge", false)) {
- throw new MetadataImportException("'expunge' action denied by configuration");
- }
-
- // Remove the item
-
- if (change) {
- itemService.delete(c, item);
- }
-
- whatHasChanged.setDeleted();
- } else if ("withdraw".equals(action)) {
- // Withdraw the item
- if (!item.isWithdrawn()) {
- if (change) {
- itemService.withdraw(c, item);
- }
- whatHasChanged.setWithdrawn();
- }
- } else if ("reinstate".equals(action)) {
- // Reinstate the item
- if (item.isWithdrawn()) {
- if (change) {
- itemService.reinstate(c, item);
- }
- whatHasChanged.setReinstated();
- }
- } else {
- // Unknown action!
- throw new MetadataImportException("Unknown action: " + action);
- }
- }
-
- // Only record if changes have been made
- if (whatHasChanged.hasChanges()) {
- changes.add(whatHasChanged);
- }
- } else {
- // This is marked as a new item, so no need to compare
-
- // First check a user is set, otherwise this can't happen
- if (c.getCurrentUser() == null) {
- throw new MetadataImportException(
- "When adding new items, a user must be specified with the -e option");
- }
-
- // Iterate through each metadata element in the csv line
- BulkEditChange whatHasChanged = new BulkEditChange();
- for (String md : line.keys()) {
- // Get the values we already have
- if (!"id".equals(md) && !"rowName".equals(md)) {
- // Get the values from the CSV
- String[] fromCSV = line.get(md).toArray(new String[line.get(md).size()]);
-
- // Remove authority unless the md is not authority controlled
- if (!isAuthorityControlledField(md)) {
- for (int i = 0; i < fromCSV.length; i++) {
- int pos = fromCSV[i].indexOf(csv.getAuthoritySeparator());
- if (pos > -1) {
- fromCSV[i] = fromCSV[i].substring(0, pos);
- }
- }
- }
-
- // Add all the values from the CSV line
- add(fromCSV, md, whatHasChanged);
- }
- }
-
- // Check it has an owning collection
- List collections = line.get("collection");
- if (collections == null) {
- throw new MetadataImportException(
- "New items must have a 'collection' assigned in the form of a handle");
- }
-
- // Check collections are really collections
- ArrayList check = new ArrayList();
- Collection collection;
- for (String handle : collections) {
- try {
- // Resolve the handle to the collection
- collection = (Collection) handleService.resolveToObject(c, handle);
-
- // Check it resolved OK
- if (collection == null) {
- throw new MetadataImportException(
- "'" + handle + "' is not a Collection! You must specify a valid collection for " +
- "new items");
- }
-
- // Check for duplicate
- if (check.contains(collection)) {
- throw new MetadataImportException(
- "Duplicate collection assignment detected in new item! " + handle);
- } else {
- check.add(collection);
- }
- } catch (Exception ex) {
- throw new MetadataImportException(
- "'" + handle + "' is not a Collection! You must specify a valid collection for new " +
- "items",
- ex);
- }
- }
-
- // Record the addition to collections
- boolean first = true;
- for (String handle : collections) {
- Collection extra = (Collection) handleService.resolveToObject(c, handle);
- if (first) {
- whatHasChanged.setOwningCollection(extra);
- } else {
- whatHasChanged.registerNewMappedCollection(extra);
- }
- first = false;
- }
-
- // Create the new item?
- if (change) {
- // Create the item
- String collectionHandle = line.get("collection").get(0);
- collection = (Collection) handleService.resolveToObject(c, collectionHandle);
- wsItem = workspaceItemService.create(c, collection, useTemplate);
- item = wsItem.getItem();
-
- // Add the metadata to the item
- for (BulkEditMetadataValue dcv : whatHasChanged.getAdds()) {
- if (!StringUtils.equals(dcv.getSchema(), MetadataSchemaEnum.RELATION.getName())) {
- itemService.addMetadata(c, item, dcv.getSchema(),
- dcv.getElement(),
- dcv.getQualifier(),
- dcv.getLanguage(),
- dcv.getValue(),
- dcv.getAuthority(),
- dcv.getConfidence());
- }
- }
- //Add relations after all metadata has been processed
- for (BulkEditMetadataValue dcv : whatHasChanged.getAdds()) {
- if (StringUtils.equals(dcv.getSchema(), MetadataSchemaEnum.RELATION.getName())) {
- addRelationship(c, item, dcv.getElement(), dcv.getValue());
- }
- }
-
-
- // Should the workflow be used?
- if (useWorkflow) {
- WorkflowService workflowService = WorkflowServiceFactory.getInstance().getWorkflowService();
- if (workflowNotify) {
- wfItem = workflowService.start(c, wsItem);
- } else {
- wfItem = workflowService.startWithoutNotify(c, wsItem);
- }
- } else {
- // Install the item
- installItemService.installItem(c, wsItem);
- }
-
- // Add to extra collections
- if (line.get("collection").size() > 0) {
- for (int i = 1; i < collections.size(); i++) {
- String handle = collections.get(i);
- Collection extra = (Collection) handleService.resolveToObject(c, handle);
- collectionService.addItem(c, extra, item);
- }
- }
-
- whatHasChanged.setItem(item);
- }
-
- // Record the changes
- changes.add(whatHasChanged);
- }
-
- if (change) {
- //only clear cache if changes have been made.
- c.uncacheEntity(wsItem);
- c.uncacheEntity(wfItem);
- c.uncacheEntity(item);
- }
- populateRefAndRowMap(line, item == null ? null : item.getID());
- // keep track of current rows processed
- rowCount++;
+ // Is there an action column?
+ if (csv.hasActions() && (!"".equals(line.getAction())) && (id == null)) {
+ throw new MetadataImportException("'action' not allowed for new items!");
}
- c.setMode(originalMode);
- } catch (MetadataImportException mie) {
- throw mie;
- } catch (Exception e) {
- e.printStackTrace();
+ WorkspaceItem wsItem = null;
+ WorkflowItem wfItem = null;
+ Item item = null;
+
+ // Is this an existing item?
+ if (id != null) {
+ // Get the item
+ item = itemService.find(c, id);
+ if (item == null) {
+ throw new MetadataImportException("Unknown item ID " + id);
+ }
+
+ // Record changes
+ BulkEditChange whatHasChanged = new BulkEditChange(item);
+
+ // Has it moved collection?
+ List collections = line.get("collection");
+ if (collections != null) {
+ // Sanity check we're not orphaning it
+ if (collections.size() == 0) {
+ throw new MetadataImportException("Missing collection from item " + item.getHandle());
+ }
+ List actualCollections = item.getCollections();
+ compare(c, item, collections, actualCollections, whatHasChanged, change);
+ }
+
+ // Iterate through each metadata element in the csv line
+ for (String md : line.keys()) {
+ // Get the values we already have
+ if (!"id".equals(md)) {
+ // Get the values from the CSV
+ String[] fromCSV = line.get(md).toArray(new String[line.get(md).size()]);
+ // Remove authority unless the md is not authority controlled
+ if (!isAuthorityControlledField(md)) {
+ for (int i = 0; i < fromCSV.length; i++) {
+ int pos = fromCSV[i].indexOf(csv.getAuthoritySeparator());
+ if (pos > -1) {
+ fromCSV[i] = fromCSV[i].substring(0, pos);
+ }
+ }
+ }
+ // Compare
+ compareAndUpdate(c, item, fromCSV, change, md, whatHasChanged, line);
+ }
+ }
+
+ if (csv.hasActions()) {
+ // Perform the action
+ String action = line.getAction();
+ if ("".equals(action)) {
+ // Do nothing
+ } else if ("expunge".equals(action)) {
+ // Does the configuration allow deletes?
+ if (!ConfigurationManager.getBooleanProperty("bulkedit", "allowexpunge", false)) {
+ throw new MetadataImportException("'expunge' action denied by configuration");
+ }
+
+ // Remove the item
+
+ if (change) {
+ itemService.delete(c, item);
+ }
+
+ whatHasChanged.setDeleted();
+ } else if ("withdraw".equals(action)) {
+ // Withdraw the item
+ if (!item.isWithdrawn()) {
+ if (change) {
+ itemService.withdraw(c, item);
+ }
+ whatHasChanged.setWithdrawn();
+ }
+ } else if ("reinstate".equals(action)) {
+ // Reinstate the item
+ if (item.isWithdrawn()) {
+ if (change) {
+ itemService.reinstate(c, item);
+ }
+ whatHasChanged.setReinstated();
+ }
+ } else {
+ // Unknown action!
+ throw new MetadataImportException("Unknown action: " + action);
+ }
+ }
+
+ // Only record if changes have been made
+ if (whatHasChanged.hasChanges()) {
+ changes.add(whatHasChanged);
+ }
+ } else {
+ // This is marked as a new item, so no need to compare
+
+ // First check a user is set, otherwise this can't happen
+ if (c.getCurrentUser() == null) {
+ throw new MetadataImportException(
+ "When adding new items, a user must be specified with the -e option");
+ }
+
+ // Iterate through each metadata element in the csv line
+ BulkEditChange whatHasChanged = new BulkEditChange();
+ for (String md : line.keys()) {
+ // Get the values we already have
+ if (!"id".equals(md) && !"rowName".equals(md)) {
+ // Get the values from the CSV
+ String[] fromCSV = line.get(md).toArray(new String[line.get(md).size()]);
+
+ // Remove authority unless the md is not authority controlled
+ if (!isAuthorityControlledField(md)) {
+ for (int i = 0; i < fromCSV.length; i++) {
+ int pos = fromCSV[i].indexOf(csv.getAuthoritySeparator());
+ if (pos > -1) {
+ fromCSV[i] = fromCSV[i].substring(0, pos);
+ }
+ }
+ }
+
+ // Add all the values from the CSV line
+ add(c, fromCSV, md, whatHasChanged);
+ }
+ }
+
+ // Check it has an owning collection
+ List collections = line.get("collection");
+ if (collections == null) {
+ throw new MetadataImportException(
+ "New items must have a 'collection' assigned in the form of a handle");
+ }
+
+ // Check collections are really collections
+ ArrayList check = new ArrayList();
+ Collection collection;
+ for (String handle : collections) {
+ try {
+ // Resolve the handle to the collection
+ collection = (Collection) handleService.resolveToObject(c, handle);
+
+ // Check it resolved OK
+ if (collection == null) {
+ throw new MetadataImportException(
+ "'" + handle + "' is not a Collection! You must specify a valid collection for " +
+ "new items");
+ }
+
+ // Check for duplicate
+ if (check.contains(collection)) {
+ throw new MetadataImportException(
+ "Duplicate collection assignment detected in new item! " + handle);
+ } else {
+ check.add(collection);
+ }
+ } catch (Exception ex) {
+ throw new MetadataImportException(
+ "'" + handle + "' is not a Collection! You must specify a valid collection for new " +
+ "items",
+ ex);
+ }
+ }
+
+ // Record the addition to collections
+ boolean first = true;
+ for (String handle : collections) {
+ Collection extra = (Collection) handleService.resolveToObject(c, handle);
+ if (first) {
+ whatHasChanged.setOwningCollection(extra);
+ } else {
+ whatHasChanged.registerNewMappedCollection(extra);
+ }
+ first = false;
+ }
+
+ // Create the new item?
+ if (change) {
+ // Create the item
+ String collectionHandle = line.get("collection").get(0);
+ collection = (Collection) handleService.resolveToObject(c, collectionHandle);
+ wsItem = workspaceItemService.create(c, collection, useTemplate);
+ item = wsItem.getItem();
+
+ // Add the metadata to the item
+ for (BulkEditMetadataValue dcv : whatHasChanged.getAdds()) {
+ if (!StringUtils.equals(dcv.getSchema(), MetadataSchemaEnum.RELATION.getName())) {
+ itemService.addMetadata(c, item, dcv.getSchema(),
+ dcv.getElement(),
+ dcv.getQualifier(),
+ dcv.getLanguage(),
+ dcv.getValue(),
+ dcv.getAuthority(),
+ dcv.getConfidence());
+ }
+ }
+ //Add relations after all metadata has been processed
+ for (BulkEditMetadataValue dcv : whatHasChanged.getAdds()) {
+ if (StringUtils.equals(dcv.getSchema(), MetadataSchemaEnum.RELATION.getName())) {
+ addRelationship(c, item, dcv.getElement(), dcv.getValue());
+ }
+ }
+
+
+ // Should the workflow be used?
+ if (useWorkflow) {
+ WorkflowService workflowService = WorkflowServiceFactory.getInstance().getWorkflowService();
+ if (workflowNotify) {
+ wfItem = workflowService.start(c, wsItem);
+ } else {
+ wfItem = workflowService.startWithoutNotify(c, wsItem);
+ }
+ } else {
+ // Install the item
+ installItemService.installItem(c, wsItem);
+ }
+
+ // Add to extra collections
+ if (line.get("collection").size() > 0) {
+ for (int i = 1; i < collections.size(); i++) {
+ String handle = collections.get(i);
+ Collection extra = (Collection) handleService.resolveToObject(c, handle);
+ collectionService.addItem(c, extra, item);
+ }
+ }
+
+ whatHasChanged.setItem(item);
+ }
+
+ // Record the changes
+ changes.add(whatHasChanged);
+ }
+
+ if (change) {
+ //only clear cache if changes have been made.
+ c.uncacheEntity(wsItem);
+ c.uncacheEntity(wfItem);
+ c.uncacheEntity(item);
+ }
+ populateRefAndRowMap(line, item == null ? null : item.getID());
+ // keep track of current rows processed
+ rowCount++;
}
+ c.setMode(originalMode);
+
+
// Return the changes
- if (!change ) {
- validateExpressedRelations();
+ if (!change) {
+ validateExpressedRelations(c);
}
return changes;
}
@@ -487,7 +628,7 @@ public class MetadataImport {
* @throws AuthorizeException if there is an authorization problem with permissions
* @throws MetadataImportException custom exception for error handling within metadataimport
*/
- protected void compareAndUpdate(Item item, String[] fromCSV, boolean change,
+ protected void compareAndUpdate(Context c, Item item, String[] fromCSV, boolean change,
String md, BulkEditChange changes, DSpaceCSVLine line)
throws SQLException, AuthorizeException, MetadataImportException {
// Log what metadata element we're looking at
@@ -565,7 +706,7 @@ public class MetadataImport {
// Compare from current->csv
for (int v = 0; v < fromCSV.length; v++) {
String value = fromCSV[v];
- BulkEditMetadataValue dcv = getBulkEditValueFromCSV(language, schema, element, qualifier, value,
+ BulkEditMetadataValue dcv = getBulkEditValueFromCSV(c, language, schema, element, qualifier, value,
fromAuthority);
if (fromAuthority != null) {
value = dcv.getValue() + csv.getAuthoritySeparator() + dcv.getAuthority() + csv
@@ -694,8 +835,8 @@ public class MetadataImport {
* @throws AuthorizeException If something goes wrong
*/
private void addRelationships(Context c, Item item, String typeName, List values)
- throws SQLException, AuthorizeException,
- MetadataImportException {
+ throws SQLException, AuthorizeException,
+ MetadataImportException {
for (String value : values) {
addRelationship(c, item, typeName, value);
}
@@ -746,22 +887,23 @@ public class MetadataImport {
Entity relationEntity = getEntity(c, value);
// Get relationship type of entity and item
String relationEntityRelationshipType = itemService.getMetadata(relationEntity.getItem(),
- "relationship", "type",
- null, Item.ANY).get(0).getValue();
+ "relationship", "type",
+ null, Item.ANY).get(0).getValue();
String itemRelationshipType = itemService.getMetadata(item, "relationship", "type",
- null, Item.ANY).get(0).getValue();
+ null, Item.ANY).get(0).getValue();
// Get the correct RelationshipType based on typeName
List relType = relationshipTypeService.findByLeftwardOrRightwardTypeName(c, typeName);
RelationshipType foundRelationshipType = matchRelationshipType(relType,
- relationEntityRelationshipType, itemRelationshipType, typeName);
+ relationEntityRelationshipType,
+ itemRelationshipType, typeName);
if (foundRelationshipType == null) {
throw new MetadataImportException("Error on CSV row " + rowCount + ":" + "\n" +
- "No Relationship type found for:\n" +
- "Target type: " + relationEntityRelationshipType + "\n" +
- "Origin referer type: " + itemRelationshipType + "\n" +
- "with typeName: " + typeName);
+ "No Relationship type found for:\n" +
+ "Target type: " + relationEntityRelationshipType + "\n" +
+ "Origin referer type: " + itemRelationshipType + "\n" +
+ "with typeName: " + typeName);
}
if (foundRelationshipType.getLeftwardType().equalsIgnoreCase(typeName)) {
@@ -783,7 +925,7 @@ public class MetadataImport {
int leftPlace = relationshipService.findNextLeftPlaceByLeftItem(c, leftItem);
int rightPlace = relationshipService.findNextRightPlaceByRightItem(c, rightItem);
Relationship persistedRelationship = relationshipService.create(c, leftItem, rightItem,
- foundRelationshipType, leftPlace, rightPlace);
+ foundRelationshipType, leftPlace, rightPlace);
relationshipService.update(c, persistedRelationship);
}
@@ -801,7 +943,7 @@ public class MetadataImport {
* @throws IOException Can be thrown when moving items in communities
* @throws MetadataImportException If something goes wrong to be reported back to the user
*/
- protected void compare(Item item,
+ protected void compare(Context c, Item item,
List collections,
List actualCollections,
BulkEditChange bechange,
@@ -898,8 +1040,8 @@ public class MetadataImport {
// Remove from old owned collection (if still a member)
if (bechange.getOldOwningCollection() != null) {
boolean found = false;
- for (Collection c : item.getCollections()) {
- if (c.getID().equals(bechange.getOldOwningCollection().getID())) {
+ for (Collection collection : item.getCollections()) {
+ if (collection.getID().equals(bechange.getOldOwningCollection().getID())) {
found = true;
}
}
@@ -926,7 +1068,7 @@ public class MetadataImport {
* @throws SQLException when an SQL error has occurred (querying DSpace)
* @throws AuthorizeException If the user can't make the changes
*/
- protected void add(String[] fromCSV, String md, BulkEditChange changes)
+ protected void add(Context c, String[] fromCSV, String md, BulkEditChange changes)
throws SQLException, AuthorizeException {
// Don't add owning collection or action
if (("collection".equals(md)) || ("action".equals(md))) {
@@ -964,7 +1106,7 @@ public class MetadataImport {
// Add all the values
for (String value : fromCSV) {
- BulkEditMetadataValue dcv = getBulkEditValueFromCSV(language, schema, element, qualifier, value,
+ BulkEditMetadataValue dcv = getBulkEditValueFromCSV(c, language, schema, element, qualifier, value,
fromAuthority);
if (fromAuthority != null) {
value = dcv.getValue() + csv.getAuthoritySeparator() + dcv.getAuthority() + csv
@@ -978,7 +1120,7 @@ public class MetadataImport {
}
}
- protected BulkEditMetadataValue getBulkEditValueFromCSV(String language, String schema, String element,
+ protected BulkEditMetadataValue getBulkEditValueFromCSV(Context c, String language, String schema, String element,
String qualifier, String value,
AuthorityValue fromAuthority) {
// Look to see if it should be removed
@@ -1057,20 +1199,6 @@ public class MetadataImport {
return in.replaceAll("\r\n", "").replaceAll("\n", "").trim();
}
- /**
- * Print the help message
- *
- * @param options The command line options the user gave
- * @param exitCode the system exit code to use
- */
- private static void printHelp(Options options, int exitCode) {
- // print the help message
- HelpFormatter myhelp = new HelpFormatter();
- myhelp.printHelp("MetatadataImport\n", options);
- System.out.println("\nmetadataimport: MetadataImport -f filename");
- System.exit(exitCode);
- }
-
/**
* Display the changes that have been detected, or that have been made
*
@@ -1078,7 +1206,7 @@ public class MetadataImport {
* @param changed Whether or not the changes have been made
* @return The number of items that have changed
*/
- private static int displayChanges(List changes, boolean changed) {
+ private int displayChanges(List changes, boolean changed) {
// Display the changes
int changeCounter = 0;
for (BulkEditChange change : changes) {
@@ -1093,20 +1221,18 @@ public class MetadataImport {
(change.isDeleted()) || (change.isWithdrawn()) || (change.isReinstated())) {
// Show the item
Item i = change.getItem();
-
- System.out.println("-----------------------------------------------------------");
+ handler.logInfo("-----------------------------------------------------------");
if (!change.isNewItem()) {
- System.out.println("Changes for item: " + i.getID() + " (" + i.getHandle() + ")");
+ handler.logInfo("Changes for item: " + i.getID() + " (" + i.getHandle() + ")");
} else {
- System.out.print("New item: ");
+ handler.logInfo("New item: ");
if (i != null) {
if (i.getHandle() != null) {
- System.out.print(i.getID() + " (" + i.getHandle() + ")");
+ handler.logInfo(i.getID() + " (" + i.getHandle() + ")");
} else {
- System.out.print(i.getID() + " (in workflow)");
+ handler.logInfo(i.getID() + " (in workflow)");
}
}
- System.out.println();
}
changeCounter++;
}
@@ -1114,23 +1240,23 @@ public class MetadataImport {
// Show actions
if (change.isDeleted()) {
if (changed) {
- System.out.println(" - EXPUNGED!");
+ handler.logInfo(" - EXPUNGED!");
} else {
- System.out.println(" - EXPUNGE!");
+ handler.logInfo(" - EXPUNGE!");
}
}
if (change.isWithdrawn()) {
if (changed) {
- System.out.println(" - WITHDRAWN!");
+ handler.logInfo(" - WITHDRAWN!");
} else {
- System.out.println(" - WITHDRAW!");
+ handler.logInfo(" - WITHDRAW!");
}
}
if (change.isReinstated()) {
if (changed) {
- System.out.println(" - REINSTATED!");
+ handler.logInfo(" - REINSTATED!");
} else {
- System.out.println(" - REINSTATE!");
+ handler.logInfo(" - REINSTATE!");
}
}
@@ -1140,11 +1266,11 @@ public class MetadataImport {
String cHandle = c.getHandle();
String cName = c.getName();
if (!changed) {
- System.out.print(" + New owning collection (" + cHandle + "): ");
+ handler.logInfo(" + New owning collection (" + cHandle + "): ");
} else {
- System.out.print(" + New owning collection (" + cHandle + "): ");
+ handler.logInfo(" + New owning collection (" + cHandle + "): ");
}
- System.out.println(cName);
+ handler.logInfo(cName);
}
c = change.getOldOwningCollection();
@@ -1152,11 +1278,11 @@ public class MetadataImport {
String cHandle = c.getHandle();
String cName = c.getName();
if (!changed) {
- System.out.print(" + Old owning collection (" + cHandle + "): ");
+ handler.logInfo(" + Old owning collection (" + cHandle + "): ");
} else {
- System.out.print(" + Old owning collection (" + cHandle + "): ");
+ handler.logInfo(" + Old owning collection (" + cHandle + "): ");
}
- System.out.println(cName);
+ handler.logInfo(cName);
}
}
@@ -1165,11 +1291,11 @@ public class MetadataImport {
String cHandle = c.getHandle();
String cName = c.getName();
if (!changed) {
- System.out.print(" + Map to collection (" + cHandle + "): ");
+ handler.logInfo(" + Map to collection (" + cHandle + "): ");
} else {
- System.out.print(" + Mapped to collection (" + cHandle + "): ");
+ handler.logInfo(" + Mapped to collection (" + cHandle + "): ");
}
- System.out.println(cName);
+ handler.logInfo(cName);
}
// Show old mapped collections
@@ -1177,11 +1303,11 @@ public class MetadataImport {
String cHandle = c.getHandle();
String cName = c.getName();
if (!changed) {
- System.out.print(" + Un-map from collection (" + cHandle + "): ");
+ handler.logInfo(" + Un-map from collection (" + cHandle + "): ");
} else {
- System.out.print(" + Un-mapped from collection (" + cHandle + "): ");
+ handler.logInfo(" + Un-mapped from collection (" + cHandle + "): ");
}
- System.out.println(cName);
+ handler.logInfo(cName);
}
// Show additions
@@ -1194,16 +1320,15 @@ public class MetadataImport {
md += "[" + metadataValue.getLanguage() + "]";
}
if (!changed) {
- System.out.print(" + Add (" + md + "): ");
+ handler.logInfo(" + Add (" + md + "): ");
} else {
- System.out.print(" + Added (" + md + "): ");
+ handler.logInfo(" + Added (" + md + "): ");
}
- System.out.print(metadataValue.getValue());
+ handler.logInfo(metadataValue.getValue());
if (isAuthorityControlledField(md)) {
- System.out.print(", authority = " + metadataValue.getAuthority());
- System.out.print(", confidence = " + metadataValue.getConfidence());
+ handler.logInfo(", authority = " + metadataValue.getAuthority());
+ handler.logInfo(", confidence = " + metadataValue.getConfidence());
}
- System.out.println("");
}
// Show removals
@@ -1216,16 +1341,15 @@ public class MetadataImport {
md += "[" + metadataValue.getLanguage() + "]";
}
if (!changed) {
- System.out.print(" - Remove (" + md + "): ");
+ handler.logInfo(" - Remove (" + md + "): ");
} else {
- System.out.print(" - Removed (" + md + "): ");
+ handler.logInfo(" - Removed (" + md + "): ");
}
- System.out.print(metadataValue.getValue());
+ handler.logInfo(metadataValue.getValue());
if (isAuthorityControlledField(md)) {
- System.out.print(", authority = " + metadataValue.getAuthority());
- System.out.print(", confidence = " + metadataValue.getConfidence());
+ handler.logInfo(", authority = " + metadataValue.getAuthority());
+ handler.logInfo(", confidence = " + metadataValue.getConfidence());
}
- System.out.println("");
}
}
return changeCounter;
@@ -1243,7 +1367,7 @@ public class MetadataImport {
/**
* Set authority controlled fields
*/
- private static void setAuthorizedMetadataFields() {
+ private void setAuthorizedMetadataFields() {
authorityControlled = new HashSet();
Enumeration propertyNames = ConfigurationManager.getProperties().propertyNames();
while (propertyNames.hasMoreElements()) {
@@ -1255,191 +1379,6 @@ public class MetadataImport {
}
}
- /**
- * main method to run the metadata exporter
- *
- * @param argv the command line arguments given
- */
- public static void main(String[] argv) {
- // Create an options object and populate it
- CommandLineParser parser = new PosixParser();
-
- Options options = new Options();
-
- options.addOption("f", "file", true, "source file");
- options.addOption("e", "email", true, "email address or user id of user (required if adding new items)");
- options.addOption("s", "silent", false,
- "silent operation - doesn't request confirmation of changes USE WITH CAUTION");
- options.addOption("w", "workflow", false, "workflow - when adding new items, use collection workflow");
- options.addOption("n", "notify", false,
- "notify - when adding new items using a workflow, send notification emails");
- options.addOption("t", "template", false,
- "template - when adding new items, use the collection template (if it exists)");
- options.addOption("v", "validate-only", false,
- "validate - just validate the csv, don't run the import");
- options.addOption("h", "help", false, "help");
-
- // Parse the command line arguments
- CommandLine line;
- try {
- line = parser.parse(options, argv);
- } catch (ParseException pe) {
- System.err.println("Error parsing command line arguments: " + pe.getMessage());
- System.exit(1);
- return;
- }
-
- if (line.hasOption('h')) {
- printHelp(options, 0);
- }
-
- // Check a filename is given
- if (!line.hasOption('f')) {
- System.err.println("Required parameter -f missing!");
- printHelp(options, 1);
- }
- String filename = line.getOptionValue('f');
-
- // Option to apply template to new items
- boolean useTemplate = false;
- if (line.hasOption('t')) {
- useTemplate = true;
- }
-
- // Options for workflows, and workflow notifications for new items
- boolean useWorkflow = false;
- boolean workflowNotify = false;
- if (line.hasOption('w')) {
- useWorkflow = true;
- if (line.hasOption('n')) {
- workflowNotify = true;
- }
- } else if (line.hasOption('n')) {
- System.err.println("Invalid option 'n': (notify) can only be specified with the 'w' (workflow) option.");
- System.exit(1);
- }
-
- // Create a context
- Context c;
- try {
- c = new Context();
- c.turnOffAuthorisationSystem();
- } catch (Exception e) {
- System.err.println("Unable to create a new DSpace Context: " + e.getMessage());
- System.exit(1);
- return;
- }
-
- // Find the EPerson, assign to context
- try {
- if (line.hasOption('e')) {
- EPerson eperson;
- String e = line.getOptionValue('e');
- if (e.indexOf('@') != -1) {
- eperson = EPersonServiceFactory.getInstance().getEPersonService().findByEmail(c, e);
- } else {
- eperson = EPersonServiceFactory.getInstance().getEPersonService().find(c, UUID.fromString(e));
- }
-
- if (eperson == null) {
- System.out.println("Error, eperson cannot be found: " + e);
- System.exit(1);
- }
- c.setCurrentUser(eperson);
- }
- } catch (Exception e) {
- System.err.println("Unable to find DSpace user: " + e.getMessage());
- System.exit(1);
- return;
- }
-
- // Is this a silent run?
- boolean change = false;
-
- // Read lines from the CSV file
- DSpaceCSV csv;
- try {
- csv = new DSpaceCSV(new File(filename), c);
- } catch (MetadataImportInvalidHeadingException miihe) {
- System.err.println(miihe.getMessage());
- System.exit(1);
- return;
- } catch (Exception e) {
- System.err.println("Error reading file: " + e.getMessage());
- System.exit(1);
- return;
- }
-
- // Perform the first import - just highlight differences
- MetadataImport importer = new MetadataImport(c, csv);
- List changes;
-
- boolean validateOnly = line.hasOption('v');
-
- if (!line.hasOption('s') || validateOnly) {
- // See what has changed
- try {
- changes = importer.runImport(false, useWorkflow, workflowNotify, useTemplate);
- } catch (MetadataImportException mie) {
- System.err.println("Error: " + mie.getMessage());
- System.exit(1);
- return;
- }
-
- // Display the changes
- int changeCounter = displayChanges(changes, false);
-
- // If there were changes, ask if we should execute them
- if (!validateOnly && changeCounter > 0) {
- try {
- // Ask the user if they want to make the changes
- System.out.println("\n" + changeCounter + " item(s) will be changed\n");
- System.out.print("Do you want to make these changes? [y/n] ");
- String yn = (new BufferedReader(new InputStreamReader(System.in))).readLine();
- if ("y".equalsIgnoreCase(yn)) {
- change = true;
- } else {
- System.out.println("No data has been changed.");
- }
- } catch (IOException ioe) {
- System.err.println("Error: " + ioe.getMessage());
- System.err.println("No changes have been made");
- System.exit(1);
- }
- } else {
- System.out.println("There were no changes detected");
- }
- } else {
- change = true;
- }
-
- try {
- // If required, make the change
- if (change && !validateOnly) {
- try {
- // Make the changes
- changes = importer.runImport(true, useWorkflow, workflowNotify, useTemplate);
- } catch (MetadataImportException mie) {
- System.err.println("Error: " + mie.getMessage());
- System.exit(1);
- return;
- }
-
- // Display the changes
- displayChanges(changes, true);
- }
-
- // Finsh off and tidy up
- c.restoreAuthSystemState();
- c.complete();
- } catch (Exception e) {
- c.abort();
- System.err.println("Error committing changes to database: " + e.getMessage());
- System.err.println("Aborting most recent changes.");
- System.exit(1);
- }
- }
-
/**
* Gets a copy of the given csv line with all entity target references resolved to UUID strings.
* Keys being iterated over represent metadatafields or special columns to be processed.
@@ -1448,7 +1387,7 @@ public class MetadataImport {
* @return a copy, with all references resolved.
* @throws MetadataImportException if there is an error resolving any entity target reference.
*/
- public DSpaceCSVLine resolveEntityRefs(DSpaceCSVLine line) throws MetadataImportException {
+ public DSpaceCSVLine resolveEntityRefs(Context c, DSpaceCSVLine line) throws MetadataImportException {
DSpaceCSVLine newLine = new DSpaceCSVLine(line.getID());
UUID originId = evaluateOriginId(line.getID());
for (String key : line.keys()) {
@@ -1503,7 +1442,7 @@ public class MetadataImport {
originIds.add(originId);
typeNames.put(relationField, originIds);
} else {
- ArrayList originIds = typeNames.get(relationField);
+ ArrayList originIds = typeNames.get(relationField);
originIds.add(originId);
typeNames.put(relationField, originIds);
}
@@ -1533,7 +1472,7 @@ public class MetadataImport {
}
for (String key : line.keys()) {
if (key.contains(".") && !key.split("\\.")[0].equalsIgnoreCase("relation") ||
- key.equalsIgnoreCase("rowName")) {
+ key.equalsIgnoreCase("rowName")) {
for (String value : line.get(key)) {
String valueKey = key + ":" + value;
Set rowNums = csvRefMap.get(valueKey);
@@ -1575,20 +1514,20 @@ public class MetadataImport {
try {
return UUID.fromString(reference);
} catch (IllegalArgumentException e) {
- throw new MetadataImportException("Error in CSV row " + rowCount + ":\n" +
- "Not a UUID or indirect entity reference: '" + reference + "'");
+ throw new MetadataImportException("Error in CSV row " + rowCount + ":\n" +
+ "Not a UUID or indirect entity reference: '" + reference + "'");
}
- } else if (!reference.startsWith("rowName:") ) { // Not a rowName ref; so it's a metadata value reference
+ } else if (!reference.startsWith("rowName:")) { // Not a rowName ref; so it's a metadata value reference
MetadataValueService metadataValueService = ContentServiceFactory.getInstance().getMetadataValueService();
MetadataFieldService metadataFieldService =
- ContentServiceFactory.getInstance().getMetadataFieldService();
+ ContentServiceFactory.getInstance().getMetadataFieldService();
int i = reference.indexOf(":");
String mfValue = reference.substring(i + 1);
String mf[] = reference.substring(0, i).split("\\.");
if (mf.length < 2) {
- throw new MetadataImportException("Error in CSV row " + rowCount + ":\n" +
- "Bad metadata field in reference: '" + reference
- + "' (expected syntax is schema.element[.qualifier])");
+ throw new MetadataImportException("Error in CSV row " + rowCount + ":\n" +
+ "Bad metadata field in reference: '" + reference
+ + "' (expected syntax is schema.element[.qualifier])");
}
String schema = mf[0];
String element = mf[1];
@@ -1600,13 +1539,13 @@ public class MetadataImport {
MetadataValue mdvVal = mdv.next();
uuid = mdvVal.getDSpaceObject().getID();
if (mdv.hasNext()) {
- throw new MetadataImportException("Error in CSV row " + rowCount + ":\n" +
- "Ambiguous reference; multiple matches in db: " + reference);
+ throw new MetadataImportException("Error in CSV row " + rowCount + ":\n" +
+ "Ambiguous reference; multiple matches in db: " + reference);
}
}
} catch (SQLException e) {
- throw new MetadataImportException("Error in CSV row " + rowCount + ":\n" +
- "Error looking up item by metadata reference: " + reference, e);
+ throw new MetadataImportException("Error in CSV row " + rowCount + ":\n" +
+ "Error looking up item by metadata reference: " + reference, e);
}
}
// Lookup UUIDs that may have already been processed into the csvRefMap
@@ -1614,24 +1553,25 @@ public class MetadataImport {
// See getMatchingCSVUUIDs() for how the reference param is sourced from the csvRefMap
Set csvUUIDs = getMatchingCSVUUIDs(reference);
if (csvUUIDs.size() > 1) {
- throw new MetadataImportException("Error in CSV row " + rowCount + ":\n" +
- "Ambiguous reference; multiple matches in csv: " + reference);
+ throw new MetadataImportException("Error in CSV row " + rowCount + ":\n" +
+ "Ambiguous reference; multiple matches in csv: " + reference);
} else if (csvUUIDs.size() == 1) {
UUID csvUUID = csvUUIDs.iterator().next();
if (csvUUID.equals(uuid)) {
return uuid; // one match from csv and db (same item)
} else if (uuid != null) {
- throw new MetadataImportException("Error in CSV row " + rowCount + ":\n" +
- "Ambiguous reference; multiple matches in db and csv: " + reference);
+ throw new MetadataImportException("Error in CSV row " + rowCount + ":\n" +
+ "Ambiguous reference; multiple matches in db and csv: " + reference);
} else {
return csvUUID; // one match from csv
}
} else { // size == 0; the reference does not exist throw an error
if (uuid == null) {
- throw new MetadataImportException("Error in CSV row " + rowCount + ":\n" +
- "No matches found for reference: " + reference
- + "\nKeep in mind you can only reference entries that are listed before " +
- "this one within the CSV.");
+ throw new MetadataImportException("Error in CSV row " + rowCount + ":\n" +
+ "No matches found for reference: " + reference
+ + "\nKeep in mind you can only reference entries that are " +
+ "listed before " +
+ "this one within the CSV.");
} else {
return uuid; // one match from db
}
@@ -1688,14 +1628,16 @@ public class MetadataImport {
* Validate every relation modification expressed in the CSV.
*
*/
- private void validateExpressedRelations() throws MetadataImportException {
+ private void validateExpressedRelations(Context c) throws MetadataImportException {
for (String targetUUID : entityRelationMap.keySet()) {
String targetType = null;
try {
// Get the type of reference. Attempt lookup in processed map first before looking in archive.
if (entityTypeMap.get(UUID.fromString(targetUUID)) != null) {
targetType = entityTypeService.
- findByEntityType(c, entityTypeMap.get(UUID.fromString(targetUUID))).getLabel();
+ findByEntityType(c,
+ entityTypeMap.get(UUID.fromString(targetUUID)))
+ .getLabel();
} else {
// Target item may be archived; check there.
// Add to errors if Realtionship.type cannot be derived
@@ -1703,18 +1645,19 @@ public class MetadataImport {
if (itemService.find(c, UUID.fromString(targetUUID)) != null) {
targetItem = itemService.find(c, UUID.fromString(targetUUID));
List relTypes = itemService.
- getMetadata(targetItem, "relationship", "type", null, Item.ANY);
+ getMetadata(targetItem, "relationship", "type",
+ null, Item.ANY);
String relTypeValue = null;
if (relTypes.size() > 0) {
relTypeValue = relTypes.get(0).getValue();
targetType = entityTypeService.findByEntityType(c, relTypeValue).getLabel();
} else {
relationValidationErrors.add("Cannot resolve Entity type for target UUID: " +
- targetUUID);
+ targetUUID);
}
} else {
relationValidationErrors.add("Cannot resolve Entity type for target UUID: " +
- targetUUID);
+ targetUUID);
}
}
if (targetType == null) {
@@ -1739,7 +1682,7 @@ public class MetadataImport {
// Attempt lookup in processed map first before looking in archive.
if (entityTypeMap.get(UUID.fromString(originRefererUUID)) != null) {
originType = entityTypeMap.get(UUID.fromString(originRefererUUID));
- validateTypesByTypeByTypeName(targetType, originType, typeName, originRow);
+ validateTypesByTypeByTypeName(c, targetType, originType, typeName, originRow);
} else {
// Origin item may be archived; check there.
// Add to errors if Realtionship.type cannot be derived.
@@ -1747,22 +1690,23 @@ public class MetadataImport {
if (itemService.find(c, UUID.fromString(targetUUID)) != null) {
originItem = itemService.find(c, UUID.fromString(originRefererUUID));
List relTypes = itemService.
- getMetadata(originItem, "relationship", "type", null, Item.ANY);
+ getMetadata(originItem, "relationship",
+ "type", null, Item.ANY);
String relTypeValue = null;
if (relTypes.size() > 0) {
relTypeValue = relTypes.get(0).getValue();
originType = entityTypeService.findByEntityType(c, relTypeValue).getLabel();
- validateTypesByTypeByTypeName(targetType, originType, typeName, originRow);
+ validateTypesByTypeByTypeName(c, targetType, originType, typeName, originRow);
} else {
relationValidationErrors.add("Error on CSV row " + originRow + ":" + "\n" +
- "Cannot resolve Entity type for reference: "
- + originRefererUUID);
+ "Cannot resolve Entity type for reference: "
+ + originRefererUUID);
}
} else {
relationValidationErrors.add("Error on CSV row " + originRow + ":" + "\n" +
- "Cannot resolve Entity type for reference: "
- + originRefererUUID + " in row: " + originRow );
+ "Cannot resolve Entity type for reference: "
+ + originRefererUUID + " in row: " + originRow);
}
}
}
@@ -1791,20 +1735,22 @@ public class MetadataImport {
* @param typeName left or right typeName of the respective Relationship.
* @return the UUID of the item.
*/
- private void validateTypesByTypeByTypeName(String targetType, String originType, String typeName, String originRow)
- throws MetadataImportException {
+ private void validateTypesByTypeByTypeName(Context c,
+ String targetType, String originType, String typeName, String originRow)
+ throws MetadataImportException {
try {
RelationshipType foundRelationshipType = null;
List relationshipTypeList = relationshipTypeService.
- findByLeftwardOrRightwardTypeName(c, typeName.split("\\.")[1]);
+ findByLeftwardOrRightwardTypeName(
+ c, typeName.split("\\.")[1]);
// Validate described relationship form the CSV.
foundRelationshipType = matchRelationshipType(relationshipTypeList, targetType, originType, typeName);
if (foundRelationshipType == null) {
relationValidationErrors.add("Error on CSV row " + originRow + ":" + "\n" +
- "No Relationship type found for:\n" +
- "Target type: " + targetType + "\n" +
- "Origin referer type: " + originType + "\n" +
- "with typeName: " + typeName + " for type: " + originType);
+ "No Relationship type found for:\n" +
+ "Target type: " + targetType + "\n" +
+ "Origin referer type: " + originType + "\n" +
+ "with typeName: " + typeName + " for type: " + originType);
}
} catch (SQLException sqle) {
throw new MetadataImportException("Error interacting with database!", sqle);
@@ -1837,7 +1783,7 @@ public class MetadataImport {
continue;
}
if (relationshipType.getLeftType().getLabel().equalsIgnoreCase(originType) &&
- relationshipType.getRightType().getLabel().equalsIgnoreCase(targetType)) {
+ relationshipType.getRightType().getLabel().equalsIgnoreCase(targetType)) {
foundRelationshipType = relationshipType;
}
} else {
@@ -1845,7 +1791,7 @@ public class MetadataImport {
continue;
}
if (relationshipType.getLeftType().getLabel().equalsIgnoreCase(targetType) &&
- relationshipType.getRightType().getLabel().equalsIgnoreCase(originType)) {
+ relationshipType.getRightType().getLabel().equalsIgnoreCase(originType)) {
foundRelationshipType = relationshipType;
}
}
@@ -1853,4 +1799,4 @@ public class MetadataImport {
return foundRelationshipType;
}
-}
+}
\ No newline at end of file
diff --git a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImportCLI.java b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImportCLI.java
new file mode 100644
index 0000000000..c319c26971
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImportCLI.java
@@ -0,0 +1,68 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.app.bulkedit;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.UUID;
+
+import org.apache.commons.cli.ParseException;
+import org.dspace.core.Context;
+import org.dspace.eperson.EPerson;
+import org.dspace.eperson.factory.EPersonServiceFactory;
+import org.dspace.scripts.handler.DSpaceRunnableHandler;
+
+/**
+ * CLI variant for the {@link MetadataImport} class
+ * This has been made so that we can specify the behaviour of the determineChanges method to be specific for the CLI
+ */
+public class MetadataImportCLI extends MetadataImport {
+
+ @Override
+ protected boolean determineChange(DSpaceRunnableHandler handler) throws IOException {
+ handler.logInfo("Do you want to make these changes? [y/n] ");
+ try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in))) {
+ String yn = bufferedReader.readLine();
+ if ("y".equalsIgnoreCase(yn)) {
+ return true;
+ }
+ return false;
+ }
+ }
+
+ @Override
+ protected void assignCurrentUserInContext(Context context) throws ParseException {
+ try {
+ if (commandLine.hasOption('e')) {
+ EPerson eperson;
+ String e = commandLine.getOptionValue('e');
+ if (e.indexOf('@') != -1) {
+ eperson = EPersonServiceFactory.getInstance().getEPersonService().findByEmail(context, e);
+ } else {
+ eperson = EPersonServiceFactory.getInstance().getEPersonService().find(context, UUID.fromString(e));
+ }
+
+ if (eperson == null) {
+ throw new ParseException("Error, eperson cannot be found: " + e);
+ }
+ context.setCurrentUser(eperson);
+ }
+ } catch (Exception e) {
+ throw new ParseException("Unable to find DSpace user: " + e.getMessage());
+ }
+ }
+
+ @Override
+ public void setup() throws ParseException {
+ super.setup();
+ if (!commandLine.hasOption('e')) {
+ throw new ParseException("Required parameter -e missing!");
+ }
+ }
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImportCliScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImportCliScriptConfiguration.java
new file mode 100644
index 0000000000..038df616ca
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImportCliScriptConfiguration.java
@@ -0,0 +1,27 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.app.bulkedit;
+
+import org.apache.commons.cli.Options;
+import org.dspace.scripts.configuration.ScriptConfiguration;
+
+/**
+ * The {@link ScriptConfiguration} for the {@link org.dspace.app.bulkedit.MetadataImportCLI} CLI script
+ */
+public class MetadataImportCliScriptConfiguration extends MetadataImportScriptConfiguration {
+
+ @Override
+ public Options getOptions() {
+ Options options = super.getOptions();
+ options.addOption("e", "email", true, "email address or user id of user (required if adding new items)");
+ options.getOption("e").setType(String.class);
+ options.getOption("e").setRequired(true);
+ super.options = options;
+ return options;
+ }
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImportScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImportScriptConfiguration.java
new file mode 100644
index 0000000000..07e6a9aec9
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImportScriptConfiguration.java
@@ -0,0 +1,81 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.app.bulkedit;
+
+import java.io.InputStream;
+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;
+
+/**
+ * The {@link ScriptConfiguration} for the {@link MetadataImport} script
+ */
+public class MetadataImportScriptConfiguration extends ScriptConfiguration {
+
+ @Autowired
+ private AuthorizeService authorizeService;
+
+ private Class dspaceRunnableClass;
+
+ @Override
+ public Class getDspaceRunnableClass() {
+ return dspaceRunnableClass;
+ }
+
+ /**
+ * Generic setter for the dspaceRunnableClass
+ * @param dspaceRunnableClass The dspaceRunnableClass to be set on this MetadataImportScriptConfiguration
+ */
+ @Override
+ public void setDspaceRunnableClass(Class dspaceRunnableClass) {
+ this.dspaceRunnableClass = dspaceRunnableClass;
+ }
+
+ @Override
+ public boolean isAllowedToExecute(Context context) {
+ try {
+ return authorizeService.isAdmin(context);
+ } catch (SQLException e) {
+ throw new RuntimeException("SQLException occurred when checking if the current user is an admin", e);
+ }
+ }
+
+ @Override
+ public Options getOptions() {
+ if (options == null) {
+ Options options = new Options();
+
+ options.addOption("f", "file", true, "source file");
+ options.getOption("f").setType(InputStream.class);
+ options.getOption("f").setRequired(true);
+ options.addOption("s", "silent", false,
+ "silent operation - doesn't request confirmation of changes USE WITH CAUTION");
+ options.getOption("s").setType(boolean.class);
+ options.addOption("w", "workflow", false, "workflow - when adding new items, use collection workflow");
+ options.getOption("w").setType(boolean.class);
+ options.addOption("n", "notify", false,
+ "notify - when adding new items using a workflow, send notification emails");
+ options.getOption("n").setType(boolean.class);
+ options.addOption("v", "validate-only", false,
+ "validate - just validate the csv, don't run the import");
+ options.getOption("v").setType(boolean.class);
+ options.addOption("t", "template", false,
+ "template - when adding new items, use the collection template (if it exists)");
+ options.getOption("t").setType(boolean.class);
+ options.addOption("h", "help", false, "help");
+ options.getOption("h").setType(boolean.class);
+
+ super.options = options;
+ }
+ return options;
+ }
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportServiceImpl.java
index 12fcd84d04..13aa236f54 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
@@ -1519,6 +1519,12 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea
if (!dir.exists() && !dir.mkdirs()) {
log.error("Unable to create directory: " + dir.getAbsolutePath());
}
+ // Verify that the directory the entry is using is a subpath of zipDir (and not somewhere else!)
+ if (!dir.toPath().normalize().startsWith(zipDir)) {
+ throw new IOException("Bad zip entry: '" + entry.getName()
+ + "' in file '" + zipfile.getAbsolutePath() + "'!"
+ + " Cannot process this file.");
+ }
//Entries could have too many directories, and we need to adjust the sourcedir
// file1.zip (SimpleArchiveFormat / item1 / contents|dublin_core|...
@@ -1539,9 +1545,16 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea
}
byte[] buffer = new byte[1024];
int len;
+ File outFile = new File(zipDir + entry.getName());
+ // Verify that this file will be created in our zipDir (and not somewhere else!)
+ if (!outFile.toPath().normalize().startsWith(zipDir)) {
+ throw new IOException("Bad zip entry: '" + entry.getName()
+ + "' in file '" + zipfile.getAbsolutePath() + "'!"
+ + " Cannot process this file.");
+ }
InputStream in = zf.getInputStream(entry);
BufferedOutputStream out = new BufferedOutputStream(
- new FileOutputStream(zipDir + entry.getName()));
+ new FileOutputStream(outFile));
while ((len = in.read(buffer)) >= 0) {
out.write(buffer, 0, len);
}
diff --git a/dspace-api/src/main/java/org/dspace/app/launcher/ScriptLauncher.java b/dspace-api/src/main/java/org/dspace/app/launcher/ScriptLauncher.java
index ef6b0b538e..6ee62bd904 100644
--- a/dspace-api/src/main/java/org/dspace/app/launcher/ScriptLauncher.java
+++ b/dspace-api/src/main/java/org/dspace/app/launcher/ScriptLauncher.java
@@ -16,9 +16,11 @@ import java.util.TreeMap;
import org.apache.commons.cli.ParseException;
import org.apache.log4j.Logger;
import org.dspace.scripts.DSpaceRunnable;
+import org.dspace.scripts.configuration.ScriptConfiguration;
import org.dspace.scripts.factory.ScriptServiceFactory;
import org.dspace.scripts.handler.DSpaceRunnableHandler;
import org.dspace.scripts.handler.impl.CommandLineDSpaceRunnableHandler;
+import org.dspace.scripts.service.ScriptService;
import org.dspace.servicemanager.DSpaceKernelImpl;
import org.dspace.servicemanager.DSpaceKernelInit;
import org.dspace.services.RequestService;
@@ -44,7 +46,8 @@ public class ScriptLauncher {
/**
* Default constructor
*/
- private ScriptLauncher() { }
+ private ScriptLauncher() {
+ }
/**
* Execute the DSpace script launcher
@@ -54,7 +57,7 @@ public class ScriptLauncher {
* @throws FileNotFoundException if file doesn't exist
*/
public static void main(String[] args)
- throws FileNotFoundException, IOException {
+ throws FileNotFoundException, IOException, IllegalAccessException, InstantiationException {
// Initialise the service manager kernel
try {
kernelImpl = DSpaceKernelInit.getKernel(null);
@@ -107,13 +110,18 @@ public class ScriptLauncher {
* @param commandConfigs The Document
* @param dSpaceRunnableHandler The DSpaceRunnableHandler for this execution
* @param kernelImpl The relevant DSpaceKernelImpl
- * @return A 1 or 0 depending on whether the script failed or passed respectively
+ * @return A 1 or 0 depending on whether the script failed or passed respectively
*/
public static int handleScript(String[] args, Document commandConfigs,
- DSpaceRunnableHandler dSpaceRunnableHandler,
- DSpaceKernelImpl kernelImpl) {
+ DSpaceRunnableHandler dSpaceRunnableHandler,
+ DSpaceKernelImpl kernelImpl) throws InstantiationException, IllegalAccessException {
int status;
- DSpaceRunnable script = ScriptServiceFactory.getInstance().getScriptService().getScriptForName(args[0]);
+ ScriptService scriptService = ScriptServiceFactory.getInstance().getScriptService();
+ ScriptConfiguration scriptConfiguration = scriptService.getScriptConfiguration(args[0]);
+ DSpaceRunnable script = null;
+ if (scriptConfiguration != null) {
+ script = scriptService.createDSpaceRunnableForScriptConfiguration(scriptConfiguration);
+ }
if (script != null) {
status = executeScript(args, dSpaceRunnableHandler, script);
} else {
@@ -127,12 +135,12 @@ public class ScriptLauncher {
* @param args The arguments of the script with the script name as first place in the array
* @param dSpaceRunnableHandler The relevant DSpaceRunnableHandler
* @param script The script to be executed
- * @return A 1 or 0 depending on whether the script failed or passed respectively
+ * @return A 1 or 0 depending on whether the script failed or passed respectively
*/
private static int executeScript(String[] args, DSpaceRunnableHandler dSpaceRunnableHandler,
DSpaceRunnable script) {
try {
- script.initialize(args, dSpaceRunnableHandler);
+ script.initialize(args, dSpaceRunnableHandler, null);
script.run();
return 0;
} catch (ParseException e) {
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 bba0913193..9b66030e90 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
@@ -19,6 +19,15 @@ import org.dspace.core.Context;
* @author Andrea Bollini
*/
public interface RequestItemAuthorExtractor {
- public RequestItemAuthor getRequestItemAuthor(Context context, Item item)
- throws SQLException;
+
+ /**
+ * Retrieve the auhtor to contact for a request copy of the give item.
+ *
+ * @param context DSpace context object
+ * @param item item to request
+ * @return An object containing name an email address to send the request to
+ * or null if no valid email address was found.
+ * @throws SQLException if database error
+ */
+ public RequestItemAuthor getRequestItemAuthor(Context context, Item item) throws SQLException;
}
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 a5f7341039..5d22efaa7a 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
@@ -74,8 +74,8 @@ public class RequestItemHelpdeskStrategy extends RequestItemSubmitterStrategy {
return new RequestItemAuthor(helpdeskEPerson);
} else {
String helpdeskName = I18nUtil.getMessage(
- "org.dspace.app.requestitem.RequestItemHelpdeskStrategy.helpdeskname",
- context);
+ "org.dspace.app.requestitem.RequestItemHelpdeskStrategy.helpdeskname",
+ context);
return new RequestItemAuthor(helpdeskName, helpDeskEmail);
}
}
diff --git a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemMetadataStrategy.java b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemMetadataStrategy.java
index 4d2f78408a..9838e58697 100644
--- a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemMetadataStrategy.java
+++ b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemMetadataStrategy.java
@@ -16,6 +16,7 @@ import org.dspace.content.MetadataValue;
import org.dspace.content.service.ItemService;
import org.dspace.core.Context;
import org.dspace.core.I18nUtil;
+import org.dspace.services.factory.DSpaceServicesFactory;
import org.springframework.beans.factory.annotation.Autowired;
/**
@@ -38,6 +39,7 @@ public class RequestItemMetadataStrategy extends RequestItemSubmitterStrategy {
@Override
public RequestItemAuthor getRequestItemAuthor(Context context, Item item)
throws SQLException {
+ RequestItemAuthor author = null;
if (emailMetadata != null) {
List vals = itemService.getMetadataByMetadataString(item, emailMetadata);
if (vals.size() > 0) {
@@ -49,19 +51,38 @@ public class RequestItemMetadataStrategy extends RequestItemSubmitterStrategy {
fullname = nameVals.iterator().next().getValue();
}
}
-
if (StringUtils.isBlank(fullname)) {
fullname = I18nUtil
- .getMessage(
- "org.dspace.app.requestitem.RequestItemMetadataStrategy.unnamed",
- context);
+ .getMessage(
+ "org.dspace.app.requestitem.RequestItemMetadataStrategy.unnamed",
+ context);
}
- RequestItemAuthor author = new RequestItemAuthor(
- fullname, email);
+ author = new RequestItemAuthor(fullname, email);
return author;
}
+ } else {
+ // Uses the basic strategy to look for the original submitter
+ author = super.getRequestItemAuthor(context, item);
+ // Is the author or his email null, so get the help desk or admin name and email
+ if (null == author || null == author.getEmail()) {
+ String email = null;
+ String name = null;
+ //First get help desk name and email
+ email = DSpaceServicesFactory.getInstance()
+ .getConfigurationService().getProperty("mail.helpdesk");
+ name = DSpaceServicesFactory.getInstance()
+ .getConfigurationService().getProperty("mail.helpdesk.name");
+ // If help desk mail is null get the mail and name of admin
+ if (email == null) {
+ email = DSpaceServicesFactory.getInstance()
+ .getConfigurationService().getProperty("mail.admin");
+ name = DSpaceServicesFactory.getInstance()
+ .getConfigurationService().getProperty("mail.admin.name");
+ }
+ author = new RequestItemAuthor(name, email);
+ }
}
- return super.getRequestItemAuthor(context, item);
+ return author;
}
public void setEmailMetadata(String emailMetadata) {
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 8ed6238a8c..2708c24ba9 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
@@ -23,13 +23,22 @@ public class RequestItemSubmitterStrategy implements RequestItemAuthorExtractor
public RequestItemSubmitterStrategy() {
}
+ /**
+ * Returns the submitter of an Item as RequestItemAuthor or null if the
+ * Submitter is deleted.
+ *
+ * @return The submitter of the item or null if the submitter is deleted
+ * @throws SQLException if database error
+ */
@Override
public RequestItemAuthor getRequestItemAuthor(Context context, Item item)
throws SQLException {
EPerson submitter = item.getSubmitter();
- RequestItemAuthor author = new RequestItemAuthor(
- submitter.getFullName(), submitter.getEmail());
+ RequestItemAuthor author = null;
+ if (null != submitter) {
+ author = new RequestItemAuthor(
+ submitter.getFullName(), submitter.getEmail());
+ }
return author;
}
-
}
diff --git a/dspace-api/src/main/java/org/dspace/app/sherpa/SHERPAResponse.java b/dspace-api/src/main/java/org/dspace/app/sherpa/SHERPAResponse.java
index c5b8bbebf3..bd2909c0c1 100644
--- a/dspace-api/src/main/java/org/dspace/app/sherpa/SHERPAResponse.java
+++ b/dspace-api/src/main/java/org/dspace/app/sherpa/SHERPAResponse.java
@@ -48,6 +48,9 @@ public class SHERPAResponse {
factory.setValidating(false);
factory.setIgnoringComments(true);
factory.setIgnoringElementContentWhitespace(true);
+ // disallow DTD parsing to ensure no XXE attacks can occur.
+ // See https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html
+ factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
DocumentBuilder db = factory.newDocumentBuilder();
Document inDoc = db.parse(xmlData);
diff --git a/dspace-api/src/main/java/org/dspace/app/sitemap/GenerateSitemaps.java b/dspace-api/src/main/java/org/dspace/app/sitemap/GenerateSitemaps.java
index bb35cd3ff9..e2743951e7 100644
--- a/dspace-api/src/main/java/org/dspace/app/sitemap/GenerateSitemaps.java
+++ b/dspace-api/src/main/java/org/dspace/app/sitemap/GenerateSitemaps.java
@@ -27,6 +27,7 @@ import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.cli.PosixParser;
+import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.Logger;
@@ -84,6 +85,9 @@ public class GenerateSitemaps {
options
.addOption("p", "ping", true,
"ping specified search engine URL");
+ options
+ .addOption("d", "delete", false,
+ "delete sitemaps dir and its contents");
CommandLine line = null;
@@ -105,10 +109,9 @@ public class GenerateSitemaps {
}
/*
- * Sanity check -- if no sitemap generation or pinging to do, print
- * usage
+ * Sanity check -- if no sitemap generation or pinging to do, or deletion, print usage
*/
- if (line.getArgs().length != 0 || line.hasOption('b')
+ if (line.getArgs().length != 0 || line.hasOption('d') || line.hasOption('b')
&& line.hasOption('s') && !line.hasOption('g')
&& !line.hasOption('m') && !line.hasOption('y')
&& !line.hasOption('p')) {
@@ -123,6 +126,10 @@ public class GenerateSitemaps {
generateSitemaps(!line.hasOption('b'), !line.hasOption('s'));
}
+ if (line.hasOption('d')) {
+ deleteSitemaps();
+ }
+
if (line.hasOption('a')) {
pingConfiguredSearchEngines();
}
@@ -140,6 +147,29 @@ public class GenerateSitemaps {
System.exit(0);
}
+ /**
+ * Runs generate-sitemaps without any params for the scheduler (task-scheduler.xml).
+ *
+ * @throws SQLException if a database error occurs.
+ * @throws IOException if IO error occurs.
+ */
+ public static void generateSitemapsScheduled() throws IOException, SQLException {
+ generateSitemaps(true, true);
+ }
+
+ /**
+ * Delete the sitemaps directory and its contents if it exists
+ * @throws IOException if IO error occurs
+ */
+ public static void deleteSitemaps() throws IOException {
+ File outputDir = new File(configurationService.getProperty("sitemap.dir"));
+ if (!outputDir.exists() && !outputDir.isDirectory()) {
+ log.error("Unable to delete sitemaps directory, doesn't exist or isn't a directort");
+ } else {
+ FileUtils.deleteDirectory(outputDir);
+ }
+ }
+
/**
* Generate sitemap.org protocol and/or basic HTML sitemaps.
*
@@ -150,14 +180,9 @@ public class GenerateSitemaps {
* @throws IOException if IO error
* if IO error occurs.
*/
- public static void generateSitemaps(boolean makeHTMLMap,
- boolean makeSitemapOrg) throws SQLException, IOException {
- String sitemapStem = configurationService.getProperty("dspace.ui.url")
- + "/sitemap";
- String htmlMapStem = configurationService.getProperty("dspace.ui.url")
- + "/htmlmap";
- String handleURLStem = configurationService.getProperty("dspace.ui.url")
- + "/handle/";
+ public static void generateSitemaps(boolean makeHTMLMap, boolean makeSitemapOrg) throws SQLException, IOException {
+ String uiURLStem = configurationService.getProperty("dspace.ui.url");
+ String sitemapStem = uiURLStem + "/sitemap";
File outputDir = new File(configurationService.getProperty("sitemap.dir"));
if (!outputDir.exists() && !outputDir.mkdir()) {
@@ -168,13 +193,11 @@ public class GenerateSitemaps {
AbstractGenerator sitemapsOrg = null;
if (makeHTMLMap) {
- html = new HTMLSitemapGenerator(outputDir, htmlMapStem + "?map=",
- null);
+ html = new HTMLSitemapGenerator(outputDir, sitemapStem, ".html");
}
if (makeSitemapOrg) {
- sitemapsOrg = new SitemapsOrgGenerator(outputDir, sitemapStem
- + "?map=", null);
+ sitemapsOrg = new SitemapsOrgGenerator(outputDir, sitemapStem, ".xml");
}
Context c = new Context(Context.Mode.READ_ONLY);
@@ -182,7 +205,7 @@ public class GenerateSitemaps {
List comms = communityService.findAll(c);
for (Community comm : comms) {
- String url = handleURLStem + comm.getHandle();
+ String url = uiURLStem + "/communities/" + comm.getID();
if (makeHTMLMap) {
html.addURL(url, null);
@@ -197,7 +220,7 @@ public class GenerateSitemaps {
List colls = collectionService.findAll(c);
for (Collection coll : colls) {
- String url = handleURLStem + coll.getHandle();
+ String url = uiURLStem + "/collections/" + coll.getID();
if (makeHTMLMap) {
html.addURL(url, null);
@@ -214,7 +237,7 @@ public class GenerateSitemaps {
while (allItems.hasNext()) {
Item i = allItems.next();
- String url = handleURLStem + i.getHandle();
+ String url = uiURLStem + "/items/" + i.getID();
Date lastMod = i.getLastModified();
if (makeHTMLMap) {
diff --git a/dspace-api/src/main/java/org/dspace/app/sitemap/SitemapsOrgGenerator.java b/dspace-api/src/main/java/org/dspace/app/sitemap/SitemapsOrgGenerator.java
index 9a0d5a6ba4..3ec4ca8239 100644
--- a/dspace-api/src/main/java/org/dspace/app/sitemap/SitemapsOrgGenerator.java
+++ b/dspace-api/src/main/java/org/dspace/app/sitemap/SitemapsOrgGenerator.java
@@ -59,7 +59,7 @@ public class SitemapsOrgGenerator extends AbstractGenerator {
@Override
public String getFilename(int number) {
- return "sitemap" + number + ".xml.gz";
+ return "sitemap" + number + ".xml";
}
@Override
@@ -100,12 +100,12 @@ public class SitemapsOrgGenerator extends AbstractGenerator {
@Override
public boolean useCompression() {
- return true;
+ return false;
}
@Override
public String getIndexFilename() {
- return "sitemap_index.xml.gz";
+ return "sitemap_index.xml";
}
@Override
diff --git a/dspace-api/src/main/java/org/dspace/app/util/AuthorizeUtil.java b/dspace-api/src/main/java/org/dspace/app/util/AuthorizeUtil.java
index 6c4271e1f2..efd813d29b 100644
--- a/dspace-api/src/main/java/org/dspace/app/util/AuthorizeUtil.java
+++ b/dspace-api/src/main/java/org/dspace/app/util/AuthorizeUtil.java
@@ -9,7 +9,10 @@ package org.dspace.app.util;
import java.sql.SQLException;
import java.util.List;
+import javax.servlet.http.HttpServletRequest;
+import org.apache.logging.log4j.Logger;
+import org.dspace.authenticate.factory.AuthenticateServiceFactory;
import org.dspace.authorize.AuthorizeConfiguration;
import org.dspace.authorize.AuthorizeException;
import org.dspace.authorize.ResourcePolicy;
@@ -19,12 +22,22 @@ import org.dspace.content.Bitstream;
import org.dspace.content.Bundle;
import org.dspace.content.Collection;
import org.dspace.content.Community;
+import org.dspace.content.DSpaceObject;
import org.dspace.content.Item;
import org.dspace.content.factory.ContentServiceFactory;
import org.dspace.content.service.CollectionService;
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.factory.EPersonServiceFactory;
+import org.dspace.eperson.service.GroupService;
+import org.dspace.services.factory.DSpaceServicesFactory;
+import org.dspace.utils.DSpace;
+import org.dspace.xmlworkflow.factory.XmlWorkflowServiceFactory;
+import org.dspace.xmlworkflow.storedcomponents.CollectionRole;
+import org.dspace.xmlworkflow.storedcomponents.service.CollectionRoleService;
/**
* This class is an addition to the AuthorizeManager that perform authorization
@@ -34,6 +47,7 @@ import org.dspace.core.Context;
*/
public class AuthorizeUtil {
+ private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(AuthorizeUtil.class);
/**
* Default constructor
*/
@@ -525,4 +539,154 @@ public class AuthorizeUtil {
}
}
}
+
+ /**
+ * This method will check whether the current user is authorized to manage the default read group
+ * @param context The relevant DSpace context
+ * @param collection The collection for which this will be checked
+ * @throws AuthorizeException If something goes wrong
+ * @throws SQLException If something goes wrong
+ */
+ public static void authorizeManageDefaultReadGroup(Context context,
+ Collection collection) throws AuthorizeException, SQLException {
+ AuthorizeService authorizeService = AuthorizeServiceFactory.getInstance().getAuthorizeService();
+ authorizeService.authorizeAction(context, collection, Constants.ADMIN);
+ }
+
+ /**
+ * This method checks whether the current user has sufficient rights to modify the group.
+ * Depending on the kind of group and due to delegated administration, separate checks need to be done to verify
+ * whether the user is allowed to modify the group.
+ *
+ * @param context the context of which the user will be checked
+ * @param group the group to be checked
+ * @throws SQLException
+ * @throws AuthorizeException
+ */
+ public static void authorizeManageGroup(Context context, Group group) throws SQLException, AuthorizeException {
+ AuthorizeService authorizeService = AuthorizeServiceFactory.getInstance().getAuthorizeService();
+ GroupService groupService = EPersonServiceFactory.getInstance().getGroupService();
+ CollectionRoleService collectionRoleService = XmlWorkflowServiceFactory.getInstance()
+ .getCollectionRoleService();
+ if (authorizeService.isAdmin(context)) {
+ return;
+ }
+
+ DSpaceObject parentObject = groupService.getParentObject(context, group);
+ if (parentObject == null) {
+ throw new AuthorizeException("not authorized to manage this group");
+ }
+ if (parentObject.getType() == Constants.COLLECTION) {
+ Collection collection = (Collection) parentObject;
+
+ if (group.equals(collection.getSubmitters())) {
+ authorizeManageSubmittersGroup(context, collection);
+ return;
+ }
+
+
+ List collectionRoles = collectionRoleService.findByCollection(context, collection);
+ for (CollectionRole role : collectionRoles) {
+ if (group.equals(role.getGroup())) {
+ authorizeManageWorkflowsGroup(context, collection);
+ return;
+ }
+ }
+
+ if (group.equals(collection.getAdministrators())) {
+ authorizeManageAdminGroup(context, collection);
+ return;
+ }
+ // if we reach this point, it means that the group is related
+ // to a collection but as it is not the submitters, nor the administrators,
+ // nor a workflow groups it must be a default item/bitstream groups
+ authorizeManageDefaultReadGroup(context, collection);
+ return;
+ }
+ if (parentObject.getType() == Constants.COMMUNITY) {
+ Community community = (Community) parentObject;
+ authorizeManageAdminGroup(context, community);
+ return;
+ }
+
+ throw new AuthorizeException("not authorized to manage this group");
+ }
+
+ /**
+ * This method will return a boolean indicating whether the current user is allowed to register a new
+ * account or not
+ * @param context The relevant DSpace context
+ * @param request The current request
+ * @return A boolean indicating whether the current user can register a new account or not
+ * @throws SQLException If something goes wrong
+ */
+ public static boolean authorizeNewAccountRegistration(Context context, HttpServletRequest request)
+ throws SQLException {
+ if (DSpaceServicesFactory.getInstance().getConfigurationService()
+ .getBooleanProperty("user.registration", true)) {
+ // This allowSetPassword is currently the only mthod that would return true only when it's
+ // actually expected to be returning true.
+ // For example the LDAP canSelfRegister will return true due to auto-register, while that
+ // does not imply a new user can register explicitly
+ return AuthenticateServiceFactory.getInstance().getAuthenticationService()
+ .allowSetPassword(context, request, null);
+ }
+ return false;
+ }
+
+ /**
+ * This method will return a boolean indicating whether it's allowed to update the password for the EPerson
+ * with the given email and canLogin property
+ * @param context The relevant DSpace context
+ * @param email The email to be checked
+ * @return A boolean indicating if the password can be updated or not
+ */
+ public static boolean authorizeUpdatePassword(Context context, String email) {
+ try {
+ EPerson eperson = EPersonServiceFactory.getInstance().getEPersonService().findByEmail(context, email);
+ if (eperson != null && eperson.canLogIn()) {
+ HttpServletRequest request = new DSpace().getRequestService().getCurrentRequest()
+ .getHttpServletRequest();
+ return AuthenticateServiceFactory.getInstance().getAuthenticationService()
+ .allowSetPassword(context, request, null);
+ }
+ } catch (SQLException e) {
+ log.error("Something went wrong trying to retrieve EPerson for email: " + email, e);
+ }
+ return false;
+ }
+
+ /**
+ * This method checks if the community Admin can manage accounts
+ *
+ * @return true if is able
+ */
+ public static boolean canCommunityAdminManageAccounts() {
+ boolean isAble = false;
+ if (AuthorizeConfiguration.canCommunityAdminManagePolicies()
+ || AuthorizeConfiguration.canCommunityAdminManageAdminGroup()
+ || AuthorizeConfiguration.canCommunityAdminManageCollectionPolicies()
+ || AuthorizeConfiguration.canCommunityAdminManageCollectionSubmitters()
+ || AuthorizeConfiguration.canCommunityAdminManageCollectionWorkflows()
+ || AuthorizeConfiguration.canCommunityAdminManageCollectionAdminGroup()) {
+ isAble = true;
+ }
+ return isAble;
+ }
+
+ /**
+ * This method checks if the Collection Admin can manage accounts
+ *
+ * @return true if is able
+ */
+ public static boolean canCollectionAdminManageAccounts() {
+ boolean isAble = false;
+ if (AuthorizeConfiguration.canCollectionAdminManagePolicies()
+ || AuthorizeConfiguration.canCollectionAdminManageSubmitters()
+ || AuthorizeConfiguration.canCollectionAdminManageWorkflows()
+ || AuthorizeConfiguration.canCollectionAdminManageAdminGroup()) {
+ isAble = true;
+ }
+ return isAble;
+ }
}
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 a6444a3890..c3cbac115a 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
@@ -12,6 +12,7 @@ import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
+import javax.annotation.Nullable;
import org.apache.commons.lang3.StringUtils;
import org.dspace.content.MetadataSchemaEnum;
@@ -291,7 +292,7 @@ public class DCInput {
*
* @return the input type
*/
- public String getInputType() {
+ public @Nullable String getInputType() {
return inputType;
}
diff --git a/dspace-api/src/main/java/org/dspace/app/util/DCInputSet.java b/dspace-api/src/main/java/org/dspace/app/util/DCInputSet.java
index faa3fb7190..bfd4270cf2 100644
--- a/dspace-api/src/main/java/org/dspace/app/util/DCInputSet.java
+++ b/dspace-api/src/main/java/org/dspace/app/util/DCInputSet.java
@@ -10,6 +10,8 @@ package org.dspace.app.util;
import java.util.List;
import java.util.Map;
+import org.apache.commons.lang3.StringUtils;
+import org.dspace.core.Utils;
/**
* Class representing all DC inputs required for a submission, organized into pages
*
@@ -107,9 +109,21 @@ public class DCInputSet {
for (int i = 0; i < inputs.length; i++) {
for (int j = 0; j < inputs[i].length; j++) {
DCInput field = inputs[i][j];
- String fullName = field.getFieldName();
- if (fullName.equals(fieldName)) {
- return true;
+ // If this is a "qualdrop_value" field, then the full field name is the field + dropdown qualifier
+ if (StringUtils.equals(field.getInputType(), "qualdrop_value")) {
+ List pairs = field.getPairs();
+ for (int k = 0; k < pairs.size(); k += 2) {
+ String qualifier = pairs.get(k + 1);
+ String fullName = Utils.standardize(field.getSchema(), field.getElement(), qualifier, ".");
+ if (fullName.equals(fieldName)) {
+ return true;
+ }
+ }
+ } else {
+ String fullName = field.getFieldName();
+ if (fullName.equals(fieldName)) {
+ return true;
+ }
}
}
}
diff --git a/dspace-api/src/main/java/org/dspace/app/util/IndexVersion.java b/dspace-api/src/main/java/org/dspace/app/util/IndexVersion.java
index d8b2d6868a..7bdaa95b5c 100644
--- a/dspace-api/src/main/java/org/dspace/app/util/IndexVersion.java
+++ b/dspace-api/src/main/java/org/dspace/app/util/IndexVersion.java
@@ -250,12 +250,8 @@ public class IndexVersion {
} else if (firstMinor > secondMinor) {
// If we get here, major versions must be EQUAL. Now, time to check our minor versions
return GREATER_THAN;
- } else if (firstMinor < secondMinor) {
- return LESS_THAN;
} else {
- // This is an impossible scenario.
- // This 'else' should never be triggered since we've checked for equality above already
- return EQUAL;
+ return LESS_THAN;
}
}
diff --git a/dspace-api/src/main/java/org/dspace/authenticate/IPMatcher.java b/dspace-api/src/main/java/org/dspace/authenticate/IPMatcher.java
index 955b6c86d3..439e53af1d 100644
--- a/dspace-api/src/main/java/org/dspace/authenticate/IPMatcher.java
+++ b/dspace-api/src/main/java/org/dspace/authenticate/IPMatcher.java
@@ -87,13 +87,16 @@ public class IPMatcher {
+ ipSpec);
}
- int maskBytes = maskBits / 8;
- for (int i = 0; i < maskBytes; i++) {
- netmask[i] = (byte) 0Xff;
- }
- netmask[maskBytes] = (byte) ((byte) 0Xff << 8 - (maskBits % 8)); // FIXME test!
- for (int i = maskBytes + 1; i < (128 / 8); i++) {
- netmask[i] = 0;
+ for (int i = 0; i < netmask.length; i++) {
+ if (maskBits <= 0) {
+ netmask[i] = 0;
+ } else if (maskBits > 8) {
+ netmask[i] = (byte) 0Xff;
+ } else {
+ netmask[i] = (byte) ((byte) 0Xff << 8 - maskBits);
+ }
+
+ maskBits = maskBits - 8;
}
break;
case 1: // No explicit mask: fill the mask with 1s
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 2384a260da..eb7d60d84c 100644
--- a/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java
+++ b/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java
@@ -430,7 +430,11 @@ public class AuthorizeServiceImpl implements AuthorizeService {
public boolean isCommunityAdmin(Context c) throws SQLException {
EPerson e = c.getCurrentUser();
+ return isCommunityAdmin(c, e);
+ }
+ @Override
+ public boolean isCommunityAdmin(Context c, EPerson e) throws SQLException {
if (e != null) {
List policies = resourcePolicyService.find(c, e,
groupService.allMemberGroups(c, e),
@@ -446,7 +450,11 @@ public class AuthorizeServiceImpl implements AuthorizeService {
public boolean isCollectionAdmin(Context c) throws SQLException {
EPerson e = c.getCurrentUser();
+ return isCollectionAdmin(c, e);
+ }
+ @Override
+ public boolean isCollectionAdmin(Context c, EPerson e) throws SQLException {
if (e != null) {
List policies = resourcePolicyService.find(c, e,
groupService.allMemberGroups(c, e),
@@ -606,6 +614,12 @@ public class AuthorizeServiceImpl implements AuthorizeService {
resourcePolicyService.removeDsoEPersonPolicies(c, o, e);
}
+ @Override
+ public void removeAllEPersonPolicies(Context c, EPerson e)
+ throws SQLException, AuthorizeException {
+ resourcePolicyService.removeAllEPersonPolicies(c, e);
+ }
+
@Override
public List getAuthorizedGroups(Context c, DSpaceObject o,
int actionID) throws java.sql.SQLException {
diff --git a/dspace-api/src/main/java/org/dspace/authorize/ResourcePolicyServiceImpl.java b/dspace-api/src/main/java/org/dspace/authorize/ResourcePolicyServiceImpl.java
index 74b3c0633f..4a2addf781 100644
--- a/dspace-api/src/main/java/org/dspace/authorize/ResourcePolicyServiceImpl.java
+++ b/dspace-api/src/main/java/org/dspace/authorize/ResourcePolicyServiceImpl.java
@@ -114,6 +114,11 @@ public class ResourcePolicyServiceImpl implements ResourcePolicyService {
return resourcePolicyDAO.findByEPersonGroupTypeIdAction(c, e, groups, action, type_id);
}
+ @Override
+ public List find(Context context, EPerson ePerson) throws SQLException {
+ return resourcePolicyDAO.findByEPerson(context, ePerson);
+ }
+
@Override
public List findByTypeGroupActionExceptId(Context context, DSpaceObject dso, Group group,
int action, int notPolicyID)
@@ -246,6 +251,11 @@ public class ResourcePolicyServiceImpl implements ResourcePolicyService {
}
+ @Override
+ public void removeAllEPersonPolicies(Context context, EPerson ePerson) throws SQLException, AuthorizeException {
+ resourcePolicyDAO.deleteByEPerson(context, ePerson);
+ }
+
@Override
public void removeGroupPolicies(Context c, Group group) throws SQLException {
resourcePolicyDAO.deleteByGroup(c, group);
diff --git a/dspace-api/src/main/java/org/dspace/authorize/dao/ResourcePolicyDAO.java b/dspace-api/src/main/java/org/dspace/authorize/dao/ResourcePolicyDAO.java
index fa3b38efc8..5c898a5bca 100644
--- a/dspace-api/src/main/java/org/dspace/authorize/dao/ResourcePolicyDAO.java
+++ b/dspace-api/src/main/java/org/dspace/authorize/dao/ResourcePolicyDAO.java
@@ -33,6 +33,8 @@ public interface ResourcePolicyDAO extends GenericDAO {
public List findByDsoAndType(Context context, DSpaceObject dSpaceObject, String type)
throws SQLException;
+ public List findByEPerson(Context context, EPerson ePerson) throws SQLException;
+
public List findByGroup(Context context, Group group) throws SQLException;
public List findByDSoAndAction(Context context, DSpaceObject dso, int actionId) throws SQLException;
@@ -66,6 +68,15 @@ public interface ResourcePolicyDAO extends GenericDAO {
public void deleteByDsoEPersonPolicies(Context context, DSpaceObject dso, EPerson ePerson) throws SQLException;
+ /**
+ * Deletes all policies that belong to an EPerson
+ *
+ * @param context DSpace context object
+ * @param ePerson ePerson whose policies to delete
+ * @throws SQLException if database error
+ */
+ public void deleteByEPerson(Context context, EPerson ePerson) throws SQLException;
+
public void deleteByDsoAndTypeNotEqualsTo(Context c, DSpaceObject o, String type) throws SQLException;
/**
@@ -101,7 +112,7 @@ public interface ResourcePolicyDAO extends GenericDAO {
* @return total resource policies of the ePerson
* @throws SQLException if database error
*/
- public int countByEPerson(Context context, EPerson eperson) throws SQLException;
+ public int countByEPerson(Context context, EPerson ePerson) throws SQLException;
/**
* Return a paginated list of policies related to a resourceUuid belong to an ePerson
diff --git a/dspace-api/src/main/java/org/dspace/authorize/dao/impl/ResourcePolicyDAOImpl.java b/dspace-api/src/main/java/org/dspace/authorize/dao/impl/ResourcePolicyDAOImpl.java
index 6aa5d2bb2e..9dd368d667 100644
--- a/dspace-api/src/main/java/org/dspace/authorize/dao/impl/ResourcePolicyDAOImpl.java
+++ b/dspace-api/src/main/java/org/dspace/authorize/dao/impl/ResourcePolicyDAOImpl.java
@@ -63,6 +63,16 @@ public class ResourcePolicyDAOImpl extends AbstractHibernateDAO
return list(context, criteriaQuery, false, ResourcePolicy.class, -1, -1);
}
+ @Override
+ public List findByEPerson(Context context, EPerson ePerson) throws SQLException {
+ CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context);
+ CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, ResourcePolicy.class);
+ Root resourcePolicyRoot = criteriaQuery.from(ResourcePolicy.class);
+ criteriaQuery.select(resourcePolicyRoot);
+ criteriaQuery.where(criteriaBuilder.equal(resourcePolicyRoot.get(ResourcePolicy_.eperson), ePerson));
+ return list(context, criteriaQuery, false, ResourcePolicy.class, -1, -1);
+ }
+
@Override
public List findByGroup(Context context, Group group) throws SQLException {
CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context);
@@ -194,6 +204,15 @@ public class ResourcePolicyDAOImpl extends AbstractHibernateDAO
}
+ @Override
+ public void deleteByEPerson(Context context, EPerson ePerson) throws SQLException {
+ String queryString = "delete from ResourcePolicy where eperson= :eperson";
+ Query query = createQuery(context, queryString);
+ query.setParameter("eperson", ePerson);
+ query.executeUpdate();
+
+ }
+
@Override
public void deleteByDsoAndTypeNotEqualsTo(Context context, DSpaceObject dso, String type) throws SQLException {
@@ -247,10 +266,10 @@ public class ResourcePolicyDAOImpl extends AbstractHibernateDAO
}
@Override
- public int countByEPerson(Context context, EPerson eperson) throws SQLException {
+ public int countByEPerson(Context context, EPerson ePerson) throws SQLException {
Query query = createQuery(context,
"SELECT count(*) FROM " + ResourcePolicy.class.getSimpleName() + " WHERE eperson_id = (:epersonUuid) ");
- query.setParameter("epersonUuid", eperson.getID());
+ query.setParameter("epersonUuid", ePerson.getID());
return count(query);
}
diff --git a/dspace-api/src/main/java/org/dspace/authorize/service/AuthorizeService.java b/dspace-api/src/main/java/org/dspace/authorize/service/AuthorizeService.java
index 9e739e2585..94a1c0297e 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
@@ -213,6 +213,26 @@ public interface AuthorizeService {
public boolean isCollectionAdmin(Context c) throws SQLException;
+ /**
+ * Check to see if a specific user is Community admin
+ *
+ * @param c current context
+ * @param e the user to check
+ * @return true if user is an admin of some community
+ * @throws SQLException
+ */
+ public boolean isCommunityAdmin(Context c, EPerson e) throws SQLException;
+
+ /**
+ * Check to see if a specific user is Collection admin
+ *
+ * @param c current context
+ * @param e the user to check
+ * @return true if user is an admin of some collection
+ * @throws SQLException if database error
+ */
+ public boolean isCollectionAdmin(Context c, EPerson e) throws SQLException;
+
///////////////////////////////////////////////
// policy manipulation methods
///////////////////////////////////////////////
@@ -429,6 +449,16 @@ public interface AuthorizeService {
*/
public void removeEPersonPolicies(Context c, DSpaceObject o, EPerson e) throws SQLException, AuthorizeException;
+ /**
+ * Removes all policies from an eperson that belong to an EPerson.
+ *
+ * @param c current context
+ * @param e the eperson
+ * @throws SQLException if there's a database problem
+ * @throws AuthorizeException if authorization error
+ */
+ public void removeAllEPersonPolicies(Context c, EPerson e) throws SQLException, AuthorizeException;
+
/**
* Returns all groups authorized to perform an action on an object. Returns
* empty array if no matches.
diff --git a/dspace-api/src/main/java/org/dspace/authorize/service/ResourcePolicyService.java b/dspace-api/src/main/java/org/dspace/authorize/service/ResourcePolicyService.java
index 48ec510c86..f1d8b30242 100644
--- a/dspace-api/src/main/java/org/dspace/authorize/service/ResourcePolicyService.java
+++ b/dspace-api/src/main/java/org/dspace/authorize/service/ResourcePolicyService.java
@@ -39,6 +39,16 @@ public interface ResourcePolicyService extends DSpaceCRUDService
public List find(Context context, Group group) throws SQLException;
+ /**
+ * Retrieve a list of ResourcePolicies by EPerson
+ *
+ * @param c context
+ * @param ePerson the EPerson for which to look up the resource policies
+ * @return a list of ResourcePolicies for the provided EPerson
+ * @throws SQLException if there's a database problem
+ */
+ public List find(Context c, EPerson ePerson) throws SQLException;
+
public List find(Context c, EPerson e, List groups, int action, int type_id)
throws SQLException;
@@ -72,6 +82,16 @@ public interface ResourcePolicyService extends DSpaceCRUDService
public void removeDsoEPersonPolicies(Context context, DSpaceObject dso, EPerson ePerson)
throws SQLException, AuthorizeException;
+ /**
+ * Removes all ResourcePolicies related to an EPerson
+ *
+ * @param context context
+ * @param ePerson the EPerson for which the ResourcePolicies will be deleted
+ * @throws SQLException if there's a database problem
+ * @throws AuthorizeException when the current user is not authorized
+ */
+ public void removeAllEPersonPolicies(Context context, EPerson ePerson) throws SQLException, AuthorizeException;
+
public void removeGroupPolicies(Context c, Group group) throws SQLException;
public void removeDsoAndTypeNotEqualsToPolicies(Context c, DSpaceObject o, String type)
diff --git a/dspace-api/src/main/java/org/dspace/content/BitstreamFormatServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/BitstreamFormatServiceImpl.java
index 21d1fa4ba4..89bf74ece6 100644
--- a/dspace-api/src/main/java/org/dspace/content/BitstreamFormatServiceImpl.java
+++ b/dspace-api/src/main/java/org/dspace/content/BitstreamFormatServiceImpl.java
@@ -153,7 +153,7 @@ public class BitstreamFormatServiceImpl implements BitstreamFormatService {
// If the exception was thrown, unknown will == null so goahead and
// load s. If not, check that the unknown's registry's name is not
// being reset.
- if (unknown == null || unknown.getID() != bitstreamFormat.getID()) {
+ if (unknown == null || !unknown.getID().equals(bitstreamFormat.getID())) {
bitstreamFormat.setShortDescriptionInternal(shortDescription);
}
}
@@ -208,7 +208,7 @@ public class BitstreamFormatServiceImpl implements BitstreamFormatService {
// Find "unknown" type
BitstreamFormat unknown = findUnknown(context);
- if (unknown.getID() == bitstreamFormat.getID()) {
+ if (unknown.getID().equals(bitstreamFormat.getID())) {
throw new IllegalArgumentException("The Unknown bitstream format may not be deleted.");
}
@@ -270,4 +270,4 @@ public class BitstreamFormatServiceImpl implements BitstreamFormatService {
}
return null;
}
-}
\ No newline at end of file
+}
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 cc6d32b8c3..559b95edb8 100644
--- a/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java
+++ b/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java
@@ -17,11 +17,13 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.MissingResourceException;
+import java.util.Set;
import java.util.UUID;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.Logger;
+import org.apache.solr.client.solrj.util.ClientUtils;
import org.dspace.app.util.AuthorizeUtil;
import org.dspace.authorize.AuthorizeConfiguration;
import org.dspace.authorize.AuthorizeException;
@@ -40,6 +42,13 @@ import org.dspace.core.Context;
import org.dspace.core.I18nUtil;
import org.dspace.core.LogManager;
import org.dspace.core.service.LicenseService;
+import org.dspace.discovery.DiscoverQuery;
+import org.dspace.discovery.DiscoverResult;
+import org.dspace.discovery.IndexableObject;
+import org.dspace.discovery.SearchService;
+import org.dspace.discovery.SearchServiceException;
+import org.dspace.discovery.indexobject.IndexableCollection;
+import org.dspace.eperson.EPerson;
import org.dspace.eperson.Group;
import org.dspace.eperson.service.GroupService;
import org.dspace.eperson.service.SubscribeService;
@@ -48,7 +57,6 @@ import org.dspace.harvest.HarvestedCollection;
import org.dspace.harvest.service.HarvestedCollectionService;
import org.dspace.workflow.factory.WorkflowServiceFactory;
import org.dspace.xmlworkflow.WorkflowConfigurationException;
-import org.dspace.xmlworkflow.XmlWorkflowFactoryImpl;
import org.dspace.xmlworkflow.factory.XmlWorkflowFactory;
import org.dspace.xmlworkflow.state.Workflow;
import org.dspace.xmlworkflow.storedcomponents.CollectionRole;
@@ -100,6 +108,9 @@ public class CollectionServiceImpl extends DSpaceObjectServiceImpl i
@Autowired(required = true)
protected CollectionRoleService collectionRoleService;
+ @Autowired(required = true)
+ protected SearchService searchService;
+
protected CollectionServiceImpl() {
super();
}
@@ -375,7 +386,7 @@ public class CollectionServiceImpl extends DSpaceObjectServiceImpl i
log.error(LogManager.getHeader(context, "setWorkflowGroup",
"collection_id=" + collection.getID() + " " + e.getMessage()), e);
}
- if (!StringUtils.equals(XmlWorkflowFactoryImpl.LEGACY_WORKFLOW_NAME, workflow.getID())) {
+ if (!StringUtils.equals(workflowFactory.getDefaultWorkflow().getID(), workflow.getID())) {
throw new IllegalArgumentException(
"setWorkflowGroup can be used only on collection with the default basic dspace workflow. "
+ "Instead, the collection: "
@@ -889,4 +900,95 @@ public class CollectionServiceImpl extends DSpaceObjectServiceImpl i
throws SQLException {
return collectionDAO.getCollectionsWithBitstreamSizesTotal(context);
}
+
+ @Override
+ public Group createDefaultReadGroup(Context context, Collection collection, String typeOfGroupString,
+ int defaultRead)
+ throws SQLException, AuthorizeException {
+ Group role = groupService.create(context);
+ groupService.setName(role, "COLLECTION_" + collection.getID().toString() + "_" + typeOfGroupString +
+ "_DEFAULT_READ");
+
+ // Remove existing privileges from the anonymous group.
+ authorizeService.removePoliciesActionFilter(context, collection, defaultRead);
+
+ // Grant our new role the default privileges.
+ authorizeService.addPolicy(context, collection, defaultRead, role);
+ groupService.update(context, role);
+ return role;
+ }
+
+ @Override
+ public List findCollectionsWithSubmit(String q, Context context, Community community,
+ int offset, int limit) throws SQLException, SearchServiceException {
+
+ List collections = new ArrayList();
+ DiscoverQuery discoverQuery = new DiscoverQuery();
+ discoverQuery.setDSpaceObjectFilter(IndexableCollection.TYPE);
+ discoverQuery.setStart(offset);
+ discoverQuery.setMaxResults(limit);
+ DiscoverResult resp = retrieveCollectionsWithSubmit(context, discoverQuery,community, q);
+ for (IndexableObject solrCollections : resp.getIndexableObjects()) {
+ Collection c = ((IndexableCollection) solrCollections).getIndexedObject();
+ collections.add(c);
+ }
+ return collections;
+ }
+
+ @Override
+ public int countCollectionsWithSubmit(String q, Context context, Community community)
+ throws SQLException, SearchServiceException {
+
+ DiscoverQuery discoverQuery = new DiscoverQuery();
+ discoverQuery.setMaxResults(0);
+ discoverQuery.setDSpaceObjectFilter(IndexableCollection.TYPE);
+ DiscoverResult resp = retrieveCollectionsWithSubmit(context, discoverQuery,community,q);
+ return (int)resp.getTotalSearchResults();
+ }
+
+ /**
+ * Finds all Indexed Collections where the current user has submit rights. If the user is an Admin,
+ * this is all Indexed Collections. Otherwise, it includes those collections where
+ * an indexed "submit" policy lists either the eperson or one of the eperson's groups
+ *
+ * @param context DSpace context
+ * @param discoverQuery
+ * @param community parent community, could be null
+ * @param q limit the returned collection to those with metadata values matching the query
+ * terms. The terms are used to make also a prefix query on SOLR
+ * so it can be used to implement an autosuggest feature over the collection name
+ * @return discovery search result objects
+ * @throws SQLException if something goes wrong
+ * @throws SearchServiceException if search error
+ */
+ private DiscoverResult retrieveCollectionsWithSubmit(Context context, DiscoverQuery discoverQuery,
+ Community community, String q) throws SQLException, SearchServiceException {
+
+ StringBuilder query = new StringBuilder();
+ EPerson currentUser = context.getCurrentUser();
+ if (!authorizeService.isAdmin(context)) {
+ String userId = "";
+ if (currentUser != null) {
+ userId = currentUser.getID().toString();
+ }
+ query.append("submit:(e").append(userId);
+ Set groups = groupService.allMemberGroupsSet(context, currentUser);
+ for (Group group : groups) {
+ query.append(" OR g").append(group.getID());
+ }
+ query.append(")");
+ discoverQuery.addFilterQueries(query.toString());
+ }
+ if (community != null) {
+ discoverQuery.addFilterQueries("location.comm:" + community.getID().toString());
+ }
+ if (StringUtils.isNotBlank(q)) {
+ StringBuilder buildQuery = new StringBuilder();
+ String escapedQuery = ClientUtils.escapeQueryChars(q);
+ buildQuery.append(escapedQuery).append(" OR ").append(escapedQuery).append("*");
+ discoverQuery.setQuery(buildQuery.toString());
+ }
+ DiscoverResult resp = searchService.search(context, discoverQuery);
+ return resp;
+ }
}
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 c49442267a..2ad0c8c2bc 100644
--- a/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java
+++ b/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java
@@ -629,6 +629,10 @@ public class CommunityServiceImpl extends DSpaceObjectServiceImpl imp
case Constants.DELETE:
if (AuthorizeConfiguration.canCommunityAdminPerformSubelementDeletion()) {
adminObject = getParentObject(context, community);
+ if (adminObject == null) {
+ //top-level community, has to be admin of the current community
+ adminObject = community;
+ }
}
break;
case Constants.ADD:
diff --git a/dspace-api/src/main/java/org/dspace/content/DSpaceObjectServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/DSpaceObjectServiceImpl.java
index 6886d41e1b..d33ad7e416 100644
--- a/dspace-api/src/main/java/org/dspace/content/DSpaceObjectServiceImpl.java
+++ b/dspace-api/src/main/java/org/dspace/content/DSpaceObjectServiceImpl.java
@@ -207,8 +207,8 @@ public abstract class DSpaceObjectServiceImpl implements
}
@Override
- public void addMetadata(Context context, T dso, String schema, String element, String qualifier, String lang,
- List values) throws SQLException {
+ public List addMetadata(Context context, T dso, String schema, String element, String qualifier,
+ String lang, List values) throws SQLException {
MetadataField metadataField = metadataFieldService.findByElement(context, schema, element, qualifier);
if (metadataField == null) {
throw new SQLException(
@@ -216,12 +216,12 @@ public abstract class DSpaceObjectServiceImpl implements
"exist!");
}
- addMetadata(context, dso, metadataField, lang, values);
+ return addMetadata(context, dso, metadataField, lang, values);
}
@Override
- public void addMetadata(Context context, T dso, String schema, String element, String qualifier, String lang,
- List values, List authorities, List confidences)
+ public List addMetadata(Context context, T dso, String schema, String element, String qualifier,
+ String lang, List values, List authorities, List confidences)
throws SQLException {
// We will not verify that they are valid entries in the registry
// until update() is called.
@@ -231,15 +231,16 @@ public abstract class DSpaceObjectServiceImpl implements
"bad_dublin_core schema=" + schema + "." + element + "." + qualifier + ". Metadata field does not " +
"exist!");
}
- addMetadata(context, dso, metadataField, lang, values, authorities, confidences);
+ return addMetadata(context, dso, metadataField, lang, values, authorities, confidences);
}
@Override
- public void addMetadata(Context context, T dso, MetadataField metadataField, String lang, List values,
- List authorities, List confidences)
+ public List addMetadata(Context context, T dso, MetadataField metadataField, String lang,
+ List values, List authorities, List confidences)
throws SQLException {
boolean authorityControlled = metadataAuthorityService.isAuthorityControlled(metadataField);
boolean authorityRequired = metadataAuthorityService.isAuthorityRequired(metadataField);
+ List newMetadata = new ArrayList<>(values.size());
// We will not verify that they are valid entries in the registry
// until update() is called.
for (int i = 0; i < values.size(); i++) {
@@ -250,6 +251,7 @@ public abstract class DSpaceObjectServiceImpl implements
}
}
MetadataValue metadataValue = metadataValueService.create(context, dso, metadataField);
+ newMetadata.add(metadataValue);
//Set place to list length of all metadatavalues for the given schema.element.qualifier combination.
// Subtract one to adhere to the 0 as first element rule
metadataValue.setPlace(
@@ -304,29 +306,31 @@ public abstract class DSpaceObjectServiceImpl implements
// metadataValueService.update(context, metadataValue);
dso.addDetails(metadataField.toString());
}
+ return newMetadata;
}
@Override
- public void addMetadata(Context context, T dso, MetadataField metadataField, String language, String value,
- String authority, int confidence) throws SQLException {
- addMetadata(context, dso, metadataField, language, Arrays.asList(value), Arrays.asList(authority),
- Arrays.asList(confidence));
+ public MetadataValue addMetadata(Context context, T dso, MetadataField metadataField, String language,
+ String value, String authority, int confidence) throws SQLException {
+ return addMetadata(context, dso, metadataField, language, Arrays.asList(value), Arrays.asList(authority),
+ Arrays.asList(confidence)).get(0);
}
@Override
- public void addMetadata(Context context, T dso, String schema, String element, String qualifier, String lang,
- String value) throws SQLException {
- addMetadata(context, dso, schema, element, qualifier, lang, Arrays.asList(value));
+ public MetadataValue addMetadata(Context context, T dso, String schema, String element, String qualifier,
+ String lang, String value) throws SQLException {
+ return addMetadata(context, dso, schema, element, qualifier, lang, Arrays.asList(value)).get(0);
}
@Override
- public void addMetadata(Context context, T dso, MetadataField metadataField, String language, String value)
+ public MetadataValue addMetadata(Context context, T dso, MetadataField metadataField, String language, String value)
throws SQLException {
- addMetadata(context, dso, metadataField, language, Arrays.asList(value));
+ return addMetadata(context, dso, metadataField, language, Arrays.asList(value)).get(0);
}
@Override
- public void addMetadata(Context context, T dso, MetadataField metadataField, String language, List values)
+ public List addMetadata(Context context, T dso, MetadataField metadataField, String language,
+ List values)
throws SQLException {
if (metadataField != null) {
String fieldKey = metadataAuthorityService
@@ -343,18 +347,19 @@ public abstract class DSpaceObjectServiceImpl implements
getAuthoritiesAndConfidences(fieldKey, null, values, authorities, confidences, i);
}
}
- addMetadata(context, dso, metadataField, language, values, authorities, confidences);
+ return addMetadata(context, dso, metadataField, language, values, authorities, confidences);
} else {
- addMetadata(context, dso, metadataField, language, values, null, null);
+ return addMetadata(context, dso, metadataField, language, values, null, null);
}
}
+ return new ArrayList<>(0);
}
@Override
- public void addMetadata(Context context, T dso, String schema, String element, String qualifier, String lang,
- String value, String authority, int confidence) throws SQLException {
- addMetadata(context, dso, schema, element, qualifier, lang, Arrays.asList(value), Arrays.asList(authority),
- Arrays.asList(confidence));
+ public MetadataValue addMetadata(Context context, T dso, String schema, String element, String qualifier,
+ String lang, String value, String authority, int confidence) throws SQLException {
+ return addMetadata(context, dso, schema, element, qualifier, lang, Arrays.asList(value),
+ Arrays.asList(authority), Arrays.asList(confidence)).get(0);
}
@Override
@@ -660,33 +665,35 @@ public abstract class DSpaceObjectServiceImpl implements
@Override
public void addAndShiftRightMetadata(Context context, T dso, String schema, String element, String qualifier,
String lang, String value, String authority, int confidence, int index)
- throws SQLException {
+ throws SQLException {
List list = getMetadata(dso, schema, element, qualifier);
- clearMetadata(context, dso, schema, element, qualifier, Item.ANY);
-
int idx = 0;
+ int place = 0;
boolean last = true;
for (MetadataValue rr : list) {
if (idx == index) {
- addMetadata(context, dso, schema, element, qualifier,
- lang, value, authority, confidence);
+ MetadataValue newMetadata = addMetadata(context, dso, schema, element, qualifier,
+ lang, value, authority, confidence);
+
+ moveSingleMetadataValue(context, dso, place, newMetadata);
+ place++;
last = false;
}
- addMetadata(context, dso, schema, element, qualifier,
- rr.getLanguage(), rr.getValue(), rr.getAuthority(), rr.getConfidence());
+ moveSingleMetadataValue(context, dso, place, rr);
+ place++;
idx++;
}
if (last) {
addMetadata(context, dso, schema, element, qualifier,
- lang, value, authority, confidence);
+ lang, value, authority, confidence);
}
}
@Override
public void moveMetadata(Context context, T dso, String schema, String element, String qualifier, int from, int to)
- throws SQLException, IllegalArgumentException {
+ throws SQLException, IllegalArgumentException {
if (from == to) {
throw new IllegalArgumentException("The \"from\" location MUST be different from \"to\" location");
@@ -701,8 +708,6 @@ public abstract class DSpaceObjectServiceImpl implements
"\n Idx from:" + from + " Idx to: " + to);
}
- clearMetadata(context, dso, schema, element, qualifier, Item.ANY);
-
int idx = 0;
MetadataValue moved = null;
for (MetadataValue md : list) {
@@ -714,49 +719,46 @@ public abstract class DSpaceObjectServiceImpl implements
}
idx = 0;
+ int place = 0;
boolean last = true;
for (MetadataValue rr : list) {
if (idx == to && to < from) {
- addMetadata(context, dso, schema, element, qualifier, moved.getLanguage(), moved.getValue(),
- moved.getAuthority(), moved.getConfidence());
+ moveSingleMetadataValue(context, dso, place, moved);
+ place++;
last = false;
}
if (idx != from) {
- addMetadata(context, dso, schema, element, qualifier, rr.getLanguage(), rr.getValue(),
- rr.getAuthority(), rr.getConfidence());
+ moveSingleMetadataValue(context, dso, place, rr);
+ place++;
}
if (idx == to && to > from) {
- addMetadata(context, dso, schema, element, qualifier, moved.getLanguage(), moved.getValue(),
- moved.getAuthority(), moved.getConfidence());
+ moveSingleMetadataValue(context, dso, place, moved);
+ place++;
last = false;
}
idx++;
}
if (last) {
- addMetadata(context, dso, schema, element, qualifier, moved.getLanguage(), moved.getValue(),
- moved.getAuthority(), moved.getConfidence());
+ moveSingleMetadataValue(context, dso, place, moved);
}
}
+ /**
+ * Supports moving metadata by updating the place of the metadata value
+ */
+ protected void moveSingleMetadataValue(Context context, T dso, int place, MetadataValue rr) {
+ //just move the metadata
+ rr.setPlace(place);
+ }
+
@Override
public void replaceMetadata(Context context, T dso, String schema, String element, String qualifier, String lang,
String value, String authority, int confidence, int index) throws SQLException {
List list = getMetadata(dso, schema, element, qualifier);
- clearMetadata(context, dso, schema, element, qualifier, Item.ANY);
-
- int idx = 0;
- for (MetadataValue rr : list) {
- if (idx == index) {
- addMetadata(context, dso, schema, element, qualifier,
- lang, value, authority, confidence);
- } else {
- addMetadata(context, dso, schema, element, qualifier,
- rr.getLanguage(), rr.getValue(), rr.getAuthority(), rr.getConfidence());
- }
- idx++;
- }
+ removeMetadataValues(context, dso, Arrays.asList(list.get(index)));
+ addAndShiftRightMetadata(context, dso, schema, element, qualifier, lang, value, authority, confidence, index);
}
@Override
diff --git a/dspace-api/src/main/java/org/dspace/content/EntityType.java b/dspace-api/src/main/java/org/dspace/content/EntityType.java
index 15fe1739e5..d44ec5a35d 100644
--- a/dspace-api/src/main/java/org/dspace/content/EntityType.java
+++ b/dspace-api/src/main/java/org/dspace/content/EntityType.java
@@ -7,6 +7,7 @@
*/
package org.dspace.content;
+import java.util.Objects;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
@@ -15,6 +16,8 @@ import javax.persistence.Id;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.dspace.core.ReloadableEntity;
/**
@@ -45,7 +48,8 @@ public class EntityType implements ReloadableEntity {
/**
* The standard setter for the ID of this EntityType
- * @param id The ID that this EntityType's ID will be set to
+ *
+ * @param id The ID that this EntityType's ID will be set to
*/
public void setId(Integer id) {
this.id = id;
@@ -53,7 +57,8 @@ public class EntityType implements ReloadableEntity {
/**
* The standard getter for the label of this EntityType
- * @return The label for this EntityType
+ *
+ * @return The label for this EntityType
*/
public String getLabel() {
return label;
@@ -61,6 +66,7 @@ public class EntityType implements ReloadableEntity {
/**
* The standard setter for the label of this EntityType
+ *
* @param label The label that this EntityType's label will be set to
*/
public void setLabel(String label) {
@@ -69,9 +75,40 @@ public class EntityType implements ReloadableEntity {
/**
* The standard getter for the ID of this EntityType
- * @return The ID for this EntityType
+ *
+ * @return The ID for this EntityType
*/
public Integer getID() {
return id;
}
+
+ /**
+ * Determines whether two entity types are equal based on the id and the label
+ * @param obj object to be compared
+ * @return
+ */
+ public boolean equals(Object obj) {
+ if (!(obj instanceof EntityType)) {
+ return false;
+ }
+ EntityType entityType = (EntityType) obj;
+
+ if (!Objects.equals(this.getID(), entityType.getID())) {
+ return false;
+ }
+
+ if (!StringUtils.equals(this.getLabel(), entityType.getLabel())) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Returns a hash code value for the object.
+ * @return hash code value
+ */
+ @Override
+ public int hashCode() {
+ return new HashCodeBuilder().append(getID()).toHashCode();
+ }
}
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 9502a2ca32..3b0253bf86 100644
--- a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java
+++ b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java
@@ -230,6 +230,12 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl implements It
return itemDAO.findBySubmitter(context, eperson);
}
+ @Override
+ public Iterator findBySubmitter(Context context, EPerson eperson, boolean retrieveAllItems)
+ throws SQLException {
+ return itemDAO.findBySubmitter(context, eperson, retrieveAllItems);
+ }
+
@Override
public Iterator findBySubmitterDateSorted(Context context, EPerson eperson, Integer limit)
throws SQLException {
@@ -1100,19 +1106,7 @@ prevent the generation of resource policy entry values with null dspace_object a
}
break;
case Constants.DELETE:
- if (item.getOwningCollection() != null) {
- if (AuthorizeConfiguration.canCollectionAdminPerformItemDeletion()) {
- adminObject = collection;
- } else if (AuthorizeConfiguration.canCommunityAdminPerformItemDeletion()) {
- adminObject = community;
- }
- } else {
- if (AuthorizeConfiguration.canCollectionAdminManageTemplateItem()) {
- adminObject = collection;
- } else if (AuthorizeConfiguration.canCommunityAdminManageCollectionTemplateItem()) {
- adminObject = community;
- }
- }
+ adminObject = item;
break;
case Constants.WRITE:
// if it is a template item we need to check the
@@ -1372,6 +1366,32 @@ prevent the generation of resource policy entry values with null dspace_object a
}
+ /**
+ * Supports moving metadata by adding the metadata value or updating the place of the relationship
+ */
+ @Override
+ protected void moveSingleMetadataValue(Context context, Item dso, int place, MetadataValue rr) {
+ if (rr instanceof RelationshipMetadataValue) {
+ try {
+ //Retrieve the applicable relationship
+ Relationship rs = relationshipService.find(context,
+ ((RelationshipMetadataValue) rr).getRelationshipId());
+ if (rs.getLeftItem() == dso) {
+ rs.setLeftPlace(place);
+ } else {
+ rs.setRightPlace(place);
+ }
+ relationshipService.update(context, rs);
+ } catch (Exception e) {
+ //should not occur, otherwise metadata can't be updated either
+ log.error("An error occurred while moving " + rr.getAuthority() + " for item " + dso.getID(), e);
+ }
+ } else {
+ //just move the metadata
+ rr.setPlace(place);
+ }
+ }
+
/**
* This method will sort the List of MetadataValue objects based on the MetadataSchema, MetadataField Element,
* MetadataField Qualifier and MetadataField Place in that order.
diff --git a/dspace-api/src/main/java/org/dspace/content/MetadataDSpaceCsvExportServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/MetadataDSpaceCsvExportServiceImpl.java
new file mode 100644
index 0000000000..1750938937
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/content/MetadataDSpaceCsvExportServiceImpl.java
@@ -0,0 +1,130 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.content;
+
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import com.google.common.collect.Iterators;
+import org.dspace.app.bulkedit.DSpaceCSV;
+import org.dspace.content.service.ItemService;
+import org.dspace.content.service.MetadataDSpaceCsvExportService;
+import org.dspace.core.Constants;
+import org.dspace.core.Context;
+import org.dspace.handle.factory.HandleServiceFactory;
+import org.dspace.scripts.handler.DSpaceRunnableHandler;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * Implementation of {@link MetadataDSpaceCsvExportService}
+ */
+public class MetadataDSpaceCsvExportServiceImpl implements MetadataDSpaceCsvExportService {
+
+ @Autowired
+ private ItemService itemService;
+
+ @Override
+ public DSpaceCSV handleExport(Context context, boolean exportAllItems, boolean exportAllMetadata, String handle,
+ DSpaceRunnableHandler handler) throws Exception {
+ Iterator toExport = null;
+
+ if (exportAllItems) {
+ handler.logInfo("Exporting whole repository WARNING: May take some time!");
+ toExport = itemService.findAll(context);
+ } else {
+ DSpaceObject dso = HandleServiceFactory.getInstance().getHandleService().resolveToObject(context, handle);
+ if (dso == null) {
+ throw new IllegalArgumentException(
+ "Item '" + handle + "' does not resolve to an item in your repository!");
+ }
+
+ if (dso.getType() == Constants.ITEM) {
+ handler.logInfo("Exporting item '" + dso.getName() + "' (" + handle + ")");
+ List item = new ArrayList<>();
+ item.add((Item) dso);
+ toExport = item.iterator();
+ } else if (dso.getType() == Constants.COLLECTION) {
+ handler.logInfo("Exporting collection '" + dso.getName() + "' (" + handle + ")");
+ Collection collection = (Collection) dso;
+ toExport = itemService.findByCollection(context, collection);
+ } else if (dso.getType() == Constants.COMMUNITY) {
+ handler.logInfo("Exporting community '" + dso.getName() + "' (" + handle + ")");
+ toExport = buildFromCommunity(context, (Community) dso);
+ } else {
+ throw new IllegalArgumentException("Error identifying '" + handle + "'");
+ }
+ }
+
+ DSpaceCSV csv = this.export(context, toExport, exportAllMetadata);
+ return csv;
+ }
+
+ @Override
+ public DSpaceCSV export(Context context, Iterator toExport, boolean exportAll) throws Exception {
+ Context.Mode originalMode = context.getCurrentMode();
+ context.setMode(Context.Mode.READ_ONLY);
+
+ // Process each item
+ DSpaceCSV csv = new DSpaceCSV(exportAll);
+ while (toExport.hasNext()) {
+ Item item = toExport.next();
+ csv.addItem(item);
+ context.uncacheEntity(item);
+ }
+
+ context.setMode(originalMode);
+ // Return the results
+ return csv;
+ }
+
+ @Override
+ public DSpaceCSV export(Context context, Community community, boolean exportAll) throws Exception {
+ return export(context, buildFromCommunity(context, community), exportAll);
+ }
+
+ /**
+ * Build an array list of item ids that are in a community (include sub-communities and collections)
+ *
+ * @param context DSpace context
+ * @param community The community to build from
+ * @return The list of item ids
+ * @throws SQLException if database error
+ */
+ private Iterator buildFromCommunity(Context context, Community community)
+ throws SQLException {
+ // Add all the collections
+ List collections = community.getCollections();
+ Iterator result = Collections.emptyIterator();
+ for (Collection collection : collections) {
+ Iterator items = itemService.findByCollection(context, collection);
+ result = addItemsToResult(result, items);
+
+ }
+ // Add all the sub-communities
+ List communities = community.getSubcommunities();
+ for (Community subCommunity : communities) {
+ Iterator items = buildFromCommunity(context, subCommunity);
+ result = addItemsToResult(result, items);
+ }
+
+ return result;
+ }
+
+ private Iterator addItemsToResult(Iterator result, Iterator items) {
+ if (result == null) {
+ result = items;
+ } else {
+ result = Iterators.concat(result, items);
+ }
+
+ return result;
+ }
+}
diff --git a/dspace-api/src/main/java/org/dspace/content/MetadataField.java b/dspace-api/src/main/java/org/dspace/content/MetadataField.java
index 3f574dab0e..0ea176c751 100644
--- a/dspace-api/src/main/java/org/dspace/content/MetadataField.java
+++ b/dspace-api/src/main/java/org/dspace/content/MetadataField.java
@@ -168,11 +168,11 @@ public class MetadataField implements ReloadableEntity {
return false;
}
Class> objClass = HibernateProxyHelper.getClassWithoutInitializingProxy(obj);
- if (getClass() != objClass) {
+ if (!getClass().equals(objClass)) {
return false;
}
final MetadataField other = (MetadataField) obj;
- if (this.getID() != other.getID()) {
+ if (!this.getID().equals(other.getID())) {
return false;
}
if (!getMetadataSchema().equals(other.getMetadataSchema())) {
diff --git a/dspace-api/src/main/java/org/dspace/content/MetadataFieldServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/MetadataFieldServiceImpl.java
index c71db2d131..569b5840c6 100644
--- a/dspace-api/src/main/java/org/dspace/content/MetadataFieldServiceImpl.java
+++ b/dspace-api/src/main/java/org/dspace/content/MetadataFieldServiceImpl.java
@@ -9,6 +9,8 @@ package org.dspace.content;
import java.io.IOException;
import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import org.apache.commons.collections4.CollectionUtils;
@@ -20,8 +22,12 @@ import org.dspace.content.dao.MetadataFieldDAO;
import org.dspace.content.service.MetadataFieldService;
import org.dspace.content.service.MetadataSchemaService;
import org.dspace.content.service.MetadataValueService;
+import org.dspace.content.service.SiteService;
+import org.dspace.core.Constants;
import org.dspace.core.Context;
import org.dspace.core.LogManager;
+import org.dspace.discovery.indexobject.IndexableMetadataField;
+import org.dspace.event.Event;
import org.springframework.beans.factory.annotation.Autowired;
/**
@@ -46,6 +52,8 @@ public class MetadataFieldServiceImpl implements MetadataFieldService {
protected MetadataValueService metadataValueService;
@Autowired(required = true)
protected MetadataSchemaService metadataSchemaService;
+ @Autowired
+ protected SiteService siteService;
protected MetadataFieldServiceImpl() {
@@ -77,6 +85,8 @@ public class MetadataFieldServiceImpl implements MetadataFieldService {
log.info(LogManager.getHeader(context, "create_metadata_field",
"metadata_field_id=" + metadataField.getID()));
+ // Update the index of type metadatafield
+ this.triggerEventToUpdateIndex(context, metadataField.getID());
return metadataField;
}
@@ -149,6 +159,8 @@ public class MetadataFieldServiceImpl implements MetadataFieldService {
"metadata_field_id=" + metadataField.getID() + "element=" + metadataField
.getElement()
+ "qualifier=" + metadataField.getQualifier()));
+ // Update the index of type metadatafield
+ this.triggerEventToUpdateIndex(context, metadataField.getID());
}
@Override
@@ -177,6 +189,21 @@ public class MetadataFieldServiceImpl implements MetadataFieldService {
log.info(LogManager.getHeader(context, "delete_metadata_field",
"metadata_field_id=" + metadataField.getID()));
+ // Update the index of type metadatafield
+ this.triggerEventToUpdateIndex(context, metadataField.getID());
+ }
+
+ /**
+ * Calls a MODIFY SITE event with the identifier of the changed mdField, so it can be indexed in
+ * {@link org.dspace.discovery.IndexEventConsumer}, with type of {@link org.dspace.discovery.IndexableObject} in
+ * {@link Event}.detail and the identifiers of the changed mdFields in {@link Event}.identifiers
+ *
+ * @param context DSpace context
+ * @param mdFieldId ID of the metadata field that needs to be (re)indexed
+ */
+ private void triggerEventToUpdateIndex(Context context, int mdFieldId) {
+ context.addEvent(new Event(Event.MODIFY, Constants.SITE, null, IndexableMetadataField.TYPE, new ArrayList<>(
+ Arrays.asList(Integer.toString(mdFieldId)))));
}
/**
diff --git a/dspace-api/src/main/java/org/dspace/content/MetadataSchema.java b/dspace-api/src/main/java/org/dspace/content/MetadataSchema.java
index 96bef0fa2c..727181ee9d 100644
--- a/dspace-api/src/main/java/org/dspace/content/MetadataSchema.java
+++ b/dspace-api/src/main/java/org/dspace/content/MetadataSchema.java
@@ -67,11 +67,11 @@ public class MetadataSchema implements ReloadableEntity {
return false;
}
Class> objClass = HibernateProxyHelper.getClassWithoutInitializingProxy(obj);
- if (getClass() != objClass) {
+ if (!getClass().equals(objClass)) {
return false;
}
final MetadataSchema other = (MetadataSchema) obj;
- if (this.id != other.id) {
+ if (!this.id.equals(other.id)) {
return false;
}
if ((this.namespace == null) ? (other.namespace != null) : !this.namespace.equals(other.namespace)) {
diff --git a/dspace-api/src/main/java/org/dspace/content/MetadataValue.java b/dspace-api/src/main/java/org/dspace/content/MetadataValue.java
index 4ce0c291f7..2d9808ae45 100644
--- a/dspace-api/src/main/java/org/dspace/content/MetadataValue.java
+++ b/dspace-api/src/main/java/org/dspace/content/MetadataValue.java
@@ -239,17 +239,17 @@ public class MetadataValue implements ReloadableEntity {
return false;
}
Class> objClass = HibernateProxyHelper.getClassWithoutInitializingProxy(obj);
- if (getClass() != objClass) {
+ if (!getClass().equals(objClass)) {
return false;
}
final MetadataValue other = (MetadataValue) obj;
- if (this.id != other.id) {
+ if (!this.id.equals(other.id)) {
return false;
}
- if (this.getID() != other.getID()) {
+ if (!this.getID().equals(other.getID())) {
return false;
}
- if (this.getDSpaceObject().getID() != other.getDSpaceObject().getID()) {
+ if (!this.getDSpaceObject().getID().equals(other.getDSpaceObject().getID())) {
return false;
}
return true;
diff --git a/dspace-api/src/main/java/org/dspace/content/RelationshipMetadataValue.java b/dspace-api/src/main/java/org/dspace/content/RelationshipMetadataValue.java
index 88d2e38beb..637d1c094b 100644
--- a/dspace-api/src/main/java/org/dspace/content/RelationshipMetadataValue.java
+++ b/dspace-api/src/main/java/org/dspace/content/RelationshipMetadataValue.java
@@ -7,6 +7,8 @@
*/
package org.dspace.content;
+import org.dspace.core.Constants;
+
/**
* This class is used as a representation of MetadataValues for the MetadataValues that are derived from the
* Relationships that the item has. This includes the useForPlace property which we'll have to use to determine
@@ -57,4 +59,13 @@ public class RelationshipMetadataValue extends MetadataValue {
}
return super.equals(obj);
}
+
+ /**
+ * Retrieves the Relationship ID from which the current RelationshipMetadataValue is derived
+ *
+ * @return the relationship ID
+ */
+ public int getRelationshipId() {
+ return Integer.parseInt(getAuthority().substring(Constants.VIRTUAL_AUTHORITY_PREFIX.length()));
+ }
}
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 f55dfaf2da..8049aa976c 100644
--- a/dspace-api/src/main/java/org/dspace/content/WorkspaceItem.java
+++ b/dspace-api/src/main/java/org/dspace/content/WorkspaceItem.java
@@ -156,11 +156,11 @@ public class WorkspaceItem
return true;
}
Class> objClass = HibernateProxyHelper.getClassWithoutInitializingProxy(o);
- if (getClass() != objClass) {
+ if (!getClass().equals(objClass)) {
return false;
}
final WorkspaceItem that = (WorkspaceItem) o;
- if (this.getID() != that.getID()) {
+ if (!this.getID().equals(that.getID())) {
return false;
}
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 c45f6c737c..8fc302f8bf 100644
--- a/dspace-api/src/main/java/org/dspace/content/WorkspaceItemServiceImpl.java
+++ b/dspace-api/src/main/java/org/dspace/content/WorkspaceItemServiceImpl.java
@@ -212,9 +212,8 @@ public class WorkspaceItemServiceImpl implements WorkspaceItemService {
*/
Item item = workspaceItem.getItem();
if (!authorizeService.isAdmin(context)
- && ((context.getCurrentUser() == null) || (context
- .getCurrentUser().getID() != item.getSubmitter()
- .getID()))) {
+ && (item.getSubmitter() == null || (context.getCurrentUser() == null)
+ || (context.getCurrentUser().getID() != item.getSubmitter().getID()))) {
// Not an admit, not the submitter
throw new AuthorizeException("Must be an administrator or the "
+ "original submitter to delete a workspace item");
@@ -265,7 +264,12 @@ public class WorkspaceItemServiceImpl implements WorkspaceItemService {
// Need to delete the workspaceitem row first since it refers
// to item ID
- workspaceItem.getSupervisorGroups().clear();
+ 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/Choice.java b/dspace-api/src/main/java/org/dspace/content/authority/Choice.java
index 9b68c75d28..6d73bdb5ea 100644
--- a/dspace-api/src/main/java/org/dspace/content/authority/Choice.java
+++ b/dspace-api/src/main/java/org/dspace/content/authority/Choice.java
@@ -33,21 +33,62 @@ public class Choice {
*/
public String value = null;
+ /**
+ * A boolean representing if choice entry value can selected (usually true).
+ * Hierarchical authority can flag some choice as not selectable to force the
+ * use to choice a more detailed terms in the tree, such a leaf or a deeper
+ * branch
+ */
+ public boolean selectable = true;
+
public Map extras = new HashMap();
public Choice() {
}
+ /**
+ * Minimal constructor for this data object. It assumes an empty map of extras
+ * information and a selected choice
+ *
+ * @param authority the authority key
+ * @param value the text value to store in the metadata
+ * @param label the value to display to the user
+ */
public Choice(String authority, String value, String label) {
this.authority = authority;
this.value = value;
this.label = label;
}
+ /**
+ * Constructor to quickly setup the data object for basic authorities. The choice is assumed to be selectable.
+ *
+ * @param authority the authority key
+ * @param value the text value to store in the metadata
+ * @param label the value to display to the user
+ * @param extras a key value map of extra information related to this choice
+ */
public Choice(String authority, String label, String value, Map extras) {
this.authority = authority;
this.label = label;
this.value = value;
this.extras = extras;
}
+
+ /**
+ * Constructor for common need of Hierarchical authorities that want to
+ * explicitely set the selectable flag
+ *
+ * @param authority the authority key
+ * @param value the text value to store in the metadata
+ * @param label the value to display to the user
+ * @param selectable true if the choice can be selected, false if the a more
+ * accurate choice should be preferred
+ */
+ public Choice(String authority, String label, String value, boolean selectable) {
+ this.authority = authority;
+ this.label = label;
+ this.value = value;
+ this.selectable = selectable;
+ }
}
diff --git a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthority.java b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthority.java
index d2d06fe983..750e761f3d 100644
--- a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthority.java
+++ b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthority.java
@@ -7,7 +7,10 @@
*/
package org.dspace.content.authority;
-import org.dspace.content.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.dspace.core.NameAwarePlugin;
/**
* Plugin interface that supplies an authority control mechanism for
@@ -17,7 +20,7 @@ import org.dspace.content.Collection;
* @see ChoiceAuthorityServiceImpl
* @see MetadataAuthorityServiceImpl
*/
-public interface ChoiceAuthority {
+public interface ChoiceAuthority extends NameAwarePlugin {
/**
* Get all values from the authority that match the preferred value.
* Note that the offering was entered by the user and may contain
@@ -32,15 +35,13 @@ public interface ChoiceAuthority {
* defaultSelected index in the Choices instance to the choice, if any,
* that matches the value.
*
- * @param field being matched for
* @param text user's value to match
- * @param collection database ID of Collection for context (owner of Item)
* @param start choice at which to start, 0 is first.
* @param limit maximum number of choices to return, 0 for no limit.
* @param locale explicit localization key if available, or null
* @return a Choices object (never null).
*/
- public Choices getMatches(String field, String text, Collection collection, int start, int limit, String locale);
+ public Choices getMatches(String text, int start, int limit, String locale);
/**
* Get the single "best" match (if any) of a value in the authority
@@ -51,13 +52,11 @@ public interface ChoiceAuthority {
* This call is typically used in non-interactive metadata ingest
* where there is no interactive agent to choose from among options.
*
- * @param field being matched for
* @param text user's value to match
- * @param collection database ID of Collection for context (owner of Item)
* @param locale explicit localization key if available, or null
* @return a Choices object (never null) with 1 or 0 values.
*/
- public Choices getBestMatch(String field, String text, Collection collection, String locale);
+ public Choices getBestMatch(String text, String locale);
/**
* Get the canonical user-visible "label" (i.e. short descriptive text)
@@ -67,31 +66,97 @@ public interface ChoiceAuthority {
* This may get called many times while populating a Web page so it should
* be implemented as efficiently as possible.
*
- * @param field being matched for
* @param key authority key known to this authority.
* @param locale explicit localization key if available, or null
* @return descriptive label - should always return something, never null.
*/
- public String getLabel(String field, String key, String locale);
+ public String getLabel(String key, String locale);
+ /**
+ * Get the canonical value to store for a key in the authority. Can be localized
+ * given the implicit or explicit locale specification.
+ *
+ * @param key authority key known to this authority.
+ * @param locale explicit localization key if available, or null
+ * @return value to store - should always return something, never null.
+ */
+ default String getValue(String key, String locale) {
+ return getLabel(key, locale);
+ }
+
+ /**
+ * Get a map of additional information related to the specified key in the
+ * authority.
+ *
+ * @param key the key of the entry
+ * @param locale explicit localization key if available, or null
+ * @return a map of additional information related to the key
+ */
+ default Map getExtra(String key, String locale) {
+ return new HashMap();
+ }
+
+ /**
+ * Return true for hierarchical authorities
+ *
+ * @return true if hierarchical, default false
+ */
default boolean isHierarchical() {
return false;
}
+ /**
+ * Scrollable authorities allows the scroll of the entries without applying
+ * filter/query to the
+ * {@link #getMatches(String, String, Collection, int, int, String)}
+ *
+ * @return true if scrollable, default false
+ */
default boolean isScrollable() {
return false;
}
- default boolean hasIdentifier() {
- return true;
+ /**
+ * Hierarchical authority can provide an hint for the UI about how many levels
+ * preload to improve the UX. It provides a valid default for hierarchical
+ * authorities
+ *
+ * @return 0 if hierarchical, null otherwise
+ */
+ default Integer getPreloadLevel() {
+ return isHierarchical() ? 0 : null;
}
- default public Choice getChoice(String fieldKey, String authKey, String locale) {
+ /**
+ * Build the preferred choice associated with the authKey. The default
+ * implementation delegate the creato to the {@link #getLabel(String, String)}
+ * {@link #getValue(String, String)} and {@link #getExtra(String, String)}
+ * methods but can be directly overriden for better efficiency or special
+ * scenario
+ *
+ * @param authKey authority key known to this authority.
+ * @param locale explicit localization key if available, or null
+ * @return the preferred choice for this authKey and locale
+ */
+ default public Choice getChoice(String authKey, String locale) {
Choice result = new Choice();
result.authority = authKey;
- result.label = getLabel(fieldKey, authKey, locale);
- result.value = getLabel(fieldKey, authKey, locale);
+ result.label = getLabel(authKey, locale);
+ result.value = getValue(authKey, locale);
+ result.extras.putAll(getExtra(authKey, locale));
return result;
}
+ /**
+ * Provide a recommendation to store the authority in the metadata value if
+ * available in the in the provided choice(s). Usually ChoiceAuthority should
+ * recommend that so the default is true and it only need to be implemented in
+ * the unusual scenario
+ *
+ * @return true if the authority provided in any choice of this
+ * authority should be stored in the metadata value
+ */
+ default public boolean storeAuthorityInMetadata() {
+ return true;
+ }
}
diff --git a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java
index 4cc3f9d6db..0e05852af0 100644
--- a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java
+++ b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java
@@ -7,10 +7,13 @@
*/
package org.dspace.content.authority;
+import java.util.ArrayList;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;
@@ -19,6 +22,9 @@ import org.dspace.app.util.DCInput;
import org.dspace.app.util.DCInputSet;
import org.dspace.app.util.DCInputsReader;
import org.dspace.app.util.DCInputsReaderException;
+import org.dspace.app.util.SubmissionConfig;
+import org.dspace.app.util.SubmissionConfigReader;
+import org.dspace.app.util.SubmissionConfigReaderException;
import org.dspace.content.Collection;
import org.dspace.content.MetadataValue;
import org.dspace.content.authority.service.ChoiceAuthorityService;
@@ -54,23 +60,37 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService
// map of field key to authority plugin
protected Map controller = new HashMap();
+ // map of field key, form definition to authority plugin
+ protected Map> controllerFormDefinitions =
+ new HashMap>();
+
// map of field key to presentation type
protected Map presentation = new HashMap();
// map of field key to closed value
protected Map closed = new HashMap();
- // map of authority name to field key
- protected Map authorities = new HashMap();
+ // flag to track the initialization status of the service
+ private boolean initialized = false;
+
+ // map of authority name to field keys (the same authority can be configured over multiple metadata)
+ protected Map> authorities = new HashMap>();
+
+ // map of authority name to form definition and field keys
+ protected Map>> authoritiesFormDefinitions =
+ new HashMap>>();
+
+ // the item submission reader
+ private SubmissionConfigReader itemSubmissionConfigReader;
@Autowired(required = true)
protected ConfigurationService configurationService;
@Autowired(required = true)
protected PluginService pluginService;
- private final String CHOICES_PLUGIN_PREFIX = "choices.plugin.";
- private final String CHOICES_PRESENTATION_PREFIX = "choices.presentation.";
- private final String CHOICES_CLOSED_PREFIX = "choices.closed.";
+ final static String CHOICES_PLUGIN_PREFIX = "choices.plugin.";
+ final static String CHOICES_PRESENTATION_PREFIX = "choices.presentation.";
+ final static String CHOICES_CLOSED_PREFIX = "choices.closed.";
protected ChoiceAuthorityServiceImpl() {
}
@@ -96,10 +116,25 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService
@Override
public Set getChoiceAuthoritiesNames() {
- if (authorities.keySet().isEmpty()) {
+ init();
+ Set authoritiesNames = new HashSet();
+ authoritiesNames.addAll(authorities.keySet());
+ authoritiesNames.addAll(authoritiesFormDefinitions.keySet());
+ return authoritiesNames;
+ }
+
+ private synchronized void init() {
+ if (!initialized) {
+ try {
+ itemSubmissionConfigReader = new SubmissionConfigReader();
+ } catch (SubmissionConfigReaderException e) {
+ // the system is in an illegal state as the submission definition is not valid
+ throw new IllegalStateException("Error reading the item submission configuration: " + e.getMessage(),
+ e);
+ }
loadChoiceAuthorityConfigurations();
+ initialized = true;
}
- return authorities.keySet();
}
@Override
@@ -112,59 +147,62 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService
@Override
public Choices getMatches(String fieldKey, String query, Collection collection,
int start, int limit, String locale) {
- ChoiceAuthority ma = getChoiceAuthorityMap().get(fieldKey);
+ ChoiceAuthority ma = getAuthorityByFieldKeyCollection(fieldKey, collection);
if (ma == null) {
throw new IllegalArgumentException(
"No choices plugin was configured for field \"" + fieldKey
- + "\".");
+ + "\", collection=" + collection.getID().toString() + ".");
}
- return ma.getMatches(fieldKey, query, collection, start, limit, locale);
+ return ma.getMatches(query, start, limit, locale);
}
+
@Override
public Choices getMatches(String fieldKey, String query, Collection collection, int start, int limit, String locale,
boolean externalInput) {
- ChoiceAuthority ma = getChoiceAuthorityMap().get(fieldKey);
+ ChoiceAuthority ma = getAuthorityByFieldKeyCollection(fieldKey, collection);
if (ma == null) {
throw new IllegalArgumentException(
"No choices plugin was configured for field \"" + fieldKey
- + "\".");
+ + "\", collection=" + collection.getID().toString() + ".");
}
if (externalInput && ma instanceof SolrAuthority) {
((SolrAuthority) ma).addExternalResultsInNextMatches();
}
- return ma.getMatches(fieldKey, query, collection, start, limit, locale);
+ return ma.getMatches(query, start, limit, locale);
}
@Override
public Choices getBestMatch(String fieldKey, String query, Collection collection,
String locale) {
- ChoiceAuthority ma = getChoiceAuthorityMap().get(fieldKey);
+ ChoiceAuthority ma = getAuthorityByFieldKeyCollection(fieldKey, collection);
if (ma == null) {
throw new IllegalArgumentException(
"No choices plugin was configured for field \"" + fieldKey
- + "\".");
+ + "\", collection=" + collection.getID().toString() + ".");
}
- return ma.getBestMatch(fieldKey, query, collection, locale);
+ return ma.getBestMatch(query, locale);
}
@Override
- public String getLabel(MetadataValue metadataValue, String locale) {
- return getLabel(metadataValue.getMetadataField().toString(), metadataValue.getAuthority(), locale);
+ public String getLabel(MetadataValue metadataValue, Collection collection, String locale) {
+ return getLabel(metadataValue.getMetadataField().toString(), collection, metadataValue.getAuthority(), locale);
}
@Override
- public String getLabel(String fieldKey, String authKey, String locale) {
- ChoiceAuthority ma = getChoiceAuthorityMap().get(fieldKey);
+ public String getLabel(String fieldKey, Collection collection, String authKey, String locale) {
+ ChoiceAuthority ma = getAuthorityByFieldKeyCollection(fieldKey, collection);
if (ma == null) {
- throw new IllegalArgumentException("No choices plugin was configured for field \"" + fieldKey + "\".");
+ throw new IllegalArgumentException(
+ "No choices plugin was configured for field \"" + fieldKey
+ + "\", collection=" + collection.getID().toString() + ".");
}
- return ma.getLabel(fieldKey, authKey, locale);
+ return ma.getLabel(authKey, locale);
}
@Override
- public boolean isChoicesConfigured(String fieldKey) {
- return getChoiceAuthorityMap().containsKey(fieldKey);
+ public boolean isChoicesConfigured(String fieldKey, Collection collection) {
+ return getAuthorityByFieldKeyCollection(fieldKey, collection) != null;
}
@Override
@@ -178,8 +216,14 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService
}
@Override
- public List getVariants(MetadataValue metadataValue) {
- ChoiceAuthority ma = getChoiceAuthorityMap().get(metadataValue.getMetadataField().toString());
+ public List getVariants(MetadataValue metadataValue, Collection collection) {
+ String fieldKey = metadataValue.getMetadataField().toString();
+ ChoiceAuthority ma = getAuthorityByFieldKeyCollection(fieldKey, collection);
+ if (ma == null) {
+ throw new IllegalArgumentException(
+ "No choices plugin was configured for field \"" + fieldKey
+ + "\", collection=" + collection.getID().toString() + ".");
+ }
if (ma instanceof AuthorityVariantsSupport) {
AuthorityVariantsSupport avs = (AuthorityVariantsSupport) ma;
return avs.getVariants(metadataValue.getAuthority(), metadataValue.getLanguage());
@@ -189,42 +233,53 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService
@Override
- public String getChoiceAuthorityName(String schema, String element, String qualifier) {
- String makeFieldKey = makeFieldKey(schema, element, qualifier);
- if (getChoiceAuthorityMap().containsKey(makeFieldKey)) {
- for (String key : this.authorities.keySet()) {
- if (this.authorities.get(key).equals(makeFieldKey)) {
- return key;
+ public String getChoiceAuthorityName(String schema, String element, String qualifier, Collection collection) {
+ init();
+ String fieldKey = makeFieldKey(schema, element, qualifier);
+ // check if there is an authority configured for the metadata valid for all the collections
+ if (controller.containsKey(fieldKey)) {
+ for (Entry> authority2md : authorities.entrySet()) {
+ if (authority2md.getValue().contains(fieldKey)) {
+ return authority2md.getKey();
+ }
+ }
+ } else if (collection != null && controllerFormDefinitions.containsKey(fieldKey)) {
+ // there is an authority configured for the metadata valid for some collections,
+ // check if it is the requested collection
+ Map controllerFormDef = controllerFormDefinitions.get(fieldKey);
+ SubmissionConfig submissionConfig = itemSubmissionConfigReader
+ .getSubmissionConfigByCollection(collection.getHandle());
+ String submissionName = submissionConfig.getSubmissionName();
+ // check if the requested collection has a submission definition that use an authority for the metadata
+ if (controllerFormDef.containsKey(submissionName)) {
+ for (Entry>> authority2defs2md :
+ authoritiesFormDefinitions.entrySet()) {
+ List mdByDefinition = authority2defs2md.getValue().get(submissionName);
+ if (mdByDefinition != null && mdByDefinition.contains(fieldKey)) {
+ return authority2defs2md.getKey();
+ }
}
}
}
- return configurationService.getProperty(
- CHOICES_PLUGIN_PREFIX + schema + "." + element + (qualifier != null ? "." + qualifier : ""));
+ return null;
}
protected String makeFieldKey(String schema, String element, String qualifier) {
return Utils.standardize(schema, element, qualifier, "_");
}
- /**
- * Return map of key to ChoiceAuthority plugin
- *
- * @return
- */
- private Map getChoiceAuthorityMap() {
- // If empty, load from configuration
- if (controller.isEmpty()) {
- loadChoiceAuthorityConfigurations();
- }
-
- return controller;
- }
-
@Override
public void clearCache() {
controller.clear();
authorities.clear();
+ presentation.clear();
+ closed.clear();
+ controllerFormDefinitions.clear();
+ authoritiesFormDefinitions.clear();
+ itemSubmissionConfigReader = null;
+ initialized = false;
}
+
private void loadChoiceAuthorityConfigurations() {
// Get all configuration keys starting with a given prefix
List propKeys = configurationService.getPropertyKeys(CHOICES_PLUGIN_PREFIX);
@@ -249,71 +304,127 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService
"Skipping invalid configuration for " + key + " because named plugin not found: " + authorityName);
continue;
}
- if (!authorities.containsKey(authorityName)) {
- controller.put(fkey, ma);
- authorities.put(authorityName, fkey);
- } else {
- log.warn(
- "Skipping invalid configuration for " + key + " because plugin is alredy in use: " +
- authorityName + " used by " + authorities
- .get(authorityName));
- continue;
- }
+ controller.put(fkey, ma);
+ List fkeys;
+ if (authorities.containsKey(authorityName)) {
+ fkeys = authorities.get(authorityName);
+ } else {
+ fkeys = new ArrayList();
+ }
+ fkeys.add(fkey);
+ authorities.put(authorityName, fkeys);
log.debug("Choice Control: For field=" + fkey + ", Plugin=" + ma);
}
autoRegisterChoiceAuthorityFromInputReader();
}
+ /**
+ * This method will register all the authorities that are required due to the
+ * submission forms configuration. This includes authorities for value pairs and
+ * xml vocabularies
+ */
private void autoRegisterChoiceAuthorityFromInputReader() {
try {
+ List submissionConfigs = itemSubmissionConfigReader
+ .getAllSubmissionConfigs(Integer.MAX_VALUE, 0);
DCInputsReader dcInputsReader = new DCInputsReader();
- for (DCInputSet dcinputSet : dcInputsReader.getAllInputs(Integer.MAX_VALUE, 0)) {
- DCInput[][] dcinputs = dcinputSet.getFields();
- for (DCInput[] dcrows : dcinputs) {
- for (DCInput dcinput : dcrows) {
- if (StringUtils.isNotBlank(dcinput.getPairsType())
- || StringUtils.isNotBlank(dcinput.getVocabulary())) {
- String authorityName = dcinput.getPairsType();
- if (StringUtils.isBlank(authorityName)) {
+
+ // loop over all the defined item submission configuration
+ for (SubmissionConfig subCfg : submissionConfigs) {
+ String submissionName = subCfg.getSubmissionName();
+ List inputsBySubmissionName = dcInputsReader.getInputsBySubmissionName(submissionName);
+ // loop over the submission forms configuration eventually associated with the submission panel
+ for (DCInputSet dcinputSet : inputsBySubmissionName) {
+ DCInput[][] dcinputs = dcinputSet.getFields();
+ for (DCInput[] dcrows : dcinputs) {
+ for (DCInput dcinput : dcrows) {
+ // for each input in the form check if it is associated with a real value pairs
+ // or an xml vocabulary
+ String authorityName = null;
+ if (StringUtils.isNotBlank(dcinput.getPairsType())
+ && !StringUtils.equals(dcinput.getInputType(), "qualdrop_value")) {
+ authorityName = dcinput.getPairsType();
+ } else if (StringUtils.isNotBlank(dcinput.getVocabulary())) {
authorityName = dcinput.getVocabulary();
}
- if (!StringUtils.equals(dcinput.getInputType(), "qualdrop_value")) {
+
+ // do we have an authority?
+ if (StringUtils.isNotBlank(authorityName)) {
String fieldKey = makeFieldKey(dcinput.getSchema(), dcinput.getElement(),
dcinput.getQualifier());
ChoiceAuthority ca = controller.get(authorityName);
if (ca == null) {
- InputFormSelfRegisterWrapperAuthority ifa = new
- InputFormSelfRegisterWrapperAuthority();
- if (controller.containsKey(fieldKey)) {
- ifa = (InputFormSelfRegisterWrapperAuthority) controller.get(fieldKey);
- }
-
- ChoiceAuthority ma = (ChoiceAuthority) pluginService
+ ca = (ChoiceAuthority) pluginService
.getNamedPlugin(ChoiceAuthority.class, authorityName);
- if (ma == null) {
- log.warn("Skipping invalid configuration for " + fieldKey
- + " because named plugin not found: " + authorityName);
- continue;
+ if (ca == null) {
+ throw new IllegalStateException("Invalid configuration for " + fieldKey
+ + " in submission definition " + submissionName
+ + ", form definition " + dcinputSet.getFormName()
+ + " no named plugin found: " + authorityName);
}
- ifa.getDelegates().put(dcinputSet.getFormName(), ma);
- controller.put(fieldKey, ifa);
- }
-
- if (!authorities.containsKey(authorityName)) {
- authorities.put(authorityName, fieldKey);
}
+ addAuthorityToFormCacheMap(submissionName, fieldKey, ca);
+ addFormDetailsToAuthorityCacheMap(submissionName, authorityName, fieldKey);
}
}
}
}
}
} catch (DCInputsReaderException e) {
- throw new IllegalStateException(e.getMessage(), e);
+ // the system is in an illegal state as the submission definition is not valid
+ throw new IllegalStateException("Error reading the item submission configuration: " + e.getMessage(),
+ e);
}
}
+ /**
+ * Add the form/field to the cache map keeping track of which form/field are
+ * associated with the specific authority name
+ *
+ * @param submissionName the form definition name
+ * @param authorityName the name of the authority plugin
+ * @param fieldKey the field key that use the authority
+ */
+ private void addFormDetailsToAuthorityCacheMap(String submissionName, String authorityName, String fieldKey) {
+ Map> submissionDefinitionNames2fieldKeys;
+ if (authoritiesFormDefinitions.containsKey(authorityName)) {
+ submissionDefinitionNames2fieldKeys = authoritiesFormDefinitions.get(authorityName);
+ } else {
+ submissionDefinitionNames2fieldKeys = new HashMap>();
+ }
+
+ List fields;
+ if (submissionDefinitionNames2fieldKeys.containsKey(submissionName)) {
+ fields = submissionDefinitionNames2fieldKeys.get(submissionName);
+ } else {
+ fields = new ArrayList();
+ }
+ fields.add(fieldKey);
+ submissionDefinitionNames2fieldKeys.put(submissionName, fields);
+ authoritiesFormDefinitions.put(authorityName, submissionDefinitionNames2fieldKeys);
+ }
+
+ /**
+ * Add the authority plugin to the cache map keeping track of which authority is
+ * used by a specific form/field
+ *
+ * @param submissionName the submission definition name
+ * @param fieldKey the field key that require the authority
+ * @param ca the authority plugin
+ */
+ private void addAuthorityToFormCacheMap(String submissionName, String fieldKey, ChoiceAuthority ca) {
+ Map definition2authority;
+ if (controllerFormDefinitions.containsKey(fieldKey)) {
+ definition2authority = controllerFormDefinitions.get(fieldKey);
+ } else {
+ definition2authority = new HashMap();
+ }
+ definition2authority.put(submissionName, ca);
+ controllerFormDefinitions.put(fieldKey, definition2authority);
+ }
+
/**
* Return map of key to presentation
*
@@ -370,26 +481,6 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService
return closed;
}
- @Override
- public String getChoiceMetadatabyAuthorityName(String name) {
- if (authorities.isEmpty()) {
- loadChoiceAuthorityConfigurations();
- }
- if (authorities.containsKey(name)) {
- return authorities.get(name);
- }
- return null;
- }
-
- @Override
- public Choice getChoice(String fieldKey, String authKey, String locale) {
- ChoiceAuthority ma = getChoiceAuthorityMap().get(fieldKey);
- if (ma == null) {
- throw new IllegalArgumentException("No choices plugin was configured for field \"" + fieldKey + "\".");
- }
- return ma.getChoice(fieldKey, authKey, locale);
- }
-
@Override
public ChoiceAuthority getChoiceAuthorityByAuthorityName(String authorityName) {
ChoiceAuthority ma = (ChoiceAuthority)
@@ -401,4 +492,68 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService
}
return ma;
}
+
+ private ChoiceAuthority getAuthorityByFieldKeyCollection(String fieldKey, Collection collection) {
+ init();
+ ChoiceAuthority ma = controller.get(fieldKey);
+ if (ma == null && collection != null) {
+ SubmissionConfigReader configReader;
+ try {
+ configReader = new SubmissionConfigReader();
+ SubmissionConfig submissionName = configReader.getSubmissionConfigByCollection(collection.getHandle());
+ ma = controllerFormDefinitions.get(fieldKey).get(submissionName.getSubmissionName());
+ } catch (SubmissionConfigReaderException e) {
+ // the system is in an illegal state as the submission definition is not valid
+ throw new IllegalStateException("Error reading the item submission configuration: " + e.getMessage(),
+ e);
+ }
+ }
+ return ma;
+ }
+
+ @Override
+ public boolean storeAuthority(String fieldKey, Collection collection) {
+ // currently only named authority can eventually provide real authority
+ return controller.containsKey(fieldKey);
+ }
+
+ /**
+ * Wrapper that calls getChoicesByParent method of the plugin.
+ *
+ * @param authorityName authority name
+ * @param parentId parent Id
+ * @param start choice at which to start, 0 is first.
+ * @param limit maximum number of choices to return, 0 for no limit.
+ * @param locale explicit localization key if available, or null
+ * @return a Choices object (never null).
+ * @see org.dspace.content.authority.ChoiceAuthority#getChoicesByParent(java.lang.String, java.lang.String,
+ * int, int, java.lang.String)
+ */
+ @Override
+ public Choices getChoicesByParent(String authorityName, String parentId, int start, int limit, String locale) {
+ HierarchicalAuthority ma = (HierarchicalAuthority) getChoiceAuthorityByAuthorityName(authorityName);
+ return ma.getChoicesByParent(authorityName, parentId, start, limit, locale);
+ }
+
+ /**
+ * Wrapper that calls getTopChoices method of the plugin.
+ *
+ * @param authorityName authority name
+ * @param start choice at which to start, 0 is first.
+ * @param limit maximum number of choices to return, 0 for no limit.
+ * @param locale explicit localization key if available, or null
+ * @return a Choices object (never null).
+ * @see org.dspace.content.authority.ChoiceAuthority#getTopChoices(java.lang.String, int, int, java.lang.String)
+ */
+ @Override
+ public Choices getTopChoices(String authorityName, int start, int limit, String locale) {
+ HierarchicalAuthority ma = (HierarchicalAuthority) getChoiceAuthorityByAuthorityName(authorityName);
+ return ma.getTopChoices(authorityName, start, limit, locale);
+ }
+
+ @Override
+ public Choice getParentChoice(String authorityName, String vocabularyId, String locale) {
+ HierarchicalAuthority ma = (HierarchicalAuthority) getChoiceAuthorityByAuthorityName(authorityName);
+ return ma.getParentChoice(authorityName, vocabularyId, locale);
+ }
}
diff --git a/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java b/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java
index a64ebdd971..b1d8cf36a5 100644
--- a/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java
+++ b/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java
@@ -9,14 +9,20 @@ package org.dspace.content.authority;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.Logger;
import org.dspace.app.util.DCInputsReader;
import org.dspace.app.util.DCInputsReaderException;
-import org.dspace.content.Collection;
+import org.dspace.core.I18nUtil;
import org.dspace.core.SelfNamedPlugin;
/**
@@ -44,16 +50,38 @@ import org.dspace.core.SelfNamedPlugin;
public class DCInputAuthority extends SelfNamedPlugin implements ChoiceAuthority {
private static Logger log = org.apache.logging.log4j.LogManager.getLogger(DCInputAuthority.class);
- private String values[] = null;
- private String labels[] = null;
+ /**
+ * The map of the values available for a specific language. Examples of keys are
+ * "en", "it", "uk"
+ */
+ private Map values = null;
- private static DCInputsReader dci = null;
+ /**
+ * The map of the labels available for a specific language. Examples of keys are
+ * "en", "it", "uk"
+ */
+ private Map labels = null;
+
+ /**
+ * The map of the input form reader associated to use for a specific java locale
+ */
+ private static Map dcis = null;
private static String pluginNames[] = null;
public DCInputAuthority() {
super();
}
+ @Override
+ public boolean storeAuthorityInMetadata() {
+ // For backward compatibility value pairs don't store authority in
+ // the metadatavalue
+ return false;
+ }
+ public static void reset() {
+ pluginNames = null;
+ }
+
public static String[] getPluginNames() {
if (pluginNames == null) {
initPluginNames();
@@ -63,20 +91,28 @@ public class DCInputAuthority extends SelfNamedPlugin implements ChoiceAuthority
}
private static synchronized void initPluginNames() {
+ Locale[] locales = I18nUtil.getSupportedLocales();
+ Set names = new HashSet();
if (pluginNames == null) {
try {
- if (dci == null) {
- dci = new DCInputsReader();
+ dcis = new HashMap();
+ for (Locale locale : locales) {
+ dcis.put(locale, new DCInputsReader(I18nUtil.getInputFormsFileName(locale)));
+ }
+ for (Locale l : locales) {
+ Iterator pi = dcis.get(l).getPairsNameIterator();
+ while (pi.hasNext()) {
+ names.add((String) pi.next());
+ }
+ }
+ DCInputsReader dcirDefault = new DCInputsReader();
+ Iterator pi = dcirDefault.getPairsNameIterator();
+ while (pi.hasNext()) {
+ names.add((String) pi.next());
}
} catch (DCInputsReaderException e) {
log.error("Failed reading DCInputs initialization: ", e);
}
- List names = new ArrayList();
- Iterator pi = dci.getPairsNameIterator();
- while (pi.hasNext()) {
- names.add((String) pi.next());
- }
-
pluginNames = names.toArray(new String[names.size()]);
log.debug("Got plugin names = " + Arrays.deepToString(pluginNames));
}
@@ -85,45 +121,65 @@ public class DCInputAuthority extends SelfNamedPlugin implements ChoiceAuthority
// once-only load of values and labels
private void init() {
if (values == null) {
+ values = new HashMap();
+ labels = new HashMap();
String pname = this.getPluginInstanceName();
- List pairs = dci.getPairs(pname);
- if (pairs != null) {
- values = new String[pairs.size() / 2];
- labels = new String[pairs.size() / 2];
- for (int i = 0; i < pairs.size(); i += 2) {
- labels[i / 2] = pairs.get(i);
- values[i / 2] = pairs.get(i + 1);
+ for (Locale l : dcis.keySet()) {
+ DCInputsReader dci = dcis.get(l);
+ List pairs = dci.getPairs(pname);
+ if (pairs != null) {
+ String[] valuesLocale = new String[pairs.size() / 2];
+ String[]labelsLocale = new String[pairs.size() / 2];
+ for (int i = 0; i < pairs.size(); i += 2) {
+ labelsLocale[i / 2] = pairs.get(i);
+ valuesLocale[i / 2] = pairs.get(i + 1);
+ }
+ values.put(l.getLanguage(), valuesLocale);
+ labels.put(l.getLanguage(), labelsLocale);
+ log.debug("Found pairs for name=" + pname + ",locale=" + l);
+ } else {
+ log.error("Failed to find any pairs for name=" + pname, new IllegalStateException());
}
- log.debug("Found pairs for name=" + pname);
- } else {
- log.error("Failed to find any pairs for name=" + pname, new IllegalStateException());
}
+
}
}
@Override
- public Choices getMatches(String field, String query, Collection collection, int start, int limit, String locale) {
+ public Choices getMatches(String query, int start, int limit, String locale) {
init();
-
+ Locale currentLocale = I18nUtil.getSupportedLocale(locale);
+ String[] valuesLocale = values.get(currentLocale.getLanguage());
+ String[] labelsLocale = labels.get(currentLocale.getLanguage());
int dflt = -1;
- Choice v[] = new Choice[values.length];
- for (int i = 0; i < values.length; ++i) {
- v[i] = new Choice(values[i], values[i], labels[i]);
- if (values[i].equalsIgnoreCase(query)) {
- dflt = i;
+ int found = 0;
+ List v = new ArrayList();
+ for (int i = 0; i < valuesLocale.length; ++i) {
+ if (query == null || StringUtils.containsIgnoreCase(valuesLocale[i], query)) {
+ if (found >= start && v.size() < limit) {
+ v.add(new Choice(null, valuesLocale[i], labelsLocale[i]));
+ if (valuesLocale[i].equalsIgnoreCase(query)) {
+ dflt = i;
+ }
+ }
+ found++;
}
}
- return new Choices(v, 0, v.length, Choices.CF_AMBIGUOUS, false, dflt);
+ Choice[] vArray = new Choice[v.size()];
+ return new Choices(v.toArray(vArray), start, found, Choices.CF_AMBIGUOUS, false, dflt);
}
@Override
- public Choices getBestMatch(String field, String text, Collection collection, String locale) {
+ public Choices getBestMatch(String text, String locale) {
init();
- for (int i = 0; i < values.length; ++i) {
- if (text.equalsIgnoreCase(values[i])) {
+ Locale currentLocale = I18nUtil.getSupportedLocale(locale);
+ String[] valuesLocale = values.get(currentLocale.getLanguage());
+ String[] labelsLocale = labels.get(currentLocale.getLanguage());
+ for (int i = 0; i < valuesLocale.length; ++i) {
+ if (text.equalsIgnoreCase(valuesLocale[i])) {
Choice v[] = new Choice[1];
- v[0] = new Choice(String.valueOf(i), values[i], labels[i]);
+ v[0] = new Choice(String.valueOf(i), valuesLocale[i], labelsLocale[i]);
return new Choices(v, 0, v.length, Choices.CF_UNCERTAIN, false, 0);
}
}
@@ -131,19 +187,31 @@ public class DCInputAuthority extends SelfNamedPlugin implements ChoiceAuthority
}
@Override
- public String getLabel(String field, String key, String locale) {
+ public String getLabel(String key, String locale) {
init();
+
+ // Get default if locale is empty
+ if (StringUtils.isBlank(locale)) {
+ locale = I18nUtil.getDefaultLocale().getLanguage();
+ }
+
+ String[] labelsLocale = labels.get(locale);
int pos = -1;
- for (int i = 0; i < values.length; i++) {
- if (values[i].equals(key)) {
+ for (int i = 0; i < labelsLocale.length; i++) {
+ if (labelsLocale[i].equals(key)) {
pos = i;
break;
}
}
if (pos != -1) {
- return labels[pos];
+ return labelsLocale[pos];
} else {
return "UNKNOWN KEY " + key;
}
}
+
+ @Override
+ public boolean isScrollable() {
+ return true;
+ }
}
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 097a19eb13..00c74bea9d 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
@@ -10,7 +10,9 @@ package org.dspace.content.authority;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
@@ -19,7 +21,6 @@ import javax.xml.xpath.XPathFactory;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.Logger;
-import org.dspace.content.Collection;
import org.dspace.core.SelfNamedPlugin;
import org.dspace.services.ConfigurationService;
import org.dspace.services.factory.DSpaceServicesFactory;
@@ -54,25 +55,35 @@ import org.xml.sax.InputSource;
* @author Michael B. Klein
*/
-public class DSpaceControlledVocabulary extends SelfNamedPlugin implements ChoiceAuthority {
+public class DSpaceControlledVocabulary extends SelfNamedPlugin implements HierarchicalAuthority {
private static Logger log = org.apache.logging.log4j.LogManager.getLogger(DSpaceControlledVocabulary.class);
protected static String xpathTemplate = "//node[contains(translate(@label,'ABCDEFGHIJKLMNOPQRSTUVWXYZ'," +
"'abcdefghijklmnopqrstuvwxyz'),'%s')]";
protected static String idTemplate = "//node[@id = '%s']";
- protected static String idParentTemplate = "//node[@id = '%s']/parent::isComposedBy";
+ protected static String labelTemplate = "//node[@label = '%s']";
+ protected static String idParentTemplate = "//node[@id = '%s']/parent::isComposedBy/parent::node";
+ protected static String rootTemplate = "/node";
protected static String pluginNames[] = null;
protected String vocabularyName = null;
protected InputSource vocabulary = null;
- protected Boolean suggestHierarchy = true;
+ protected Boolean suggestHierarchy = false;
protected Boolean storeHierarchy = true;
protected String hierarchyDelimiter = "::";
+ protected Integer preloadLevel = 1;
public DSpaceControlledVocabulary() {
super();
}
+ @Override
+ public boolean storeAuthorityInMetadata() {
+ // For backward compatibility controlled vocabularies don't store the node id in
+ // the metadatavalue
+ return false;
+ }
+
public static String[] getPluginNames() {
if (pluginNames == null) {
initPluginNames();
@@ -112,6 +123,7 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Choic
String configurationPrefix = "vocabulary.plugin." + vocabularyName;
storeHierarchy = config.getBooleanProperty(configurationPrefix + ".hierarchy.store", storeHierarchy);
suggestHierarchy = config.getBooleanProperty(configurationPrefix + ".hierarchy.suggest", suggestHierarchy);
+ preloadLevel = config.getIntProperty(configurationPrefix + ".hierarchy.preloadLevel", preloadLevel);
String configuredDelimiter = config.getProperty(configurationPrefix + ".delimiter");
if (configuredDelimiter != null) {
hierarchyDelimiter = configuredDelimiter.replaceAll("(^\"|\"$)", "");
@@ -142,7 +154,7 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Choic
}
@Override
- public Choices getMatches(String field, String text, Collection collection, int start, int limit, String locale) {
+ public Choices getMatches(String text, int start, int limit, String locale) {
init();
log.debug("Getting matches for '" + text + "'");
String xpathExpression = "";
@@ -151,59 +163,60 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Choic
xpathExpression += String.format(xpathTemplate, textHierarchy[i].replaceAll("'", "'").toLowerCase());
}
XPath xpath = XPathFactory.newInstance().newXPath();
- Choice[] choices;
+ int total = 0;
+ List choices = new ArrayList();
try {
NodeList results = (NodeList) xpath.evaluate(xpathExpression, vocabulary, XPathConstants.NODESET);
- String[] authorities = new String[results.getLength()];
- String[] values = new String[results.getLength()];
- String[] labels = new String[results.getLength()];
- String[] parent = new String[results.getLength()];
- String[] notes = new String[results.getLength()];
- for (int i = 0; i < results.getLength(); i++) {
- Node node = results.item(i);
- readNode(authorities, values, labels, parent, notes, i, node);
- }
- int resultCount = labels.length - start;
- // limit = 0 means no limit
- if ((limit > 0) && (resultCount > limit)) {
- resultCount = limit;
- }
- choices = new Choice[resultCount];
- if (resultCount > 0) {
- for (int i = 0; i < resultCount; i++) {
- choices[i] = new Choice(authorities[start + i], values[start + i], labels[start + i]);
- if (StringUtils.isNotBlank(parent[i])) {
- choices[i].extras.put("parent", parent[i]);
- }
- if (StringUtils.isNotBlank(notes[i])) {
- choices[i].extras.put("note", notes[i]);
- }
- }
- }
+ total = results.getLength();
+ choices = getChoicesFromNodeList(results, start, limit);
} catch (XPathExpressionException e) {
- choices = new Choice[0];
+ log.warn(e.getMessage(), e);
+ return new Choices(true);
}
- return new Choices(choices, 0, choices.length, Choices.CF_AMBIGUOUS, false);
+ return new Choices(choices.toArray(new Choice[choices.size()]), start, total, Choices.CF_AMBIGUOUS,
+ total > start + limit);
}
@Override
- public Choices getBestMatch(String field, String text, Collection collection, String locale) {
+ public Choices getBestMatch(String text, String locale) {
init();
- log.debug("Getting best match for '" + text + "'");
- return getMatches(field, text, collection, 0, 2, locale);
- }
-
- @Override
- public String getLabel(String field, String key, String locale) {
- init();
- String xpathExpression = String.format(idTemplate, key);
+ log.debug("Getting best matches for '" + text + "'");
+ String xpathExpression = "";
+ String[] textHierarchy = text.split(hierarchyDelimiter, -1);
+ for (int i = 0; i < textHierarchy.length; i++) {
+ xpathExpression += String.format(labelTemplate, textHierarchy[i].replaceAll("'", "'"));
+ }
XPath xpath = XPathFactory.newInstance().newXPath();
+ List choices = new ArrayList();
try {
- Node node = (Node) xpath.evaluate(xpathExpression, vocabulary, XPathConstants.NODE);
- return node.getAttributes().getNamedItem("label").getNodeValue();
+ NodeList results = (NodeList) xpath.evaluate(xpathExpression, vocabulary, XPathConstants.NODESET);
+ choices = getChoicesFromNodeList(results, 0, 1);
} catch (XPathExpressionException e) {
- return ("");
+ log.warn(e.getMessage(), e);
+ return new Choices(true);
}
+ return new Choices(choices.toArray(new Choice[choices.size()]), 0, choices.size(), Choices.CF_AMBIGUOUS, false);
+ }
+
+ @Override
+ public String getLabel(String key, String locale) {
+ return getNodeLabel(key, this.suggestHierarchy);
+ }
+
+ @Override
+ public String getValue(String key, String locale) {
+ return getNodeLabel(key, this.storeHierarchy);
+ }
+
+ @Override
+ public Choice getChoice(String authKey, String locale) {
+ Node node;
+ try {
+ node = getNode(authKey);
+ } catch (XPathExpressionException e) {
+ return null;
+ }
+ return createChoiceFromNode(node);
}
@Override
@@ -212,81 +225,227 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Choic
}
@Override
- public Choice getChoice(String fieldKey, String authKey, String locale) {
+ public Choices getTopChoices(String authorityName, int start, int limit, String locale) {
init();
- log.debug("Getting matches for '" + authKey + "'");
- String xpathExpression = String.format(idTemplate, authKey);
- XPath xpath = XPathFactory.newInstance().newXPath();
- try {
- Node node = (Node) xpath.evaluate(xpathExpression, vocabulary, XPathConstants.NODE);
- if (node != null) {
- String[] authorities = new String[1];
- String[] values = new String[1];
- String[] labels = new String[1];
- String[] parent = new String[1];
- String[] note = new String[1];
- readNode(authorities, values, labels, parent, note, 0, node);
-
- if (values.length > 0) {
- Choice choice = new Choice(authorities[0], values[0], labels[0]);
- if (StringUtils.isNotBlank(parent[0])) {
- choice.extras.put("parent", parent[0]);
- }
- if (StringUtils.isNotBlank(note[0])) {
- choice.extras.put("note", note[0]);
- }
- return choice;
- }
- }
- } catch (XPathExpressionException e) {
- log.warn(e.getMessage(), e);
- }
- return null;
+ String xpathExpression = rootTemplate;
+ return getChoicesByXpath(xpathExpression, start, limit);
}
- private void readNode(String[] authorities, String[] values, String[] labels, String[] parent, String[] notes,
- int i, Node node) {
+ @Override
+ public Choices getChoicesByParent(String authorityName, String parentId, int start, int limit, String locale) {
+ init();
+ String xpathExpression = String.format(idTemplate, parentId);
+ return getChoicesByXpath(xpathExpression, start, limit);
+ }
+
+ @Override
+ public Choice getParentChoice(String authorityName, String childId, String locale) {
+ init();
+ try {
+ String xpathExpression = String.format(idParentTemplate, childId);
+ Choice choice = createChoiceFromNode(getNodeFromXPath(xpathExpression));
+ return choice;
+ } catch (XPathExpressionException e) {
+ log.error(e.getMessage(), e);
+ return null;
+ }
+ }
+
+ @Override
+ public Integer getPreloadLevel() {
+ return preloadLevel;
+ }
+
+ private boolean isRootElement(Node node) {
+ if (node != null && node.getOwnerDocument().getDocumentElement().equals(node)) {
+ return true;
+ }
+ return false;
+ }
+
+ private Node getNode(String key) throws XPathExpressionException {
+ init();
+ String xpathExpression = String.format(idTemplate, key);
+ Node node = getNodeFromXPath(xpathExpression);
+ return node;
+ }
+
+ private Node getNodeFromXPath(String xpathExpression) throws XPathExpressionException {
+ XPath xpath = XPathFactory.newInstance().newXPath();
+ Node node = (Node) xpath.evaluate(xpathExpression, vocabulary, XPathConstants.NODE);
+ return node;
+ }
+
+ private List getChoicesFromNodeList(NodeList results, int start, int limit) {
+ List choices = new ArrayList();
+ for (int i = 0; i < results.getLength(); i++) {
+ if (i < start) {
+ continue;
+ }
+ if (choices.size() == limit) {
+ break;
+ }
+ Node node = results.item(i);
+ Choice choice = new Choice(getAuthority(node), getLabel(node), getValue(node),
+ isSelectable(node));
+ choice.extras = addOtherInformation(getParent(node), getNote(node), getChildren(node), getAuthority(node));
+ choices.add(choice);
+ }
+ return choices;
+ }
+
+ private Map addOtherInformation(String parentCurr, String noteCurr,
+ List childrenCurr, String authorityCurr) {
+ Map extras = new HashMap();
+ if (StringUtils.isNotBlank(parentCurr)) {
+ extras.put("parent", parentCurr);
+ }
+ if (StringUtils.isNotBlank(noteCurr)) {
+ extras.put("note", noteCurr);
+ }
+ if (childrenCurr.size() > 0) {
+ extras.put("hasChildren", "true");
+ } else {
+ extras.put("hasChildren", "false");
+ }
+ extras.put("id", authorityCurr);
+ return extras;
+ }
+
+ private String getNodeLabel(String key, boolean useHierarchy) {
+ try {
+ Node node = getNode(key);
+ if (useHierarchy) {
+ return this.buildString(node);
+ } else {
+ return node.getAttributes().getNamedItem("label").getNodeValue();
+ }
+ } catch (XPathExpressionException e) {
+ return ("");
+ }
+ }
+
+ private String getLabel(Node node) {
String hierarchy = this.buildString(node);
if (this.suggestHierarchy) {
- labels[i] = hierarchy;
+ return hierarchy;
} else {
- labels[i] = node.getAttributes().getNamedItem("label").getNodeValue();
- }
- if (this.storeHierarchy) {
- values[i] = hierarchy;
- } else {
- values[i] = node.getAttributes().getNamedItem("label").getNodeValue();
+ return node.getAttributes().getNamedItem("label").getNodeValue();
}
+ }
+ private String getValue(Node node) {
+ String hierarchy = this.buildString(node);
+ if (this.storeHierarchy) {
+ return hierarchy;
+ } else {
+ return node.getAttributes().getNamedItem("label").getNodeValue();
+ }
+ }
+
+ private String getNote(Node node) {
NodeList childNodes = node.getChildNodes();
for (int ci = 0; ci < childNodes.getLength(); ci++) {
Node firstChild = childNodes.item(ci);
if (firstChild != null && "hasNote".equals(firstChild.getNodeName())) {
String nodeValue = firstChild.getTextContent();
if (StringUtils.isNotBlank(nodeValue)) {
- notes[i] = nodeValue;
+ return nodeValue;
}
}
}
- Node idAttr = node.getAttributes().getNamedItem("id");
- if (null != idAttr) { // 'id' is optional
- authorities[i] = idAttr.getNodeValue();
- if (isHierarchical()) {
- Node parentN = node.getParentNode();
- if (parentN != null) {
- parentN = parentN.getParentNode();
- if (parentN != null) {
- Node parentIdAttr = parentN.getAttributes().getNamedItem("id");
- if (null != parentIdAttr) {
- parent[i] = parentIdAttr.getNodeValue();
+ return null;
+ }
+
+ private List getChildren(Node node) {
+ List children = new ArrayList();
+ NodeList childNodes = node.getChildNodes();
+ for (int ci = 0; ci < childNodes.getLength(); ci++) {
+ Node firstChild = childNodes.item(ci);
+ if (firstChild != null && "isComposedBy".equals(firstChild.getNodeName())) {
+ for (int cii = 0; cii < firstChild.getChildNodes().getLength(); cii++) {
+ Node childN = firstChild.getChildNodes().item(cii);
+ if (childN != null && "node".equals(childN.getNodeName())) {
+ Node childIdAttr = childN.getAttributes().getNamedItem("id");
+ if (null != childIdAttr) {
+ children.add(childIdAttr.getNodeValue());
}
}
}
+ break;
}
- } else {
- authorities[i] = null;
- parent[i] = null;
+ }
+ return children;
+ }
+
+ private boolean isSelectable(Node node) {
+ Node selectableAttr = node.getAttributes().getNamedItem("selectable");
+ if (null != selectableAttr) {
+ return Boolean.valueOf(selectableAttr.getNodeValue());
+ } else { // Default is true
+ return true;
}
}
+ private String getParent(Node node) {
+ Node parentN = node.getParentNode();
+ if (parentN != null) {
+ parentN = parentN.getParentNode();
+ if (parentN != null && !isRootElement(parentN)) {
+ return buildString(parentN);
+ }
+ }
+ return null;
+ }
+
+ private String getAuthority(Node node) {
+ Node idAttr = node.getAttributes().getNamedItem("id");
+ if (null != idAttr) { // 'id' is optional
+ return idAttr.getNodeValue();
+ } else {
+ return null;
+ }
+ }
+
+ private Choices getChoicesByXpath(String xpathExpression, int start, int limit) {
+ List choices = new ArrayList();
+ XPath xpath = XPathFactory.newInstance().newXPath();
+ try {
+ Node parentNode = (Node) xpath.evaluate(xpathExpression, vocabulary, XPathConstants.NODE);
+ int count = 0;
+ if (parentNode != null) {
+ NodeList childNodes = (NodeList) xpath.evaluate(".//isComposedBy", parentNode, XPathConstants.NODE);
+ if (null != childNodes) {
+ for (int i = 0; i < childNodes.getLength(); i++) {
+ Node childNode = childNodes.item(i);
+ if (childNode != null && "node".equals(childNode.getNodeName())) {
+ if (count < start || choices.size() >= limit) {
+ count++;
+ continue;
+ }
+ count++;
+ choices.add(createChoiceFromNode(childNode));
+ }
+ }
+ }
+ return new Choices(choices.toArray(new Choice[choices.size()]), start, count,
+ Choices.CF_AMBIGUOUS, false);
+ }
+ } catch (XPathExpressionException e) {
+ log.warn(e.getMessage(), e);
+ return new Choices(true);
+ }
+ return new Choices(false);
+ }
+
+ private Choice createChoiceFromNode(Node node) {
+ if (node != null && !isRootElement(node)) {
+ Choice choice = new Choice(getAuthority(node), getLabel(node), getValue(node),
+ isSelectable(node));
+ choice.extras = addOtherInformation(getParent(node), getNote(node),getChildren(node), getAuthority(node));
+ return choice;
+ }
+ return null;
+ }
+
}
diff --git a/dspace-api/src/main/java/org/dspace/content/authority/HierarchicalAuthority.java b/dspace-api/src/main/java/org/dspace/content/authority/HierarchicalAuthority.java
new file mode 100644
index 0000000000..c25b74d354
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/content/authority/HierarchicalAuthority.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.authority;
+
+/**
+ * Plugin interface that supplies an authority control mechanism for
+ * one metadata field.
+ *
+ * @author Larry Stone
+ * @see ChoiceAuthority
+ */
+public interface HierarchicalAuthority extends ChoiceAuthority {
+
+ /**
+ * Get all values from the authority that match the preferred value.
+ * Note that the offering was entered by the user and may contain
+ * mixed/incorrect case, whitespace, etc so the plugin should be careful
+ * to clean up user data before making comparisons.
+ *
+ * Value of a "Name" field will be in canonical DSpace person name format,
+ * which is "Lastname, Firstname(s)", e.g. "Smith, John Q.".
+ *
+ * Some authorities with a small set of values may simply return the whole
+ * set for any sample value, although it's a good idea to set the
+ * defaultSelected index in the Choices instance to the choice, if any,
+ * that matches the value.
+ *
+ * @param authorityName authority name
+ * @param start choice at which to start, 0 is first.
+ * @param limit maximum number of choices to return, 0 for no limit.
+ * @param locale explicit localization key if available, or null
+ * @return a Choices object (never null).
+ */
+ public Choices getTopChoices(String authorityName, int start, int limit, String locale);
+
+ /**
+ * Get all values from the authority that match the preferred value.
+ * Note that the offering was entered by the user and may contain
+ * mixed/incorrect case, whitespace, etc so the plugin should be careful
+ * to clean up user data before making comparisons.
+ *
+ * Value of a "Name" field will be in canonical DSpace person name format,
+ * which is "Lastname, Firstname(s)", e.g. "Smith, John Q.".
+ *
+ * Some authorities with a small set of values may simply return the whole
+ * set for any sample value, although it's a good idea to set the
+ * defaultSelected index in the Choices instance to the choice, if any,
+ * that matches the value.
+ *
+ * @param authorityName authority name
+ * @param parentId user's value to match
+ * @param start choice at which to start, 0 is first.
+ * @param limit maximum number of choices to return, 0 for no limit.
+ * @param locale explicit localization key if available, or null
+ * @return a Choices object (never null).
+ */
+ public Choices getChoicesByParent(String authorityName, String parentId, int start, int limit, String locale);
+
+ /**
+ * It returns the parent choice in the hierarchy if any
+ *
+ * @param authorityName authority name
+ * @param vocabularyId user's value to match
+ * @param locale explicit localization key if available, or null
+ * @return a Choice object
+ */
+ public Choice getParentChoice(String authorityName, String vocabularyId, String locale);
+
+ /**
+ * Provides an hint for the UI to preload some levels to improve the UX. It
+ * usually mean that these preloaded level will be shown expanded by default
+ */
+ public Integer getPreloadLevel();
+
+ @Override
+ default boolean isHierarchical() {
+ return true;
+ }
+
+}
\ No newline at end of file
diff --git a/dspace-api/src/main/java/org/dspace/content/authority/InputFormSelfRegisterWrapperAuthority.java b/dspace-api/src/main/java/org/dspace/content/authority/InputFormSelfRegisterWrapperAuthority.java
deleted file mode 100644
index 8716ef38b9..0000000000
--- a/dspace-api/src/main/java/org/dspace/content/authority/InputFormSelfRegisterWrapperAuthority.java
+++ /dev/null
@@ -1,166 +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.authority;
-
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-import org.apache.commons.lang3.StringUtils;
-import org.apache.logging.log4j.Logger;
-import org.dspace.app.util.DCInputsReader;
-import org.dspace.app.util.DCInputsReaderException;
-import org.dspace.content.Collection;
-
-/**
- * This authority is registered automatically by the ChoiceAuthorityService for
- * all the metadata that use a value-pair or a vocabulary in the submission-form.xml
- *
- * It keeps a map of form-name vs ChoiceAuthority to delegate the execution of
- * the method to the specific ChoiceAuthority configured for the collection when
- * the same metadata have different vocabulary or value-pair on a collection
- * basis
- *
- * @author Andrea Bollini (andrea.bollini at 4science.it)
- */
-public class InputFormSelfRegisterWrapperAuthority implements ChoiceAuthority {
-
- private static Logger log =
- org.apache.logging.log4j.LogManager.getLogger(InputFormSelfRegisterWrapperAuthority.class);
-
- private Map delegates = new HashMap();
-
- private static DCInputsReader dci = null;
-
- private void init() {
- try {
- if (dci == null) {
- dci = new DCInputsReader();
- }
- } catch (DCInputsReaderException e) {
- log.error("Failed reading DCInputs initialization: ", e);
- }
- }
-
- @Override
- public Choices getMatches(String field, String query, Collection collection, int start, int limit, String locale) {
- String formName;
- try {
- init();
- if (collection == null) {
- Set choices = new HashSet();
- //workaround search in all authority configured
- for (ChoiceAuthority ca : delegates.values()) {
- Choices tmp = ca.getMatches(field, query, null, start, limit, locale);
- if (tmp.total > 0) {
- Set mySet = new HashSet(Arrays.asList(tmp.values));
- choices.addAll(mySet);
- }
- }
- if (!choices.isEmpty()) {
- Choice[] results = new Choice[choices.size()];
- choices.toArray(results);
- return new Choices(results, 0, choices.size(), Choices.CF_AMBIGUOUS, false);
- }
- } else {
- formName = dci.getInputFormNameByCollectionAndField(collection, field);
- return delegates.get(formName).getMatches(field, query, collection, start, limit, locale);
- }
- } catch (DCInputsReaderException e) {
- log.error(e.getMessage(), e);
- }
- return new Choices(Choices.CF_NOTFOUND);
- }
-
- @Override
- public Choices getBestMatch(String field, String text, Collection collection, String locale) {
- String formName;
- try {
- init();
- if (collection == null) {
- Set choices = new HashSet();
- //workaround search in all authority configured
- for (ChoiceAuthority ca : delegates.values()) {
- Choices tmp = ca.getBestMatch(field, text, null, locale);
- if (tmp.total > 0) {
- Set mySet = new HashSet(Arrays.asList(tmp.values));
- choices.addAll(mySet);
- }
- }
- if (!choices.isEmpty()) {
- Choice[] results = new Choice[choices.size() - 1];
- choices.toArray(results);
- return new Choices(results, 0, choices.size(), Choices.CF_UNCERTAIN, false);
- }
- } else {
- formName = dci.getInputFormNameByCollectionAndField(collection, field);
- return delegates.get(formName).getBestMatch(field, text, collection, locale);
- }
- } catch (DCInputsReaderException e) {
- log.error(e.getMessage(), e);
- }
- return new Choices(Choices.CF_NOTFOUND);
- }
-
- @Override
- public String getLabel(String field, String key, String locale) {
- // TODO we need to manage REALLY the authority
- // WRONG BEHAVIOUR: now in each delegates can exists the same key with
- // different value
- for (ChoiceAuthority delegate : delegates.values()) {
- String label = delegate.getLabel(field, key, locale);
- if (StringUtils.isNotBlank(label)) {
- return label;
- }
- }
- return "UNKNOWN KEY " + key;
- }
-
- @Override
- public boolean isHierarchical() {
- // TODO we need to manage REALLY the authority
- // WRONG BEHAVIOUR: now in each delegates can exists the same key with
- // different value
- for (ChoiceAuthority delegate : delegates.values()) {
- return delegate.isHierarchical();
- }
- return false;
- }
-
- @Override
- public boolean isScrollable() {
- // TODO we need to manage REALLY the authority
- // WRONG BEHAVIOUR: now in each delegates can exists the same key with
- // different value
- for (ChoiceAuthority delegate : delegates.values()) {
- return delegate.isScrollable();
- }
- return false;
- }
-
- @Override
- public boolean hasIdentifier() {
- // TODO we need to manage REALLY the authority
- // WRONG BEHAVIOUR: now in each delegates can exists the same key with
- // different value
- for (ChoiceAuthority delegate : delegates.values()) {
- return delegate.hasIdentifier();
- }
- return false;
- }
-
- public Map getDelegates() {
- return delegates;
- }
-
- public void setDelegates(Map delegates) {
- this.delegates = delegates;
- }
-}
diff --git a/dspace-api/src/main/java/org/dspace/content/authority/MetadataAuthorityServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/authority/MetadataAuthorityServiceImpl.java
index 6a5b17a029..c542c6a89e 100644
--- a/dspace-api/src/main/java/org/dspace/content/authority/MetadataAuthorityServiceImpl.java
+++ b/dspace-api/src/main/java/org/dspace/content/authority/MetadataAuthorityServiceImpl.java
@@ -14,12 +14,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.Logger;
-import org.dspace.app.util.DCInput;
-import org.dspace.app.util.DCInputSet;
-import org.dspace.app.util.DCInputsReader;
-import org.dspace.app.util.DCInputsReaderException;
import org.dspace.content.MetadataField;
import org.dspace.content.authority.service.MetadataAuthorityService;
import org.dspace.content.service.MetadataFieldService;
@@ -144,8 +139,6 @@ public class MetadataAuthorityServiceImpl implements MetadataAuthorityService {
if (dmc >= Choices.CF_UNSET) {
defaultMinConfidence = dmc;
}
-
- autoRegisterAuthorityFromInputReader();
}
}
@@ -205,7 +198,6 @@ public class MetadataAuthorityServiceImpl implements MetadataAuthorityService {
}
}
-
/**
* Give the minimal level of confidence required to consider valid an authority value
* for the given metadata.
@@ -229,35 +221,4 @@ public class MetadataAuthorityServiceImpl implements MetadataAuthorityService {
}
return copy;
}
-
-
- private void autoRegisterAuthorityFromInputReader() {
- try {
- DCInputsReader dcInputsReader = new DCInputsReader();
- for (DCInputSet dcinputSet : dcInputsReader.getAllInputs(Integer.MAX_VALUE, 0)) {
- DCInput[][] dcinputs = dcinputSet.getFields();
- for (DCInput[] dcrows : dcinputs) {
- for (DCInput dcinput : dcrows) {
- if (StringUtils.isNotBlank(dcinput.getPairsType())
- || StringUtils.isNotBlank(dcinput.getVocabulary())) {
- String authorityName = dcinput.getPairsType();
- if (StringUtils.isBlank(authorityName)) {
- authorityName = dcinput.getVocabulary();
- }
- if (!StringUtils.equals(dcinput.getInputType(), "qualdrop_value")) {
- String fieldKey = makeFieldKey(dcinput.getSchema(), dcinput.getElement(),
- dcinput.getQualifier());
- boolean req = ConfigurationManager
- .getBooleanProperty("authority.required." + fieldKey, false);
- controlled.put(fieldKey, true);
- isAuthorityRequired.put(fieldKey, req);
- }
- }
- }
- }
- }
- } catch (DCInputsReaderException e) {
- throw new IllegalStateException(e.getMessage(), e);
- }
- }
}
diff --git a/dspace-api/src/main/java/org/dspace/content/authority/SampleAuthority.java b/dspace-api/src/main/java/org/dspace/content/authority/SampleAuthority.java
index 8197f180af..e6cc9b9d44 100644
--- a/dspace-api/src/main/java/org/dspace/content/authority/SampleAuthority.java
+++ b/dspace-api/src/main/java/org/dspace/content/authority/SampleAuthority.java
@@ -7,13 +7,13 @@
*/
package org.dspace.content.authority;
-import org.dspace.content.Collection;
-
/**
* This is a *very* stupid test fixture for authority control, and also
* serves as a trivial example of an authority plugin implementation.
*/
public class SampleAuthority implements ChoiceAuthority {
+ private String pluginInstanceName;
+
protected static String values[] = {
"sun",
"mon",
@@ -35,7 +35,7 @@ public class SampleAuthority implements ChoiceAuthority {
};
@Override
- public Choices getMatches(String field, String query, Collection collection, int start, int limit, String locale) {
+ public Choices getMatches(String query, int start, int limit, String locale) {
int dflt = -1;
Choice v[] = new Choice[values.length];
for (int i = 0; i < values.length; ++i) {
@@ -48,7 +48,7 @@ public class SampleAuthority implements ChoiceAuthority {
}
@Override
- public Choices getBestMatch(String field, String text, Collection collection, String locale) {
+ public Choices getBestMatch(String text, String locale) {
for (int i = 0; i < values.length; ++i) {
if (text.equalsIgnoreCase(values[i])) {
Choice v[] = new Choice[1];
@@ -60,7 +60,17 @@ public class SampleAuthority implements ChoiceAuthority {
}
@Override
- public String getLabel(String field, String key, String locale) {
+ public String getLabel(String key, String locale) {
return labels[Integer.parseInt(key)];
}
+
+ @Override
+ public String getPluginInstanceName() {
+ return pluginInstanceName;
+ }
+
+ @Override
+ public void setPluginInstanceName(String name) {
+ this.pluginInstanceName = name;
+ }
}
diff --git a/dspace-api/src/main/java/org/dspace/content/authority/SolrAuthority.java b/dspace-api/src/main/java/org/dspace/content/authority/SolrAuthority.java
index 5e913430b7..c93e6db786 100644
--- a/dspace-api/src/main/java/org/dspace/content/authority/SolrAuthority.java
+++ b/dspace-api/src/main/java/org/dspace/content/authority/SolrAuthority.java
@@ -11,6 +11,7 @@ import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.Map.Entry;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.Logger;
@@ -24,8 +25,9 @@ import org.dspace.authority.AuthorityValue;
import org.dspace.authority.SolrAuthorityInterface;
import org.dspace.authority.factory.AuthorityServiceFactory;
import org.dspace.authority.service.AuthorityValueService;
-import org.dspace.content.Collection;
import org.dspace.core.ConfigurationManager;
+import org.dspace.core.NameAwarePlugin;
+import org.dspace.services.ConfigurationService;
import org.dspace.services.factory.DSpaceServicesFactory;
/**
@@ -35,7 +37,14 @@ import org.dspace.services.factory.DSpaceServicesFactory;
* @author Mark Diggory (markd at atmire dot com)
*/
public class SolrAuthority implements ChoiceAuthority {
+ /** the name assigned to the specific instance by the PluginService, @see {@link NameAwarePlugin} **/
+ private String authorityName;
+ /**
+ * the metadata managed by the plugin instance, derived from its authority name
+ * in the form schema_element_qualifier
+ */
+ private String field;
protected SolrAuthorityInterface source =
DSpaceServicesFactory.getInstance().getServiceManager()
.getServiceByName("AuthoritySource", SolrAuthorityInterface.class);
@@ -45,8 +54,9 @@ public class SolrAuthority implements ChoiceAuthority {
protected boolean externalResults = false;
protected final AuthorityValueService authorityValueService = AuthorityServiceFactory.getInstance()
.getAuthorityValueService();
-
- public Choices getMatches(String field, String text, Collection collection, int start, int limit, String locale,
+ protected final ConfigurationService configurationService = DSpaceServicesFactory.getInstance()
+ .getConfigurationService();
+ public Choices getMatches(String text, int start, int limit, String locale,
boolean bestMatch) {
if (limit == 0) {
limit = 10;
@@ -193,13 +203,13 @@ public class SolrAuthority implements ChoiceAuthority {
}
@Override
- public Choices getMatches(String field, String text, Collection collection, int start, int limit, String locale) {
- return getMatches(field, text, collection, start, limit, locale, true);
+ public Choices getMatches(String text, int start, int limit, String locale) {
+ return getMatches(text, start, limit, locale, true);
}
@Override
- public Choices getBestMatch(String field, String text, Collection collection, String locale) {
- Choices matches = getMatches(field, text, collection, 0, 1, locale, false);
+ public Choices getBestMatch(String text, String locale) {
+ Choices matches = getMatches(text, 0, 1, locale, false);
if (matches.values.length != 0 && !matches.values[0].value.equalsIgnoreCase(text)) {
matches = new Choices(false);
}
@@ -207,7 +217,7 @@ public class SolrAuthority implements ChoiceAuthority {
}
@Override
- public String getLabel(String field, String key, String locale) {
+ public String getLabel(String key, String locale) {
try {
if (log.isDebugEnabled()) {
log.debug("requesting label for key " + key + " using locale " + locale);
@@ -276,4 +286,23 @@ public class SolrAuthority implements ChoiceAuthority {
public void addExternalResultsInNextMatches() {
this.externalResults = true;
}
+
+ @Override
+ public void setPluginInstanceName(String name) {
+ authorityName = name;
+ for (Entry conf : configurationService.getProperties().entrySet()) {
+ if (StringUtils.startsWith((String) conf.getKey(), ChoiceAuthorityServiceImpl.CHOICES_PLUGIN_PREFIX)
+ && StringUtils.equals((String) conf.getValue(), authorityName)) {
+ field = ((String) conf.getKey()).substring(ChoiceAuthorityServiceImpl.CHOICES_PLUGIN_PREFIX.length())
+ .replace(".", "_");
+ // exit the look immediately as we have found it
+ break;
+ }
+ }
+ }
+
+ @Override
+ public String getPluginInstanceName() {
+ return authorityName;
+ }
}
diff --git a/dspace-api/src/main/java/org/dspace/content/authority/TestAuthority.java b/dspace-api/src/main/java/org/dspace/content/authority/TestAuthority.java
index a017e8fe28..15c000e978 100644
--- a/dspace-api/src/main/java/org/dspace/content/authority/TestAuthority.java
+++ b/dspace-api/src/main/java/org/dspace/content/authority/TestAuthority.java
@@ -11,7 +11,6 @@ import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
-import org.dspace.content.Collection;
/**
* This is a *very* stupid test fixture for authority control with AuthorityVariantsSupport.
@@ -19,6 +18,7 @@ import org.dspace.content.Collection;
* @author Andrea Bollini (CILEA)
*/
public class TestAuthority implements ChoiceAuthority, AuthorityVariantsSupport {
+ private String pluginInstanceName;
@Override
public List getVariants(String key, String locale) {
@@ -33,8 +33,7 @@ public class TestAuthority implements ChoiceAuthority, AuthorityVariantsSupport
}
@Override
- public Choices getMatches(String field, String text, Collection collection,
- int start, int limit, String locale) {
+ public Choices getMatches(String text, int start, int limit, String locale) {
Choices choices = new Choices(false);
if (StringUtils.isNotBlank(text)) {
@@ -52,8 +51,7 @@ public class TestAuthority implements ChoiceAuthority, AuthorityVariantsSupport
}
@Override
- public Choices getBestMatch(String field, String text, Collection collection,
- String locale) {
+ public Choices getBestMatch(String text, String locale) {
Choices choices = new Choices(false);
if (StringUtils.isNotBlank(text)) {
@@ -70,10 +68,20 @@ public class TestAuthority implements ChoiceAuthority, AuthorityVariantsSupport
}
@Override
- public String getLabel(String field, String key, String locale) {
+ public String getLabel(String key, String locale) {
if (StringUtils.isNotBlank(key)) {
return key.replaceAll("authority", "label");
}
return "Unknown";
}
+
+ @Override
+ public String getPluginInstanceName() {
+ return pluginInstanceName;
+ }
+
+ @Override
+ public void setPluginInstanceName(String name) {
+ this.pluginInstanceName = name;
+ }
}
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 83db9a734e..1cc5075d02 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
@@ -48,10 +48,10 @@ public interface ChoiceAuthorityService {
* @param element element of metadata field
* @param qualifier qualifier of metadata field
* @return the name of the choice authority associated with the specified
- * metadata. Throw IllegalArgumentException if the supplied metadat
+ * metadata. Throw IllegalArgumentException if the supplied metadata
* is not associated with an authority choice
*/
- public String getChoiceAuthorityName(String schema, String element, String qualifier);
+ public String getChoiceAuthorityName(String schema, String element, String qualifier, Collection collection);
/**
* Wrapper that calls getMatches method of the plugin corresponding to
@@ -112,30 +112,33 @@ public interface ChoiceAuthorityService {
* the metadata field defined by schema,element,qualifier.
*
* @param metadataValue metadata value
+ * @param collection Collection owner of Item
* @param locale explicit localization key if available
* @return label
*/
- public String getLabel(MetadataValue metadataValue, String locale);
+ public String getLabel(MetadataValue metadataValue, Collection collection, String locale);
/**
* Wrapper that calls getLabel method of the plugin corresponding to
* the metadata field defined by single field key.
*
* @param fieldKey single string identifying metadata field
+ * @param collection Collection owner of Item
* @param locale explicit localization key if available
* @param authKey authority key
* @return label
*/
- public String getLabel(String fieldKey, String authKey, String locale);
+ public String getLabel(String fieldKey, Collection collection, String authKey, String locale);
/**
* Predicate, is there a Choices configuration of any kind for the
* given metadata field?
*
* @param fieldKey single string identifying metadata field
+ * @param collection Collection owner of Item
* @return true if choices are configured for this field.
*/
- public boolean isChoicesConfigured(String fieldKey);
+ public boolean isChoicesConfigured(String fieldKey, Collection collection);
/**
* Get the presentation keyword (should be "lookup", "select" or "suggest", but this
@@ -160,12 +163,14 @@ public interface ChoiceAuthorityService {
* @param metadataValue metadata value
* @return List of variants
*/
- public List getVariants(MetadataValue metadataValue);
-
- public String getChoiceMetadatabyAuthorityName(String name);
-
- public Choice getChoice(String fieldKey, String authKey, String locale);
+ public List getVariants(MetadataValue metadataValue, Collection collection);
+ /**
+ * Return the ChoiceAuthority instance identified by the specified name
+ *
+ * @param authorityName the ChoiceAuthority instance name
+ * @return the ChoiceAuthority identified by the specified name
+ */
public ChoiceAuthority getChoiceAuthorityByAuthorityName(String authorityName);
/**
@@ -173,4 +178,49 @@ public interface ChoiceAuthorityService {
*/
public void clearCache();
+ /**
+ * Should we store the authority key (if any) for such field key and collection?
+ *
+ * @param fieldKey single string identifying metadata field
+ * @param collection Collection owner of Item or where the item is submitted to
+ * @return true if the configuration allows to store the authority value
+ */
+ public boolean storeAuthority(String fieldKey, Collection collection);
+
+ /**
+ * Wrapper that calls getChoicesByParent method of the plugin.
+ *
+ * @param authorityName authority name
+ * @param parentId parent Id
+ * @param start choice at which to start, 0 is first.
+ * @param limit maximum number of choices to return, 0 for no limit.
+ * @param locale explicit localization key if available, or null
+ * @return a Choices object (never null).
+ * @see org.dspace.content.authority.ChoiceAuthority#getChoicesByParent(java.lang.String, java.lang.String,
+ * int, int, java.lang.String)
+ */
+ public Choices getChoicesByParent(String authorityName, String parentId, int start, int limit, String locale);
+
+ /**
+ * Wrapper that calls getTopChoices method of the plugin.
+ *
+ * @param authorityName authority name
+ * @param start choice at which to start, 0 is first.
+ * @param limit maximum number of choices to return, 0 for no limit.
+ * @param locale explicit localization key if available, or null
+ * @return a Choices object (never null).
+ * @see org.dspace.content.authority.ChoiceAuthority#getTopChoices(java.lang.String, int, int, java.lang.String)
+ */
+ public Choices getTopChoices(String authorityName, int start, int limit, String locale);
+
+ /**
+ * Return the direct parent of an entry identified by its id in an hierarchical
+ * authority.
+ *
+ * @param authorityName authority name
+ * @param vocabularyId child id
+ * @param locale explicit localization key if available, or null
+ * @return the parent Choice object if any
+ */
+ public Choice getParentChoice(String authorityName, String vocabularyId, String locale);
}
diff --git a/dspace-api/src/main/java/org/dspace/content/dao/ItemDAO.java b/dspace-api/src/main/java/org/dspace/content/dao/ItemDAO.java
index 979f42836a..4c391d973b 100644
--- a/dspace-api/src/main/java/org/dspace/content/dao/ItemDAO.java
+++ b/dspace-api/src/main/java/org/dspace/content/dao/ItemDAO.java
@@ -47,6 +47,19 @@ public interface ItemDAO extends DSpaceObjectLegacySupportDAO {
public Iterator findBySubmitter(Context context, EPerson eperson) throws SQLException;
+ /**
+ * Find all the items by a given submitter. The order is
+ * indeterminate. All items are included.
+ *
+ * @param context DSpace context object
+ * @param eperson the submitter
+ * @param retrieveAllItems flag to determine if only archive should be returned
+ * @return an iterator over the items submitted by eperson
+ * @throws SQLException if database error
+ */
+ public Iterator findBySubmitter(Context context, EPerson eperson, boolean retrieveAllItems)
+ throws SQLException;
+
public Iterator findBySubmitter(Context context, EPerson eperson, MetadataField metadataField, int limit)
throws SQLException;
diff --git a/dspace-api/src/main/java/org/dspace/content/dao/ProcessDAO.java b/dspace-api/src/main/java/org/dspace/content/dao/ProcessDAO.java
index f20225a202..4ef26cffcb 100644
--- a/dspace-api/src/main/java/org/dspace/content/dao/ProcessDAO.java
+++ b/dspace-api/src/main/java/org/dspace/content/dao/ProcessDAO.java
@@ -13,6 +13,7 @@ import java.util.List;
import org.dspace.core.Context;
import org.dspace.core.GenericDAO;
import org.dspace.scripts.Process;
+import org.dspace.scripts.ProcessQueryParameterContainer;
/**
* This is the Data Access Object for the {@link Process} object
@@ -54,4 +55,30 @@ public interface ProcessDAO extends GenericDAO {
*/
int countRows(Context context) throws SQLException;
+ /**
+ * Returns a list of all Processes in the database which match the given field requirements. If the
+ * requirements are not null, they will be combined with an AND operation.
+ * @param context The relevant DSpace context
+ * @param processQueryParameterContainer The {@link ProcessQueryParameterContainer} containing all the values
+ * that the returned {@link Process} objects must adhere to
+ * @param limit The limit for the amount of Processes returned
+ * @param offset The offset for the Processes to be returned
+ * @return The list of all Processes which match the metadata requirements
+ * @throws SQLException If something goes wrong
+ */
+ List search(Context context, ProcessQueryParameterContainer processQueryParameterContainer, int limit,
+ int offset) throws SQLException;
+
+ /**
+ * Count all the processes which match the requirements. The requirements are evaluated like the search
+ * method.
+ * @param context The relevant DSpace context
+ * @param processQueryParameterContainer The {@link ProcessQueryParameterContainer} containing all the values
+ * that the returned {@link Process} objects must adhere to
+ * @return The number of results matching the query
+ * @throws SQLException If something goes wrong
+ */
+
+ int countTotalWithParameters(Context context, ProcessQueryParameterContainer processQueryParameterContainer)
+ throws SQLException;
}
diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/ItemDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/ItemDAOImpl.java
index b935812c8c..683a6502c5 100644
--- a/dspace-api/src/main/java/org/dspace/content/dao/impl/ItemDAOImpl.java
+++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/ItemDAOImpl.java
@@ -108,6 +108,17 @@ public class ItemDAOImpl extends AbstractHibernateDSODAO implements ItemDA
return iterate(query);
}
+ @Override
+ public Iterator findBySubmitter(Context context, EPerson eperson, boolean retrieveAllItems)
+ throws SQLException {
+ if (!retrieveAllItems) {
+ return findBySubmitter(context, eperson);
+ }
+ Query query = createQuery(context, "FROM Item WHERE submitter= :submitter");
+ query.setParameter("submitter", eperson);
+ return iterate(query);
+ }
+
@Override
public Iterator findBySubmitter(Context context, EPerson eperson, MetadataField metadataField, int limit)
throws SQLException {
diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/ProcessDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/ProcessDAOImpl.java
index 4c10387d93..5c8083a86b 100644
--- a/dspace-api/src/main/java/org/dspace/content/dao/impl/ProcessDAOImpl.java
+++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/ProcessDAOImpl.java
@@ -8,15 +8,20 @@
package org.dspace.content.dao.impl;
import java.sql.SQLException;
+import java.util.LinkedList;
import java.util.List;
+import java.util.Map;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
+import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
+import org.apache.commons.lang3.StringUtils;
import org.dspace.content.dao.ProcessDAO;
import org.dspace.core.AbstractHibernateDAO;
import org.dspace.core.Context;
import org.dspace.scripts.Process;
+import org.dspace.scripts.ProcessQueryParameterContainer;
import org.dspace.scripts.Process_;
/**
@@ -56,6 +61,7 @@ public class ProcessDAOImpl extends AbstractHibernateDAO implements Pro
CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Process.class);
Root processRoot = criteriaQuery.from(Process.class);
criteriaQuery.select(processRoot);
+ criteriaQuery.orderBy(criteriaBuilder.desc(processRoot.get(Process_.processId)));
return list(context, criteriaQuery, false, Process.class, limit, offset);
}
@@ -71,6 +77,76 @@ public class ProcessDAOImpl extends AbstractHibernateDAO implements Pro
return count(context, criteriaQuery, criteriaBuilder, processRoot);
}
+
+ @Override
+ public List search(Context context, ProcessQueryParameterContainer processQueryParameterContainer,
+ int limit, int offset) throws SQLException {
+ CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context);
+ CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Process.class);
+ Root processRoot = criteriaQuery.from(Process.class);
+ criteriaQuery.select(processRoot);
+
+ handleProcessQueryParameters(processQueryParameterContainer, criteriaBuilder, criteriaQuery, processRoot);
+ return list(context, criteriaQuery, false, Process.class, limit, offset);
+
+ }
+
+ /**
+ * This method will ensure that the params contained in the {@link ProcessQueryParameterContainer} are transferred
+ * to the ProcessRoot and that the correct conditions apply to the query
+ * @param processQueryParameterContainer The object containing the conditions that need to be met
+ * @param criteriaBuilder The criteriaBuilder to be used
+ * @param criteriaQuery The criteriaQuery to be used
+ * @param processRoot The processRoot to be used
+ */
+ private void handleProcessQueryParameters(ProcessQueryParameterContainer processQueryParameterContainer,
+ CriteriaBuilder criteriaBuilder, CriteriaQuery criteriaQuery,
+ Root processRoot) {
+ addProcessQueryParameters(processQueryParameterContainer, criteriaBuilder, criteriaQuery, processRoot);
+ if (StringUtils.equalsIgnoreCase(processQueryParameterContainer.getSortOrder(), "asc")) {
+ criteriaQuery
+ .orderBy(criteriaBuilder.asc(processRoot.get(processQueryParameterContainer.getSortProperty())));
+ } else if (StringUtils.equalsIgnoreCase(processQueryParameterContainer.getSortOrder(), "desc")) {
+ criteriaQuery
+ .orderBy(criteriaBuilder.desc(processRoot.get(processQueryParameterContainer.getSortProperty())));
+ }
+ }
+
+ /**
+ * This method will apply the variables in the {@link ProcessQueryParameterContainer} as criteria for the
+ * {@link Process} objects to the given CriteriaQuery.
+ * They'll need to adhere to these variables in order to be eligible for return
+ * @param processQueryParameterContainer The object containing the variables for the {@link Process}
+ * to adhere to
+ * @param criteriaBuilder The current CriteriaBuilder
+ * @param criteriaQuery The current CriteriaQuery
+ * @param processRoot The processRoot
+ */
+ private void addProcessQueryParameters(ProcessQueryParameterContainer processQueryParameterContainer,
+ CriteriaBuilder criteriaBuilder, CriteriaQuery criteriaQuery,
+ Root processRoot) {
+ List