mirror of
https://github.com/DSpace/DSpace.git
synced 2025-10-07 10:04:21 +00:00
Merge branch 'main' into D4CRIS-952-orcidV3-merge
This commit is contained in:
22
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
22
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -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.
|
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -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.
|
26
.github/disabled-workflows/pull_request_opened.yml
vendored
Normal file
26
.github/disabled-workflows/pull_request_opened.yml
vendored
Normal file
@@ -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
|
9
.github/pull_request_template.md
vendored
9
.github/pull_request_template.md
vendored
@@ -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:_
|
||||
* Related to [REST Contract](https://github.com/DSpace/Rest7Contract) or an open REST Contract PR, if any
|
||||
* Fixes [GitHub issue](https://github.com/DSpace/DSpace/issues), if any
|
||||
|
||||
## Description
|
||||
Short summary of changes (1-2 sentences).
|
||||
@@ -23,5 +22,5 @@ _This checklist provides a reminder of what we are going to look for when review
|
||||
- [ ] My PR passes Checkstyle validation based on the [Code Style Guide](https://wiki.lyrasis.org/display/DSPACE/Code+Style+Guide).
|
||||
- [ ] My PR includes Javadoc for _all new (or modified) public methods and classes_. It also includes Javadoc for large or complex private methods.
|
||||
- [ ] My PR passes all tests and includes new/updated Unit or Integration Tests based on the [Code Testing Guide](https://wiki.lyrasis.org/display/DSPACE/Code+Testing+Guide).
|
||||
- [ ] If my PR includes new, third-party dependencies (in any `pom.xml`), I've made sure their licenses align with the [DSpace BSD License](https://github.com/DSpace/DSpace/blob/master/LICENSE) based on the [Licensing of Contributions](https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines#CodeContributionGuidelines-LicensingofContributions) documentation.
|
||||
- [ ] 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.
|
||||
|
29
.github/workflows/issue_opened.yml
vendored
Normal file
29
.github/workflows/issue_opened.yml
vendored
Normal file
@@ -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
|
25
.github/workflows/label_merge_conflicts.yml
vendored
Normal file
25
.github/workflows/label_merge_conflicts.yml
vendored
Normal file
@@ -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
|
9
.lgtm.yml
Normal file
9
.lgtm.yml
Normal file
@@ -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
|
70
.travis.yml
70
.travis.yml
@@ -1,48 +1,60 @@
|
||||
# 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
|
||||
- sed -i 's/^orcid\.clientid.*/orcid.clientid='$ORCID_CLIENTID'/g' dspace-api/src/test/data/dspaceFolder/config/local.cfg
|
||||
- sed -i 's/^orcid\.clientsecret.*/orcid.clientsecret='$ORCID_CLIENTSECRET'/g' dspace-api/src/test/data/dspaceFolder/config/local.cfg
|
||||
- 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)
|
||||
# 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
|
||||
# -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"
|
||||
# -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)
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
62
README.md
62
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,44 +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`):
|
||||
```
|
||||
mvn clean test -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 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: failIfNoTests=false is required to skip tests in other modules
|
||||
mvn test -Dmaven.test.skip=false -Dtest=[full.package.testClassName] -DfailIfNoTests=false
|
||||
mvn test -DskipUnitTests=false -Dtest=[full.package.testClassName] -DfailIfNoTests=false
|
||||
|
||||
# Run one test method in a specific test class
|
||||
mvn test -Dmaven.test.skip=false -Dtest=[full.package.testClassName]#[testMethodName] -DfailIfNoTests=false
|
||||
mvn test -DskipUnitTests=false -Dtest=[full.package.testClassName]#[testMethodName] -DfailIfNoTests=false
|
||||
```
|
||||
* How to run Integration Tests (requires enabling Unit tests too)
|
||||
* How to run _only_ Integration Tests
|
||||
```
|
||||
mvn verify -Dmaven.test.skip=false -DskipITs=false
|
||||
mvn install -DskipIntegrationTests=false
|
||||
```
|
||||
* How to run a *single* Integration Test (requires enabling Unit tests too)
|
||||
* How to run a *single* Integration Test
|
||||
```
|
||||
# Run all integration tests in a specific test class
|
||||
# NOTE: failIfNoTests=false is required to skip tests in other modules
|
||||
mvn test -Dmaven.test.skip=false -DskipITs=false -Dtest=[full.package.testClassName] -DfailIfNoTests=false
|
||||
mvn install -DskipIntegrationTests=false -Dtest=[full.package.testClassName] -DfailIfNoTests=false
|
||||
|
||||
# Run one test method in a specific test class
|
||||
mvn test -Dmaven.test.skip=false -DskipITs=false -Dtest=[full.package.testClassName]#[testMethodName] -DfailIfNoTests=false
|
||||
mvn install -DskipIntegrationTests=false -Dtest=[full.package.testClassName]#[testMethodName] -DfailIfNoTests=false
|
||||
```
|
||||
* How to run only tests of a specific DSpace module
|
||||
```
|
||||
@@ -130,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/
|
||||
|
@@ -12,7 +12,7 @@
|
||||
<parent>
|
||||
<groupId>org.dspace</groupId>
|
||||
<artifactId>dspace-parent</artifactId>
|
||||
<version>7.0-beta3-SNAPSHOT</version>
|
||||
<version>7.0-beta4-SNAPSHOT</version>
|
||||
<relativePath>..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -127,81 +127,8 @@
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>findbugs</id>
|
||||
<activation>
|
||||
<activeByDefault>false</activeByDefault>
|
||||
<!-- property>
|
||||
<name>maven.test.skip</name>
|
||||
<value>false</value>
|
||||
</property -->
|
||||
</activation>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>findbugs-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
|
||||
<!-- If Unit Testing is enabled, then setup the Unit Test Environment.
|
||||
See also the 'skiptests' profile in Parent POM. -->
|
||||
<profile>
|
||||
<id>test-environment</id>
|
||||
<activation>
|
||||
<activeByDefault>false</activeByDefault>
|
||||
<property>
|
||||
<name>maven.test.skip</name>
|
||||
<value>false</value>
|
||||
</property>
|
||||
</activation>
|
||||
<build>
|
||||
<plugins>
|
||||
<!-- Unit/Integration Testing setup: This plugin unzips the
|
||||
'testEnvironment.zip' file (created by dspace-parent POM), into
|
||||
the 'target/testing/' folder, to essentially create a test
|
||||
install of DSpace, against which Tests can be run. -->
|
||||
<plugin>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<configuration>
|
||||
<outputDirectory>${project.build.directory}/testing</outputDirectory>
|
||||
<artifactItems>
|
||||
<artifactItem>
|
||||
<groupId>org.dspace</groupId>
|
||||
<artifactId>dspace-parent</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<type>zip</type>
|
||||
<classifier>testEnvironment</classifier>
|
||||
</artifactItem>
|
||||
</artifactItems>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>setupTestEnvironment</id>
|
||||
<phase>generate-test-resources</phase>
|
||||
<goals>
|
||||
<goal>unpack</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>setupIntegrationTestEnvironment</id>
|
||||
<phase>pre-integration-test</phase>
|
||||
<goals>
|
||||
<goal>unpack</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<!-- This plugin allows us to run a Groovy script in our Maven POM
|
||||
(see: http://gmaven.codehaus.org/Executing+Groovy+Code )
|
||||
(see: https://groovy.github.io/gmaven/groovy-maven-plugin/execute.html )
|
||||
We are generating a OS-agnostic version (agnostic.build.dir) of
|
||||
the ${project.build.directory} property (full path of target dir).
|
||||
This is needed by the Surefire & Failsafe plugins (see below)
|
||||
@@ -230,7 +157,68 @@
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<!-- Run Unit Testing! This plugin just kicks off the tests (when enabled). -->
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>spotbugs</id>
|
||||
<activation>
|
||||
<activeByDefault>false</activeByDefault>
|
||||
</activation>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>com.github.spotbugs</groupId>
|
||||
<artifactId>spotbugs-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
|
||||
<!-- Setup the Unit Test Environment (when -DskipUnitTests=false) -->
|
||||
<profile>
|
||||
<id>unit-test-environment</id>
|
||||
<activation>
|
||||
<activeByDefault>false</activeByDefault>
|
||||
<property>
|
||||
<name>skipUnitTests</name>
|
||||
<value>false</value>
|
||||
</property>
|
||||
</activation>
|
||||
<build>
|
||||
<plugins>
|
||||
<!-- Unit Testing setup: This plugin unzips the
|
||||
'testEnvironment.zip' file (created by dspace-parent POM), into
|
||||
the 'target/testing/' folder, to essentially create a test
|
||||
install of DSpace, against which Tests can be run. -->
|
||||
<plugin>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<configuration>
|
||||
<outputDirectory>${project.build.directory}/testing</outputDirectory>
|
||||
<artifactItems>
|
||||
<artifactItem>
|
||||
<groupId>org.dspace</groupId>
|
||||
<artifactId>dspace-parent</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<type>zip</type>
|
||||
<classifier>testEnvironment</classifier>
|
||||
</artifactItem>
|
||||
</artifactItems>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>setupUnitTestEnvironment</id>
|
||||
<phase>generate-test-resources</phase>
|
||||
<goals>
|
||||
<goal>unpack</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<!-- Run Unit Testing! This plugin just kicks off the tests. -->
|
||||
<plugin>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
@@ -241,11 +229,56 @@
|
||||
<dspace.dir>${agnostic.build.dir}/testing/dspace/</dspace.dir>
|
||||
<!-- Turn off any DSpace logging -->
|
||||
<dspace.log.init.disable>true</dspace.log.init.disable>
|
||||
<solr.install.dir>${agnostic.build.dir}/testing/dspace/solr/</solr.install.dir>
|
||||
</systemPropertyVariables>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
|
||||
<!-- Run Integration Testing! This plugin just kicks off the tests (when enabled). -->
|
||||
<!-- Setup the Integration Test Environment (when -DskipIntegrationTests=false) -->
|
||||
<profile>
|
||||
<id>integration-test-environment</id>
|
||||
<activation>
|
||||
<activeByDefault>false</activeByDefault>
|
||||
<property>
|
||||
<name>skipIntegrationTests</name>
|
||||
<value>false</value>
|
||||
</property>
|
||||
</activation>
|
||||
<build>
|
||||
<plugins>
|
||||
<!-- Integration Testing setup: This plugin unzips the
|
||||
'testEnvironment.zip' file (created by dspace-parent POM), into
|
||||
the 'target/testing/' folder, to essentially create a test
|
||||
install of DSpace, against which Tests can be run. -->
|
||||
<plugin>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<configuration>
|
||||
<outputDirectory>${project.build.directory}/testing</outputDirectory>
|
||||
<artifactItems>
|
||||
<artifactItem>
|
||||
<groupId>org.dspace</groupId>
|
||||
<artifactId>dspace-parent</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<type>zip</type>
|
||||
<classifier>testEnvironment</classifier>
|
||||
</artifactItem>
|
||||
</artifactItems>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>setupIntegrationTestEnvironment</id>
|
||||
<phase>pre-integration-test</phase>
|
||||
<goals>
|
||||
<goal>unpack</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<!-- Run Integration Testing! This plugin just kicks off the tests. -->
|
||||
<plugin>
|
||||
<artifactId>maven-failsafe-plugin</artifactId>
|
||||
<configuration>
|
||||
@@ -255,12 +288,12 @@
|
||||
<dspace.dir>${agnostic.build.dir}/testing/dspace/</dspace.dir>
|
||||
<!-- Turn off any DSpace logging -->
|
||||
<dspace.log.init.disable>true</dspace.log.init.disable>
|
||||
<solr.install.dir>${agnostic.build.dir}/testing/dspace/solr/</solr.install.dir>
|
||||
</systemPropertyVariables>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</profile>
|
||||
</profiles>
|
||||
|
||||
@@ -291,9 +324,20 @@
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.dspace</groupId>
|
||||
<groupId>net.handle</groupId>
|
||||
<artifactId>handle</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.cnri</groupId>
|
||||
<artifactId>cnri-servlet-container</artifactId>
|
||||
<exclusions>
|
||||
<!-- Newer versions provided in our parent POM -->
|
||||
<exclusion>
|
||||
<groupId>org.ow2.asm</groupId>
|
||||
<artifactId>asm-commons</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<!-- Jetty is needed to run Handle Server -->
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
@@ -312,6 +356,18 @@
|
||||
<artifactId>apache-jena-libs</artifactId>
|
||||
<type>pom</type>
|
||||
</dependency>
|
||||
<!-- Required to support PubMed API call in "PubmedImportMetadataSourceServiceImpl.GetRecord" -->
|
||||
<!-- Makes runtime operations in Jersey Dependency Injection -->
|
||||
<dependency>
|
||||
<groupId>org.glassfish.jersey.inject</groupId>
|
||||
<artifactId>jersey-hk2</artifactId>
|
||||
<version>${jersey.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>commons-cli</groupId>
|
||||
<artifactId>commons-cli</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-codec</groupId>
|
||||
<artifactId>commons-codec</artifactId>
|
||||
@@ -468,16 +524,164 @@
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.solr</groupId>
|
||||
<artifactId>solr-cell</artifactId>
|
||||
<artifactId>solr-solrj</artifactId>
|
||||
<version>${solr.client.version}</version>
|
||||
</dependency>
|
||||
<!-- Solr Core is needed for Integration Tests (to run a MockSolrServer) -->
|
||||
<!-- The following Solr / Lucene dependencies also support integration tests -->
|
||||
<dependency>
|
||||
<groupId>org.apache.solr</groupId>
|
||||
<artifactId>solr-core</artifactId>
|
||||
<scope>test</scope>
|
||||
<version>${solr.client.version}</version>
|
||||
<exclusions>
|
||||
<!-- Newer version provided in our parent POM -->
|
||||
<exclusion>
|
||||
<groupId>commons-cli</groupId>
|
||||
<artifactId>commons-cli</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-continuation</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-deploy</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-http</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-io</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-jmx</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-rewrite</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-security</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-server</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-servlet</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-servlets</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-util</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-webapp</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-xml</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.solr</groupId>
|
||||
<artifactId>solr-cell</artifactId>
|
||||
<exclusions>
|
||||
<!-- Newer versions provided in our parent POM -->
|
||||
<exclusion>
|
||||
<groupId>commons-cli</groupId>
|
||||
<artifactId>commons-cli</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.ow2.asm</groupId>
|
||||
<artifactId>asm-commons</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcpkix-jdk15on</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk15on</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-xml</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-http</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-servlet</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-webapp</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-util</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-deploy</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-continuation</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-servlets</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-io</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-security</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.lucene</groupId>
|
||||
<artifactId>lucene-core</artifactId>
|
||||
</dependency>
|
||||
<!-- Reminder: Keep icu4j (in Parent POM) synced with version used by lucene-analyzers-icu below,
|
||||
otherwise ICUFoldingFilterFactory may throw errors in tests. -->
|
||||
<dependency>
|
||||
<groupId>org.apache.lucene</groupId>
|
||||
<artifactId>lucene-analyzers-icu</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.lucene</groupId>
|
||||
<artifactId>lucene-analyzers-smartcn</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.lucene</groupId>
|
||||
<artifactId>lucene-analyzers-stempel</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.xmlbeans</groupId>
|
||||
<artifactId>xmlbeans</artifactId>
|
||||
<version>2.6.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.maxmind.geoip2</groupId>
|
||||
@@ -559,6 +763,7 @@
|
||||
<groupId>com.google.oauth-client</groupId>
|
||||
<artifactId>google-oauth-client</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- FindBugs -->
|
||||
<dependency>
|
||||
<groupId>com.google.code.findbugs</groupId>
|
||||
@@ -568,6 +773,7 @@
|
||||
<groupId>com.google.code.findbugs</groupId>
|
||||
<artifactId>annotations</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>joda-time</groupId>
|
||||
<artifactId>joda-time</artifactId>
|
||||
@@ -658,7 +864,7 @@
|
||||
|
||||
<dependency>
|
||||
<groupId>org.xmlunit</groupId>
|
||||
<artifactId>xmlunit-matchers</artifactId>
|
||||
<artifactId>xmlunit-core</artifactId>
|
||||
<version>2.6.3</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
@@ -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: ");
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -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<Item> toExport;
|
||||
public class MetadataExport extends DSpaceRunnable<MetadataExportScriptConfiguration> {
|
||||
|
||||
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();
|
||||
|
||||
@Override
|
||||
public void internalRun() throws Exception {
|
||||
|
||||
if (help) {
|
||||
logHelpInfo();
|
||||
printHelp();
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<Item> 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();
|
||||
|
||||
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<Item> buildFromCommunity(Context context, Community community, int indent)
|
||||
throws SQLException {
|
||||
// Add all the collections
|
||||
List<Collection> collections = community.getCollections();
|
||||
Iterator<Item> result = null;
|
||||
for (Collection collection : collections) {
|
||||
for (int i = 0; i < indent; i++) {
|
||||
System.out.print(" ");
|
||||
protected void logHelpInfo() {
|
||||
handler.logInfo("\nfull export: metadata-export");
|
||||
handler.logInfo("partial export: metadata-export -i handle");
|
||||
}
|
||||
|
||||
Iterator<Item> items = itemService.findByCollection(context, collection);
|
||||
result = addItemsToResult(result, items);
|
||||
|
||||
}
|
||||
// Add all the sub-communities
|
||||
List<Community> communities = community.getSubcommunities();
|
||||
for (Community subCommunity : communities) {
|
||||
for (int i = 0; i < indent; i++) {
|
||||
System.out.print(" ");
|
||||
}
|
||||
Iterator<Item> items = buildFromCommunity(context, subCommunity, indent + 1);
|
||||
result = addItemsToResult(result, items);
|
||||
@Override
|
||||
public MetadataExportScriptConfiguration getScriptConfiguration() {
|
||||
return new DSpace().getServiceManager().getServiceByName("metadata-export",
|
||||
MetadataExportScriptConfiguration.class);
|
||||
}
|
||||
|
||||
return result;
|
||||
@Override
|
||||
public void setup() throws ParseException {
|
||||
|
||||
if (commandLine.hasOption('h')) {
|
||||
help = true;
|
||||
return;
|
||||
}
|
||||
|
||||
private Iterator<Item> addItemsToResult(Iterator<Item> result, Iterator<Item> items) {
|
||||
if (result == null) {
|
||||
result = items;
|
||||
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 {
|
||||
DSpaceObject dso = null;
|
||||
if (StringUtils.isNotBlank(handle)) {
|
||||
dso = HandleServiceFactory.getInstance().getHandleService().resolveToObject(context, handle);
|
||||
} else {
|
||||
result = Iterators.concat(result, items);
|
||||
dso = ContentServiceFactory.getInstance().getSiteService().findSite(context);
|
||||
}
|
||||
|
||||
return result;
|
||||
if (dso == null) {
|
||||
throw new ParseException("A handle got given that wasn't able to be parsed to a DSpaceObject");
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the export
|
||||
*
|
||||
* @return the exported CSV lines
|
||||
*/
|
||||
public DSpaceCSV export() {
|
||||
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);
|
||||
return dso.getID().toString() + ".csv";
|
||||
} catch (SQLException e) {
|
||||
handler.handleException("Something went wrong trying to retrieve DSO for handle: " + handle, e);
|
||||
}
|
||||
|
||||
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<Item> 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> 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);
|
||||
} else {
|
||||
System.err.println("Error identifying '" + handle + "'");
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
|
@@ -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");
|
||||
}
|
||||
}
|
@@ -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<MetadataExportCli> {
|
||||
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
@@ -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<T extends MetadataExport> extends ScriptConfiguration<T> {
|
||||
|
||||
@Autowired
|
||||
private AuthorizeService authorizeService;
|
||||
|
||||
private Class<T> dspaceRunnableClass;
|
||||
|
||||
@Override
|
||||
public Class<T> getDspaceRunnableClass() {
|
||||
return dspaceRunnableClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic setter for the dspaceRunnableClass
|
||||
* @param dspaceRunnableClass The dspaceRunnableClass to be set on this MetadataExportScriptConfiguration
|
||||
*/
|
||||
@Override
|
||||
public void setDspaceRunnableClass(Class<T> 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;
|
||||
}
|
||||
|
||||
}
|
@@ -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<MetadataImportScriptConfiguration> {
|
||||
|
||||
/**
|
||||
* The DSpaceCSV object we're processing
|
||||
@@ -95,10 +89,6 @@ public class MetadataImport {
|
||||
*/
|
||||
protected static Set<String> 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<InputStream> 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<BulkEditChange> 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,15 +340,15 @@ public class MetadataImport {
|
||||
* @return An array of BulkEditChange elements representing the items that have changed
|
||||
* @throws MetadataImportException if something goes wrong
|
||||
*/
|
||||
public List<BulkEditChange> runImport(boolean change,
|
||||
public List<BulkEditChange> 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<BulkEditChange> changes = new ArrayList<BulkEditChange>();
|
||||
|
||||
// Make the changes
|
||||
try {
|
||||
Context.Mode originalMode = c.getCurrentMode();
|
||||
c.setMode(Context.Mode.BATCH_EDIT);
|
||||
|
||||
@@ -212,7 +357,7 @@ public class MetadataImport {
|
||||
for (DSpaceCSVLine line : toImport) {
|
||||
// Resolve target references to other items
|
||||
populateRefAndRowMap(line, line.getID());
|
||||
line = resolveEntityRefs(line);
|
||||
line = resolveEntityRefs(c, line);
|
||||
// Get the DSpace item to compare with
|
||||
UUID id = line.getID();
|
||||
|
||||
@@ -244,7 +389,7 @@ public class MetadataImport {
|
||||
throw new MetadataImportException("Missing collection from item " + item.getHandle());
|
||||
}
|
||||
List<Collection> actualCollections = item.getCollections();
|
||||
compare(item, collections, actualCollections, whatHasChanged, change);
|
||||
compare(c, item, collections, actualCollections, whatHasChanged, change);
|
||||
}
|
||||
|
||||
// Iterate through each metadata element in the csv line
|
||||
@@ -263,7 +408,7 @@ public class MetadataImport {
|
||||
}
|
||||
}
|
||||
// Compare
|
||||
compareAndUpdate(item, fromCSV, change, md, whatHasChanged, line);
|
||||
compareAndUpdate(c, item, fromCSV, change, md, whatHasChanged, line);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -339,7 +484,7 @@ public class MetadataImport {
|
||||
}
|
||||
|
||||
// Add all the values from the CSV line
|
||||
add(fromCSV, md, whatHasChanged);
|
||||
add(c, fromCSV, md, whatHasChanged);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -461,15 +606,11 @@ public class MetadataImport {
|
||||
}
|
||||
|
||||
c.setMode(originalMode);
|
||||
} catch (MetadataImportException mie) {
|
||||
throw mie;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
|
||||
// Return the changes
|
||||
if (!change) {
|
||||
validateExpressedRelations();
|
||||
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
|
||||
@@ -754,7 +895,8 @@ public class MetadataImport {
|
||||
// Get the correct RelationshipType based on typeName
|
||||
List<RelationshipType> 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" +
|
||||
@@ -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<String> collections,
|
||||
List<Collection> 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<BulkEditChange> changes, boolean changed) {
|
||||
private int displayChanges(List<BulkEditChange> 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<String>();
|
||||
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<BulkEditChange> 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()) {
|
||||
@@ -1630,7 +1569,8 @@ public class MetadataImport {
|
||||
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 " +
|
||||
+ "\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,7 +1645,8 @@ public class MetadataImport {
|
||||
if (itemService.find(c, UUID.fromString(targetUUID)) != null) {
|
||||
targetItem = itemService.find(c, UUID.fromString(targetUUID));
|
||||
List<MetadataValue> 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();
|
||||
@@ -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,12 +1690,13 @@ public class MetadataImport {
|
||||
if (itemService.find(c, UUID.fromString(targetUUID)) != null) {
|
||||
originItem = itemService.find(c, UUID.fromString(originRefererUUID));
|
||||
List<MetadataValue> 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: "
|
||||
@@ -1791,12 +1735,14 @@ 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)
|
||||
private void validateTypesByTypeByTypeName(Context c,
|
||||
String targetType, String originType, String typeName, String originRow)
|
||||
throws MetadataImportException {
|
||||
try {
|
||||
RelationshipType foundRelationshipType = null;
|
||||
List<RelationshipType> 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) {
|
||||
|
@@ -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!");
|
||||
}
|
||||
}
|
||||
}
|
@@ -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<MetadataImportCLI> {
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
@@ -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<T extends MetadataImport> extends ScriptConfiguration<T> {
|
||||
|
||||
@Autowired
|
||||
private AuthorizeService authorizeService;
|
||||
|
||||
private Class<T> dspaceRunnableClass;
|
||||
|
||||
@Override
|
||||
public Class<T> getDspaceRunnableClass() {
|
||||
return dspaceRunnableClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic setter for the dspaceRunnableClass
|
||||
* @param dspaceRunnableClass The dspaceRunnableClass to be set on this MetadataImportScriptConfiguration
|
||||
*/
|
||||
@Override
|
||||
public void setDspaceRunnableClass(Class<T> 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;
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
|
@@ -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);
|
||||
@@ -111,9 +114,14 @@ public class ScriptLauncher {
|
||||
*/
|
||||
public static int handleScript(String[] args, Document commandConfigs,
|
||||
DSpaceRunnableHandler dSpaceRunnableHandler,
|
||||
DSpaceKernelImpl kernelImpl) {
|
||||
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 {
|
||||
@@ -132,7 +140,7 @@ public class ScriptLauncher {
|
||||
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) {
|
||||
|
@@ -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);
|
||||
|
@@ -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<Community> 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<Collection> 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) {
|
||||
|
@@ -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
|
||||
|
@@ -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;
|
||||
@@ -26,9 +29,12 @@ 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;
|
||||
@@ -41,6 +47,7 @@ import org.dspace.xmlworkflow.storedcomponents.service.CollectionRoleService;
|
||||
*/
|
||||
public class AuthorizeUtil {
|
||||
|
||||
private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(AuthorizeUtil.class);
|
||||
/**
|
||||
* Default constructor
|
||||
*/
|
||||
@@ -590,8 +597,11 @@ public class AuthorizeUtil {
|
||||
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;
|
||||
@@ -601,4 +611,82 @@ public class AuthorizeUtil {
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
|
@@ -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,12 +109,24 @@ 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];
|
||||
// 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<String> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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++) {
|
||||
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
|
||||
|
@@ -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<ResourcePolicy> 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<ResourcePolicy> policies = resourcePolicyService.find(c, e,
|
||||
groupService.allMemberGroups(c, e),
|
||||
|
@@ -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
|
||||
///////////////////////////////////////////////
|
||||
|
@@ -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.");
|
||||
}
|
||||
|
||||
|
@@ -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<Collection> 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<Collection> 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: "
|
||||
@@ -907,4 +918,77 @@ public class CollectionServiceImpl extends DSpaceObjectServiceImpl<Collection> i
|
||||
return role;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Collection> findCollectionsWithSubmit(String q, Context context, Community community,
|
||||
int offset, int limit) throws SQLException, SearchServiceException {
|
||||
|
||||
List<Collection> collections = new ArrayList<Collection>();
|
||||
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<Group> 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;
|
||||
}
|
||||
}
|
||||
|
@@ -207,8 +207,8 @@ public abstract class DSpaceObjectServiceImpl<T extends DSpaceObject> implements
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addMetadata(Context context, T dso, String schema, String element, String qualifier, String lang,
|
||||
List<String> values) throws SQLException {
|
||||
public List<MetadataValue> addMetadata(Context context, T dso, String schema, String element, String qualifier,
|
||||
String lang, List<String> 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<T extends DSpaceObject> 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<String> values, List<String> authorities, List<Integer> confidences)
|
||||
public List<MetadataValue> addMetadata(Context context, T dso, String schema, String element, String qualifier,
|
||||
String lang, List<String> values, List<String> authorities, List<Integer> 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<T extends DSpaceObject> 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<String> values,
|
||||
List<String> authorities, List<Integer> confidences)
|
||||
public List<MetadataValue> addMetadata(Context context, T dso, MetadataField metadataField, String lang,
|
||||
List<String> values, List<String> authorities, List<Integer> confidences)
|
||||
throws SQLException {
|
||||
boolean authorityControlled = metadataAuthorityService.isAuthorityControlled(metadataField);
|
||||
boolean authorityRequired = metadataAuthorityService.isAuthorityRequired(metadataField);
|
||||
List<MetadataValue> 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<T extends DSpaceObject> 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<T extends DSpaceObject> 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<String> values)
|
||||
public List<MetadataValue> addMetadata(Context context, T dso, MetadataField metadataField, String language,
|
||||
List<String> values)
|
||||
throws SQLException {
|
||||
if (metadataField != null) {
|
||||
String fieldKey = metadataAuthorityService
|
||||
@@ -343,18 +347,19 @@ public abstract class DSpaceObjectServiceImpl<T extends DSpaceObject> 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
|
||||
@@ -664,18 +669,20 @@ public abstract class DSpaceObjectServiceImpl<T extends DSpaceObject> implements
|
||||
|
||||
List<MetadataValue> 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,
|
||||
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) {
|
||||
@@ -701,8 +708,6 @@ public abstract class DSpaceObjectServiceImpl<T extends DSpaceObject> 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<T extends DSpaceObject> 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<MetadataValue> 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
|
||||
|
@@ -1372,6 +1372,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.
|
||||
|
@@ -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<Item> 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> 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<Item> 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<Item> buildFromCommunity(Context context, Community community)
|
||||
throws SQLException {
|
||||
// Add all the collections
|
||||
List<Collection> collections = community.getCollections();
|
||||
Iterator<Item> result = Collections.<Item>emptyIterator();
|
||||
for (Collection collection : collections) {
|
||||
Iterator<Item> items = itemService.findByCollection(context, collection);
|
||||
result = addItemsToResult(result, items);
|
||||
|
||||
}
|
||||
// Add all the sub-communities
|
||||
List<Community> communities = community.getSubcommunities();
|
||||
for (Community subCommunity : communities) {
|
||||
Iterator<Item> items = buildFromCommunity(context, subCommunity);
|
||||
result = addItemsToResult(result, items);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private Iterator<Item> addItemsToResult(Iterator<Item> result, Iterator<Item> items) {
|
||||
if (result == null) {
|
||||
result = items;
|
||||
} else {
|
||||
result = Iterators.concat(result, items);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
@@ -168,11 +168,11 @@ public class MetadataField implements ReloadableEntity<Integer> {
|
||||
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())) {
|
||||
|
@@ -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)))));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -67,11 +67,11 @@ public class MetadataSchema implements ReloadableEntity<Integer> {
|
||||
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)) {
|
||||
|
@@ -239,17 +239,17 @@ public class MetadataValue implements ReloadableEntity<Integer> {
|
||||
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;
|
||||
|
@@ -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()));
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
|
@@ -265,7 +265,12 @@ public class WorkspaceItemServiceImpl implements WorkspaceItemService {
|
||||
|
||||
// Need to delete the workspaceitem row first since it refers
|
||||
// to item ID
|
||||
try {
|
||||
workspaceItem.getSupervisorGroups().clear();
|
||||
} catch (Exception e) {
|
||||
log.error("failed to clear supervisor group", e);
|
||||
}
|
||||
|
||||
workspaceItemDAO.delete(context, workspaceItem);
|
||||
|
||||
}
|
||||
|
@@ -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<String, String> extras = new HashMap<String, String>();
|
||||
|
||||
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<String, String> 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;
|
||||
}
|
||||
}
|
||||
|
@@ -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<String, String> getExtra(String key, String locale) {
|
||||
return new HashMap<String, String>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true for hierarchical authorities
|
||||
*
|
||||
* @return <code>true</code> if hierarchical, default <code>false</code>
|
||||
*/
|
||||
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 <code>true</code> if scrollable, default <code>false</code>
|
||||
*/
|
||||
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 <code>0</code> 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 <code>true</code> if the authority provided in any choice of this
|
||||
* authority should be stored in the metadata value
|
||||
*/
|
||||
default public boolean storeAuthorityInMetadata() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@@ -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<String, ChoiceAuthority> controller = new HashMap<String, ChoiceAuthority>();
|
||||
|
||||
// map of field key, form definition to authority plugin
|
||||
protected Map<String, Map<String, ChoiceAuthority>> controllerFormDefinitions =
|
||||
new HashMap<String, Map<String, ChoiceAuthority>>();
|
||||
|
||||
// map of field key to presentation type
|
||||
protected Map<String, String> presentation = new HashMap<String, String>();
|
||||
|
||||
// map of field key to closed value
|
||||
protected Map<String, Boolean> closed = new HashMap<String, Boolean>();
|
||||
|
||||
// map of authority name to field key
|
||||
protected Map<String, String> authorities = new HashMap<String, String>();
|
||||
// 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<String, List<String>> authorities = new HashMap<String, List<String>>();
|
||||
|
||||
// map of authority name to form definition and field keys
|
||||
protected Map<String, Map<String, List<String>>> authoritiesFormDefinitions =
|
||||
new HashMap<String, Map<String, List<String>>>();
|
||||
|
||||
// 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<String> getChoiceAuthoritiesNames() {
|
||||
if (authorities.keySet().isEmpty()) {
|
||||
loadChoiceAuthorityConfigurations();
|
||||
init();
|
||||
Set<String> authoritiesNames = new HashSet<String>();
|
||||
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<String> getVariants(MetadataValue metadataValue) {
|
||||
ChoiceAuthority ma = getChoiceAuthorityMap().get(metadataValue.getMetadataField().toString());
|
||||
public List<String> 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<String, List<String>> 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<String, ChoiceAuthority> 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<String, Map<String, List<String>>> authority2defs2md :
|
||||
authoritiesFormDefinitions.entrySet()) {
|
||||
List<String> 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<String, ChoiceAuthority> 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<String> 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<String> fkeys;
|
||||
if (authorities.containsKey(authorityName)) {
|
||||
fkeys = authorities.get(authorityName);
|
||||
} else {
|
||||
fkeys = new ArrayList<String>();
|
||||
}
|
||||
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<SubmissionConfig> submissionConfigs = itemSubmissionConfigReader
|
||||
.getAllSubmissionConfigs(Integer.MAX_VALUE, 0);
|
||||
DCInputsReader dcInputsReader = new DCInputsReader();
|
||||
for (DCInputSet dcinputSet : dcInputsReader.getAllInputs(Integer.MAX_VALUE, 0)) {
|
||||
|
||||
// loop over all the defined item submission configuration
|
||||
for (SubmissionConfig subCfg : submissionConfigs) {
|
||||
String submissionName = subCfg.getSubmissionName();
|
||||
List<DCInputSet> 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.isNotBlank(dcinput.getVocabulary())) {
|
||||
String authorityName = dcinput.getPairsType();
|
||||
if (StringUtils.isBlank(authorityName)) {
|
||||
&& !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<String, List<String>> submissionDefinitionNames2fieldKeys;
|
||||
if (authoritiesFormDefinitions.containsKey(authorityName)) {
|
||||
submissionDefinitionNames2fieldKeys = authoritiesFormDefinitions.get(authorityName);
|
||||
} else {
|
||||
submissionDefinitionNames2fieldKeys = new HashMap<String, List<String>>();
|
||||
}
|
||||
|
||||
List<String> fields;
|
||||
if (submissionDefinitionNames2fieldKeys.containsKey(submissionName)) {
|
||||
fields = submissionDefinitionNames2fieldKeys.get(submissionName);
|
||||
} else {
|
||||
fields = new ArrayList<String>();
|
||||
}
|
||||
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<String, ChoiceAuthority> definition2authority;
|
||||
if (controllerFormDefinitions.containsKey(fieldKey)) {
|
||||
definition2authority = controllerFormDefinitions.get(fieldKey);
|
||||
} else {
|
||||
definition2authority = new HashMap<String, ChoiceAuthority>();
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@@ -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<String, String[]> 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<String, String[]> labels = null;
|
||||
|
||||
/**
|
||||
* The map of the input form reader associated to use for a specific java locale
|
||||
*/
|
||||
private static Map<Locale, DCInputsReader> 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<String> names = new HashSet<String>();
|
||||
if (pluginNames == null) {
|
||||
try {
|
||||
if (dci == null) {
|
||||
dci = new DCInputsReader();
|
||||
dcis = new HashMap<Locale, DCInputsReader>();
|
||||
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<String> names = new ArrayList<String>();
|
||||
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<String, String[]>();
|
||||
labels = new HashMap<String, String[]>();
|
||||
String pname = this.getPluginInstanceName();
|
||||
for (Locale l : dcis.keySet()) {
|
||||
DCInputsReader dci = dcis.get(l);
|
||||
List<String> pairs = dci.getPairs(pname);
|
||||
if (pairs != null) {
|
||||
values = new String[pairs.size() / 2];
|
||||
labels = new String[pairs.size() / 2];
|
||||
String[] valuesLocale = new String[pairs.size() / 2];
|
||||
String[]labelsLocale = 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);
|
||||
labelsLocale[i / 2] = pairs.get(i);
|
||||
valuesLocale[i / 2] = pairs.get(i + 1);
|
||||
}
|
||||
log.debug("Found pairs for name=" + pname);
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@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)) {
|
||||
int found = 0;
|
||||
List<Choice> v = new ArrayList<Choice>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
return new Choices(v, 0, v.length, Choices.CF_AMBIGUOUS, false, dflt);
|
||||
found++;
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@@ -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<Choice> choices = new ArrayList<Choice>();
|
||||
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);
|
||||
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("'", "'"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLabel(String field, String key, String locale) {
|
||||
init();
|
||||
String xpathExpression = String.format(idTemplate, key);
|
||||
XPath xpath = XPathFactory.newInstance().newXPath();
|
||||
List<Choice> choices = new ArrayList<Choice>();
|
||||
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);
|
||||
String xpathExpression = rootTemplate;
|
||||
return getChoicesByXpath(xpathExpression, start, limit);
|
||||
}
|
||||
|
||||
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]);
|
||||
@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.warn(e.getMessage(), e);
|
||||
}
|
||||
log.error(e.getMessage(), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void readNode(String[] authorities, String[] values, String[] labels, String[] parent, String[] notes,
|
||||
int i, Node node) {
|
||||
@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<Choice> getChoicesFromNodeList(NodeList results, int start, int limit) {
|
||||
List<Choice> choices = new ArrayList<Choice>();
|
||||
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<String, String> addOtherInformation(String parentCurr, String noteCurr,
|
||||
List<String> childrenCurr, String authorityCurr) {
|
||||
Map<String, String> extras = new HashMap<String, String>();
|
||||
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();
|
||||
return node.getAttributes().getNamedItem("label").getNodeValue();
|
||||
}
|
||||
if (this.storeHierarchy) {
|
||||
values[i] = hierarchy;
|
||||
} else {
|
||||
values[i] = 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<String> getChildren(Node node) {
|
||||
List<String> children = new ArrayList<String>();
|
||||
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<Choice> choices = new ArrayList<Choice>();
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
}
|
@@ -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<String, ChoiceAuthority> delegates = new HashMap<String, ChoiceAuthority>();
|
||||
|
||||
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<Choice> choices = new HashSet<Choice>();
|
||||
//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<Choice> mySet = new HashSet<Choice>(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<Choice> choices = new HashSet<Choice>();
|
||||
//workaround search in all authority configured
|
||||
for (ChoiceAuthority ca : delegates.values()) {
|
||||
Choices tmp = ca.getBestMatch(field, text, null, locale);
|
||||
if (tmp.total > 0) {
|
||||
Set<Choice> mySet = new HashSet<Choice>(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<String, ChoiceAuthority> getDelegates() {
|
||||
return delegates;
|
||||
}
|
||||
|
||||
public void setDelegates(Map<String, ChoiceAuthority> delegates) {
|
||||
this.delegates = delegates;
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
@@ -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<String> 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;
|
||||
}
|
||||
}
|
||||
|
@@ -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<String> getVariants(MetadataValue metadataValue);
|
||||
|
||||
public String getChoiceMetadatabyAuthorityName(String name);
|
||||
|
||||
public Choice getChoice(String fieldKey, String authKey, String locale);
|
||||
public List<String> 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);
|
||||
}
|
||||
|
@@ -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<Process> {
|
||||
*/
|
||||
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<Process> 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;
|
||||
}
|
||||
|
@@ -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_;
|
||||
|
||||
/**
|
||||
@@ -71,6 +76,76 @@ public class ProcessDAOImpl extends AbstractHibernateDAO<Process> implements Pro
|
||||
return count(context, criteriaQuery, criteriaBuilder, processRoot);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Process> search(Context context, ProcessQueryParameterContainer processQueryParameterContainer,
|
||||
int limit, int offset) throws SQLException {
|
||||
CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context);
|
||||
CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Process.class);
|
||||
Root<Process> 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<Process> 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<Process> processRoot) {
|
||||
List<Predicate> andPredicates = new LinkedList<>();
|
||||
|
||||
for (Map.Entry<String, Object> entry : processQueryParameterContainer.getQueryParameterMap().entrySet()) {
|
||||
andPredicates.add(criteriaBuilder.equal(processRoot.get(entry.getKey()), entry.getValue()));
|
||||
}
|
||||
criteriaQuery.where(criteriaBuilder.and(andPredicates.toArray(new Predicate[]{})));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int countTotalWithParameters(Context context, ProcessQueryParameterContainer processQueryParameterContainer)
|
||||
throws SQLException {
|
||||
|
||||
CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context);
|
||||
CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Process.class);
|
||||
Root<Process> processRoot = criteriaQuery.from(Process.class);
|
||||
criteriaQuery.select(processRoot);
|
||||
|
||||
addProcessQueryParameters(processQueryParameterContainer, criteriaBuilder, criteriaQuery, processRoot);
|
||||
return count(context, criteriaQuery, criteriaBuilder, processRoot);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@@ -272,13 +272,17 @@ public class METSManifest {
|
||||
// Set validation feature
|
||||
if (validate) {
|
||||
builder.setFeature("http://apache.org/xml/features/validation/schema", true);
|
||||
}
|
||||
|
||||
// Tell the parser where local copies of schemas are, to speed up
|
||||
// validation. Local XSDs are identified in the configuration file.
|
||||
// validation & avoid XXE attacks from remote schemas. Local XSDs are identified in the configuration file.
|
||||
if (localSchemas.length() > 0) {
|
||||
builder.setProperty("http://apache.org/xml/properties/schema/external-schemaLocation", localSchemas);
|
||||
}
|
||||
} else {
|
||||
// disallow DTD parsing to ensure no XXE attacks can occur.
|
||||
// See https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html
|
||||
builder.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
|
||||
}
|
||||
|
||||
// Parse the METS file
|
||||
Document metsDocument;
|
||||
|
@@ -20,8 +20,10 @@ import org.dspace.content.Collection;
|
||||
import org.dspace.content.Community;
|
||||
import org.dspace.content.Item;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.discovery.SearchServiceException;
|
||||
import org.dspace.eperson.Group;
|
||||
|
||||
|
||||
/**
|
||||
* Service interface class for the Collection object.
|
||||
* The implementation of this class is responsible for all business logic calls for the Collection object and is
|
||||
@@ -354,4 +356,42 @@ public interface CollectionService
|
||||
*/
|
||||
Group createDefaultReadGroup(Context context, Collection collection, String typeOfGroupString, int defaultRead)
|
||||
throws SQLException, AuthorizeException;
|
||||
|
||||
/**
|
||||
* Returns Collections for which the current user has 'submit' privileges.
|
||||
* NOTE: for better performance, this method retrieves its results from an
|
||||
* index (cache) and does not query the database directly.
|
||||
* This means that results may be stale or outdated until DS-4524 is resolved"
|
||||
*
|
||||
* @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
|
||||
* @param context DSpace Context
|
||||
* @param community parent community
|
||||
* @param offset the position of the first result to return
|
||||
* @param limit paging limit
|
||||
* @return discovery search result objects
|
||||
* @throws SQLException if something goes wrong
|
||||
* @throws SearchServiceException if search error
|
||||
*/
|
||||
public List<Collection> findCollectionsWithSubmit(String q, Context context, Community community,
|
||||
int offset, int limit) throws SQLException, SearchServiceException;
|
||||
|
||||
/**
|
||||
* Counts the number of Collection for which the current user has 'submit' privileges.
|
||||
* NOTE: for better performance, this method retrieves its results from an index (cache)
|
||||
* and does not query the database directly.
|
||||
* This means that results may be stale or outdated until DS-4524 is resolved."
|
||||
*
|
||||
* @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
|
||||
* @param context DSpace Context
|
||||
* @param community parent community
|
||||
* @return total collections found
|
||||
* @throws SQLException if something goes wrong
|
||||
* @throws SearchServiceException if search error
|
||||
*/
|
||||
public int countCollectionsWithSubmit(String q, Context context, Community community)
|
||||
throws SQLException, SearchServiceException;
|
||||
}
|
||||
|
@@ -200,10 +200,11 @@ public interface DSpaceObjectService<T extends DSpaceObject> {
|
||||
* and the ISO3166 country code. <code>null</code> means the
|
||||
* value has no language (for example, a date).
|
||||
* @param values the values to add.
|
||||
* @return the list of MetadataValues added to the object
|
||||
* @throws SQLException if database error
|
||||
*/
|
||||
public void addMetadata(Context context, T dso, String schema, String element, String qualifier, String lang,
|
||||
List<String> values) throws SQLException;
|
||||
public List<MetadataValue> addMetadata(Context context, T dso, String schema, String element, String qualifier,
|
||||
String lang, List<String> values) throws SQLException;
|
||||
|
||||
/**
|
||||
* Add metadata fields. These are appended to existing values.
|
||||
@@ -223,10 +224,11 @@ public interface DSpaceObjectService<T extends DSpaceObject> {
|
||||
* @param values the values to add.
|
||||
* @param authorities the external authority key for this value (or null)
|
||||
* @param confidences the authority confidence (default 0)
|
||||
* @return the list of MetadataValues added to the object
|
||||
* @throws SQLException if database error
|
||||
*/
|
||||
public void addMetadata(Context context, T dso, String schema, String element, String qualifier, String lang,
|
||||
List<String> values, List<String> authorities, List<Integer> confidences)
|
||||
public List<MetadataValue> addMetadata(Context context, T dso, String schema, String element, String qualifier,
|
||||
String lang, List<String> values, List<String> authorities, List<Integer> confidences)
|
||||
throws SQLException;
|
||||
|
||||
/**
|
||||
@@ -243,32 +245,64 @@ public interface DSpaceObjectService<T extends DSpaceObject> {
|
||||
* @param values the values to add.
|
||||
* @param authorities the external authority key for this value (or null)
|
||||
* @param confidences the authority confidence (default 0)
|
||||
* @return the list of MetadataValues added to the object
|
||||
* @throws SQLException if database error
|
||||
*/
|
||||
public void addMetadata(Context context, T dso, MetadataField metadataField, String lang, List<String> values,
|
||||
List<String> authorities, List<Integer> confidences) throws SQLException;
|
||||
public List<MetadataValue> addMetadata(Context context, T dso, MetadataField metadataField, String lang,
|
||||
List<String> values, List<String> authorities, List<Integer> confidences) throws SQLException;
|
||||
|
||||
/**
|
||||
* Shortcut for {@link #addMetadata(Context, DSpaceObject, MetadataField, String, List, List, List)} when a single
|
||||
* value need to be added
|
||||
*
|
||||
* @param context
|
||||
* @param dso
|
||||
* @param metadataField
|
||||
* @param language
|
||||
* @param value
|
||||
* @param authority
|
||||
* @param confidence
|
||||
* @param context DSpace context
|
||||
* @param dso DSpaceObject
|
||||
* @param metadataField the metadata field to which the value is to be set
|
||||
* @param language the ISO639 language code, optionally followed by an underscore
|
||||
* and the ISO3166 country code. <code>null</code> means the
|
||||
* value has no language (for example, a date).
|
||||
* @param value the value to add.
|
||||
* @param authority the external authority key for this value (or null)
|
||||
* @param confidence the authority confidence (default 0)
|
||||
* @return the MetadataValue added ot the object
|
||||
* @throws SQLException
|
||||
*/
|
||||
public void addMetadata(Context context, T dso, MetadataField metadataField, String language, String value,
|
||||
String authority, int confidence) throws SQLException;
|
||||
public MetadataValue addMetadata(Context context, T dso, MetadataField metadataField, String language,
|
||||
String value, String authority, int confidence) throws SQLException;
|
||||
|
||||
public void addMetadata(Context context, T dso, MetadataField metadataField, String language, String value)
|
||||
/**
|
||||
* Add a metadatafield. These are appended to existing values.
|
||||
* Use <code>clearMetadata</code> to remove values.
|
||||
*
|
||||
* @param context DSpace context
|
||||
* @param dso DSpaceObject
|
||||
* @param metadataField the metadata field to which the value is to be set
|
||||
* @param language the ISO639 language code, optionally followed by an underscore
|
||||
* and the ISO3166 country code. <code>null</code> means the
|
||||
* value has no language (for example, a date).
|
||||
* @param value the value to add.
|
||||
* @return the MetadataValue added ot the object
|
||||
* @throws SQLException if database error
|
||||
*/
|
||||
public MetadataValue addMetadata(Context context, T dso, MetadataField metadataField, String language, String value)
|
||||
throws SQLException;
|
||||
|
||||
public void addMetadata(Context context, T dso, MetadataField metadataField, String language, List<String> values)
|
||||
throws SQLException;
|
||||
/**
|
||||
* Add a metadatafields. These are appended to existing values.
|
||||
* Use <code>clearMetadata</code> to remove values.
|
||||
*
|
||||
* @param context DSpace context
|
||||
* @param dso DSpaceObject
|
||||
* @param metadataField the metadata field to which the value is to be set
|
||||
* @param language the ISO639 language code, optionally followed by an underscore
|
||||
* and the ISO3166 country code. <code>null</code> means the
|
||||
* value has no language (for example, a date).
|
||||
* @param values the values to add.
|
||||
* @return the list of MetadataValues added to the object
|
||||
* @throws SQLException if database error
|
||||
*/
|
||||
public List<MetadataValue> addMetadata(Context context, T dso, MetadataField metadataField, String language,
|
||||
List<String> values) throws SQLException;
|
||||
|
||||
/**
|
||||
* Add a single metadata field. This is appended to existing
|
||||
@@ -285,10 +319,11 @@ public interface DSpaceObjectService<T extends DSpaceObject> {
|
||||
* and the ISO3166 country code. <code>null</code> means the
|
||||
* value has no language (for example, a date).
|
||||
* @param value the value to add.
|
||||
* @return the MetadataValue added ot the object
|
||||
* @throws SQLException if database error
|
||||
*/
|
||||
public void addMetadata(Context context, T dso, String schema, String element, String qualifier, String lang,
|
||||
String value) throws SQLException;
|
||||
public MetadataValue addMetadata(Context context, T dso, String schema, String element, String qualifier,
|
||||
String lang, String value) throws SQLException;
|
||||
|
||||
/**
|
||||
* Add a single metadata field. This is appended to existing
|
||||
@@ -307,10 +342,11 @@ public interface DSpaceObjectService<T extends DSpaceObject> {
|
||||
* @param value the value to add.
|
||||
* @param authority the external authority key for this value (or null)
|
||||
* @param confidence the authority confidence (default 0)
|
||||
* @return the MetadataValue added ot the object
|
||||
* @throws SQLException if database error
|
||||
*/
|
||||
public void addMetadata(Context context, T dso, String schema, String element, String qualifier, String lang,
|
||||
String value, String authority, int confidence) throws SQLException;
|
||||
public MetadataValue addMetadata(Context context, T dso, String schema, String element, String qualifier,
|
||||
String lang, String value, String authority, int confidence) throws SQLException;
|
||||
|
||||
/**
|
||||
* Clear metadata values. As with <code>getDC</code> above,
|
||||
|
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
package org.dspace.content.service;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.dspace.app.bulkedit.DSpaceCSV;
|
||||
import org.dspace.content.Community;
|
||||
import org.dspace.content.Item;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.scripts.handler.DSpaceRunnableHandler;
|
||||
|
||||
/**
|
||||
* This is the interface to be implemented by a Service that deals with the exporting of Metadata
|
||||
*/
|
||||
public interface MetadataDSpaceCsvExportService {
|
||||
|
||||
/**
|
||||
* This method will export DSpaceObject objects depending on the parameters it gets. It can export all the items
|
||||
* in the repository, all the items in a community, all the items in a collection or a specific item. The latter
|
||||
* three are specified by the handle parameter. The entire repository can be exported by defining the
|
||||
* exportAllItems parameter as true
|
||||
* @param context The relevant DSpace context
|
||||
* @param exportAllItems A boolean indicating whether or not the entire repository should be exported
|
||||
* @param exportAllMetadata Defines if all metadata should be exported or only the allowed ones
|
||||
* @param handle The handle for the DSpaceObject to be exported, can be a Community, Collection or Item
|
||||
* @return A DSpaceCSV object containing the exported information
|
||||
* @throws Exception If something goes wrong
|
||||
*/
|
||||
public DSpaceCSV handleExport(Context context, boolean exportAllItems, boolean exportAllMetadata,
|
||||
String handle, DSpaceRunnableHandler dSpaceRunnableHandler) throws Exception;
|
||||
|
||||
/**
|
||||
* This method will export all the Items in the given toExport iterator to a DSpaceCSV
|
||||
* @param context The relevant DSpace context
|
||||
* @param toExport The iterator containing the items to export
|
||||
* @param exportAll Defines if all metadata should be exported or only the allowed ones
|
||||
* @return A DSpaceCSV object containing the exported information
|
||||
* @throws Exception If something goes wrong
|
||||
*/
|
||||
public DSpaceCSV export(Context context, Iterator<Item> toExport, boolean exportAll) throws Exception;
|
||||
|
||||
/**
|
||||
* This method will export all the Items within the given Community to a DSpaceCSV
|
||||
* @param context The relevant DSpace context
|
||||
* @param community The Community that contains the Items to be exported
|
||||
* @param exportAll Defines if all metadata should be exported or only the allowed ones
|
||||
* @return A DSpaceCSV object containing the exported information
|
||||
* @throws Exception If something goes wrong
|
||||
*/
|
||||
public DSpaceCSV export(Context context, Community community, boolean exportAll) throws Exception;
|
||||
|
||||
}
|
@@ -179,7 +179,7 @@ public class Context implements AutoCloseable {
|
||||
}
|
||||
|
||||
currentUser = null;
|
||||
currentLocale = I18nUtil.DEFAULTLOCALE;
|
||||
currentLocale = I18nUtil.getDefaultLocale();
|
||||
extraLogInfo = "";
|
||||
ignoreAuth = false;
|
||||
|
||||
@@ -876,4 +876,5 @@ public class Context implements AutoCloseable {
|
||||
private void reloadContextBoundEntities() throws SQLException {
|
||||
currentUser = reloadEntity(currentUser);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -37,9 +37,6 @@ import org.dspace.services.factory.DSpaceServicesFactory;
|
||||
public class I18nUtil {
|
||||
private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(I18nUtil.class);
|
||||
|
||||
// the default Locale of this DSpace Instance
|
||||
public static final Locale DEFAULTLOCALE = getDefaultLocale();
|
||||
|
||||
// delimiters between elements of UNIX/POSIX locale spec, e.g. en_US.UTF-8
|
||||
private static final String LOCALE_DELIMITERS = " _.";
|
||||
|
||||
@@ -127,7 +124,7 @@ public class I18nUtil {
|
||||
return parseLocales(locales);
|
||||
} else {
|
||||
Locale[] availableLocales = new Locale[1];
|
||||
availableLocales[0] = DEFAULTLOCALE;
|
||||
availableLocales[0] = getDefaultLocale();
|
||||
return availableLocales;
|
||||
}
|
||||
}
|
||||
@@ -148,7 +145,7 @@ public class I18nUtil {
|
||||
Locale supportedLocale = null;
|
||||
String testLocale = "";
|
||||
if (availableLocales == null) {
|
||||
supportedLocale = DEFAULTLOCALE;
|
||||
supportedLocale = getDefaultLocale();
|
||||
} else {
|
||||
if (!locale.getVariant().equals("")) {
|
||||
testLocale = locale.toString();
|
||||
@@ -188,12 +185,29 @@ public class I18nUtil {
|
||||
}
|
||||
}
|
||||
if (!isSupported) {
|
||||
supportedLocale = DEFAULTLOCALE;
|
||||
supportedLocale = getDefaultLocale();
|
||||
}
|
||||
}
|
||||
return supportedLocale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the appropriate supported Locale according for a given Locale If
|
||||
* no appropriate supported locale is found, the DEFAULTLOCALE is used
|
||||
*
|
||||
* @param locale String to find the corresponding Locale
|
||||
* @return supportedLocale
|
||||
* Locale for session according to locales supported by this DSpace instance as set in dspace.cfg
|
||||
*/
|
||||
public static Locale getSupportedLocale(String locale) {
|
||||
Locale currentLocale = null;
|
||||
if (locale != null) {
|
||||
currentLocale = I18nUtil.getSupportedLocale(new Locale(locale));
|
||||
} else {
|
||||
currentLocale = I18nUtil.getDefaultLocale();
|
||||
}
|
||||
return currentLocale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the appropriate localized version of submission-forms.xml according to language settings
|
||||
@@ -220,7 +234,7 @@ public class I18nUtil {
|
||||
* String of the message
|
||||
*/
|
||||
public static String getMessage(String key) {
|
||||
return getMessage(key.trim(), DEFAULTLOCALE);
|
||||
return getMessage(key.trim(), getDefaultLocale());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -233,7 +247,7 @@ public class I18nUtil {
|
||||
*/
|
||||
public static String getMessage(String key, Locale locale) {
|
||||
if (locale == null) {
|
||||
locale = DEFAULTLOCALE;
|
||||
locale = getDefaultLocale();
|
||||
}
|
||||
ResourceBundle.Control control =
|
||||
ResourceBundle.Control.getNoFallbackControl(
|
||||
@@ -384,4 +398,23 @@ public class I18nUtil {
|
||||
}
|
||||
return resultList.toArray(new Locale[resultList.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the input locale is in the list of supported locales
|
||||
* @param locale
|
||||
* @return true if locale is supported, false otherwise
|
||||
*/
|
||||
public static boolean isSupportedLocale(Locale locale) {
|
||||
boolean isSupported = false;
|
||||
Locale[] supportedLocales = getSupportedLocales();
|
||||
if (supportedLocales != null) {
|
||||
for (Locale sLocale: supportedLocales) {
|
||||
if (locale.getLanguage().equals(sLocale.getLanguage()) ) {
|
||||
isSupported = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return isSupported;
|
||||
}
|
||||
}
|
||||
|
@@ -345,8 +345,8 @@ public class LegacyPluginServiceImpl implements PluginService {
|
||||
" for interface=" + iname +
|
||||
" pluginName=" + name);
|
||||
Object result = pluginClass.newInstance();
|
||||
if (result instanceof SelfNamedPlugin) {
|
||||
((SelfNamedPlugin) result).setPluginInstanceName(name);
|
||||
if (result instanceof NameAwarePlugin) {
|
||||
((NameAwarePlugin) result).setPluginInstanceName(name);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
package org.dspace.core;
|
||||
|
||||
/**
|
||||
* This is the interface that should be implemented by all the named plugin that
|
||||
* like to be aware of their name
|
||||
*
|
||||
* @author Andrea Bollini (andrea.bollini at 4science.it)
|
||||
* @version $Revision$
|
||||
* @see org.dspace.core.service.PluginService
|
||||
*/
|
||||
public interface NameAwarePlugin {
|
||||
|
||||
/**
|
||||
* Get the instance's particular name.
|
||||
* Returns the name by which the class was chosen when
|
||||
* this instance was created. Only works for instances created
|
||||
* by <code>PluginService</code>, or if someone remembers to call <code>setPluginName.</code>
|
||||
* <p>
|
||||
* Useful when the implementation class wants to be configured differently
|
||||
* when it is invoked under different names.
|
||||
*
|
||||
* @return name or null if not available.
|
||||
*/
|
||||
public String getPluginInstanceName();
|
||||
|
||||
/**
|
||||
* Set the name under which this plugin was instantiated.
|
||||
* Not to be invoked by application code, it is
|
||||
* called automatically by <code>PluginService.getNamedPlugin()</code>
|
||||
* when the plugin is instantiated.
|
||||
*
|
||||
* @param name -- name used to select this class.
|
||||
*/
|
||||
public void setPluginInstanceName(String name);
|
||||
}
|
@@ -28,7 +28,7 @@ package org.dspace.core;
|
||||
* @version $Revision$
|
||||
* @see org.dspace.core.service.PluginService
|
||||
*/
|
||||
public abstract class SelfNamedPlugin {
|
||||
public abstract class SelfNamedPlugin implements NameAwarePlugin {
|
||||
// the specific alias used to find the class that created this instance.
|
||||
private String myName = null;
|
||||
|
||||
@@ -52,30 +52,13 @@ public abstract class SelfNamedPlugin {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an instance's particular name.
|
||||
* Returns the name by which the class was chosen when
|
||||
* this instance was created. Only works for instances created
|
||||
* by <code>PluginService</code>, or if someone remembers to call <code>setPluginName.</code>
|
||||
* <p>
|
||||
* Useful when the implementation class wants to be configured differently
|
||||
* when it is invoked under different names.
|
||||
*
|
||||
* @return name or null if not available.
|
||||
*/
|
||||
@Override
|
||||
public String getPluginInstanceName() {
|
||||
return myName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the name under which this plugin was instantiated.
|
||||
* Not to be invoked by application code, it is
|
||||
* called automatically by <code>PluginService.getNamedPlugin()</code>
|
||||
* when the plugin is instantiated.
|
||||
*
|
||||
* @param name -- name used to select this class.
|
||||
*/
|
||||
protected void setPluginInstanceName(String name) {
|
||||
@Override
|
||||
public void setPluginInstanceName(String name) {
|
||||
myName = name;
|
||||
}
|
||||
}
|
||||
|
@@ -199,6 +199,9 @@ public class MetadataWebService extends AbstractCurationTask implements Namespac
|
||||
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||
factory.setNamespaceAware(true);
|
||||
try {
|
||||
// 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);
|
||||
docBuilder = factory.newDocumentBuilder();
|
||||
} catch (ParserConfigurationException pcE) {
|
||||
log.error("caught exception: " + pcE);
|
||||
|
371
dspace-api/src/main/java/org/dspace/curate/Curation.java
Normal file
371
dspace-api/src/main/java/org/dspace/curate/Curation.java
Normal file
@@ -0,0 +1,371 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
package org.dspace.curate;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.PrintStream;
|
||||
import java.io.Writer;
|
||||
import java.sql.SQLException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.apache.commons.cli.ParseException;
|
||||
import org.apache.commons.io.output.NullOutputStream;
|
||||
import org.dspace.authorize.AuthorizeException;
|
||||
import org.dspace.content.DSpaceObject;
|
||||
import org.dspace.content.factory.ContentServiceFactory;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.core.factory.CoreServiceFactory;
|
||||
import org.dspace.curate.factory.CurateServiceFactory;
|
||||
import org.dspace.eperson.EPerson;
|
||||
import org.dspace.eperson.factory.EPersonServiceFactory;
|
||||
import org.dspace.eperson.service.EPersonService;
|
||||
import org.dspace.handle.factory.HandleServiceFactory;
|
||||
import org.dspace.handle.service.HandleService;
|
||||
import org.dspace.scripts.DSpaceRunnable;
|
||||
import org.dspace.utils.DSpace;
|
||||
|
||||
/**
|
||||
* CurationCli provides command-line access to Curation tools and processes.
|
||||
*
|
||||
* @author richardrodgers
|
||||
*/
|
||||
public class Curation extends DSpaceRunnable<CurationScriptConfiguration> {
|
||||
|
||||
protected EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService();
|
||||
|
||||
protected Context context;
|
||||
private CurationClientOptions curationClientOptions;
|
||||
|
||||
private String task;
|
||||
private String taskFile;
|
||||
private String id;
|
||||
private String queue;
|
||||
private String scope;
|
||||
private String reporter;
|
||||
private Map<String, String> parameters;
|
||||
private boolean verbose;
|
||||
|
||||
@Override
|
||||
public void internalRun() throws Exception {
|
||||
if (curationClientOptions == CurationClientOptions.HELP) {
|
||||
printHelp();
|
||||
return;
|
||||
}
|
||||
|
||||
Curator curator = initCurator();
|
||||
|
||||
// load curation tasks
|
||||
if (curationClientOptions == CurationClientOptions.TASK) {
|
||||
long start = System.currentTimeMillis();
|
||||
handleCurationTask(curator);
|
||||
this.endScript(start);
|
||||
}
|
||||
|
||||
// process task queue
|
||||
if (curationClientOptions == CurationClientOptions.QUEUE) {
|
||||
// process the task queue
|
||||
TaskQueue taskQueue = (TaskQueue) CoreServiceFactory.getInstance().getPluginService()
|
||||
.getSinglePlugin(TaskQueue.class);
|
||||
if (taskQueue == null) {
|
||||
super.handler.logError("No implementation configured for queue");
|
||||
throw new UnsupportedOperationException("No queue service available");
|
||||
}
|
||||
long timeRun = this.runQueue(taskQueue, curator);
|
||||
this.endScript(timeRun);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the curation task (-t) or the task in the given file (-T).
|
||||
* Checks:
|
||||
* - if required option -i is missing.
|
||||
* - if option -t has a valid task option
|
||||
*/
|
||||
private void handleCurationTask(Curator curator) throws IOException, SQLException {
|
||||
String taskName;
|
||||
if (commandLine.hasOption('t')) {
|
||||
if (verbose) {
|
||||
handler.logInfo("Adding task: " + this.task);
|
||||
}
|
||||
curator.addTask(this.task);
|
||||
if (verbose && !curator.hasTask(this.task)) {
|
||||
handler.logInfo("Task: " + this.task + " not resolved");
|
||||
}
|
||||
} else if (commandLine.hasOption('T')) {
|
||||
// load taskFile
|
||||
BufferedReader reader = null;
|
||||
try {
|
||||
reader = new BufferedReader(new FileReader(this.taskFile));
|
||||
while ((taskName = reader.readLine()) != null) {
|
||||
if (verbose) {
|
||||
super.handler.logInfo("Adding task: " + taskName);
|
||||
}
|
||||
curator.addTask(taskName);
|
||||
}
|
||||
} finally {
|
||||
if (reader != null) {
|
||||
reader.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
// run tasks against object
|
||||
if (verbose) {
|
||||
super.handler.logInfo("Starting curation");
|
||||
super.handler.logInfo("Curating id: " + this.id);
|
||||
}
|
||||
if ("all".equals(this.id)) {
|
||||
// run on whole Site
|
||||
curator.curate(context,
|
||||
ContentServiceFactory.getInstance().getSiteService().findSite(context).getHandle());
|
||||
} else {
|
||||
curator.curate(context, this.id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs task queue (-q set)
|
||||
*
|
||||
* @param queue The task queue
|
||||
* @param curator The curator
|
||||
* @return Time when queue started
|
||||
*/
|
||||
private long runQueue(TaskQueue queue, Curator curator) throws SQLException, AuthorizeException, IOException {
|
||||
// use current time as our reader 'ticket'
|
||||
long ticket = System.currentTimeMillis();
|
||||
Iterator<TaskQueueEntry> entryIter = queue.dequeue(this.queue, ticket).iterator();
|
||||
while (entryIter.hasNext()) {
|
||||
TaskQueueEntry entry = entryIter.next();
|
||||
if (verbose) {
|
||||
super.handler.logInfo("Curating id: " + entry.getObjectId());
|
||||
}
|
||||
curator.clear();
|
||||
// does entry relate to a DSO or workflow object?
|
||||
if (entry.getObjectId().indexOf('/') > 0) {
|
||||
for (String taskName : entry.getTaskNames()) {
|
||||
curator.addTask(taskName);
|
||||
}
|
||||
curator.curate(context, entry.getObjectId());
|
||||
} else {
|
||||
// make eperson who queued task the effective user
|
||||
EPerson agent = ePersonService.findByEmail(context, entry.getEpersonId());
|
||||
if (agent != null) {
|
||||
context.setCurrentUser(agent);
|
||||
}
|
||||
CurateServiceFactory.getInstance().getWorkflowCuratorService()
|
||||
.curate(curator, context, entry.getObjectId());
|
||||
}
|
||||
}
|
||||
queue.release(this.queue, ticket, true);
|
||||
return ticket;
|
||||
}
|
||||
|
||||
/**
|
||||
* End of curation script; logs script time if -v verbose is set
|
||||
*
|
||||
* @param timeRun Time script was started
|
||||
* @throws SQLException If DSpace contextx can't complete
|
||||
*/
|
||||
private void endScript(long timeRun) throws SQLException {
|
||||
context.complete();
|
||||
if (verbose) {
|
||||
long elapsed = System.currentTimeMillis() - timeRun;
|
||||
this.handler.logInfo("Ending curation. Elapsed time: " + elapsed);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the curator with command line variables
|
||||
*
|
||||
* @return Initialised curator
|
||||
* @throws FileNotFoundException If file of command line variable -r reporter is not found
|
||||
*/
|
||||
private Curator initCurator() throws FileNotFoundException {
|
||||
Curator curator = new Curator();
|
||||
OutputStream reporterStream;
|
||||
if (null == this.reporter) {
|
||||
reporterStream = new NullOutputStream();
|
||||
} else if ("-".equals(this.reporter)) {
|
||||
reporterStream = System.out;
|
||||
} else {
|
||||
reporterStream = new PrintStream(this.reporter);
|
||||
}
|
||||
Writer reportWriter = new OutputStreamWriter(reporterStream);
|
||||
curator.setReporter(reportWriter);
|
||||
|
||||
if (this.scope != null) {
|
||||
Curator.TxScope txScope = Curator.TxScope.valueOf(this.scope.toUpperCase());
|
||||
curator.setTransactionScope(txScope);
|
||||
}
|
||||
|
||||
curator.addParameters(parameters);
|
||||
// we are operating in batch mode, if anyone cares.
|
||||
curator.setInvoked(Curator.Invoked.BATCH);
|
||||
return curator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void printHelp() {
|
||||
super.printHelp();
|
||||
super.handler.logInfo("\nwhole repo: CurationCli -t estimate -i all");
|
||||
super.handler.logInfo("single item: CurationCli -t generate -i itemId");
|
||||
super.handler.logInfo("task queue: CurationCli -q monthly");
|
||||
}
|
||||
|
||||
@Override
|
||||
public CurationScriptConfiguration getScriptConfiguration() {
|
||||
return new DSpace().getServiceManager().getServiceByName("curate", CurationScriptConfiguration.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setup() throws ParseException {
|
||||
assignCurrentUserInContext();
|
||||
this.curationClientOptions = CurationClientOptions.getClientOption(commandLine);
|
||||
|
||||
if (this.curationClientOptions != null) {
|
||||
this.initGeneralLineOptionsAndCheckIfValid();
|
||||
if (curationClientOptions == CurationClientOptions.TASK) {
|
||||
this.initTaskLineOptionsAndCheckIfValid();
|
||||
} else if (curationClientOptions == CurationClientOptions.QUEUE) {
|
||||
this.queue = this.commandLine.getOptionValue('q');
|
||||
}
|
||||
} else {
|
||||
throw new IllegalArgumentException("[--help || --task|--taskfile <> -identifier <> || -queue <> ] must be" +
|
||||
" specified");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will assign the currentUser to the {@link Context} variable which is also created in this method.
|
||||
* The instance of the method in this class will fetch the EPersonIdentifier from this class, this identifier
|
||||
* was given to this class upon instantiation, it'll then be used to find the {@link EPerson} associated with it
|
||||
* and this {@link EPerson} will be set as the currentUser of the created {@link Context}
|
||||
* @throws ParseException If something went wrong with the retrieval of the EPerson Identifier
|
||||
*/
|
||||
protected void assignCurrentUserInContext() throws ParseException {
|
||||
UUID currentUserUuid = this.getEpersonIdentifier();
|
||||
try {
|
||||
this.context = new Context(Context.Mode.BATCH_EDIT);
|
||||
EPerson eperson = ePersonService.find(context, currentUserUuid);
|
||||
if (eperson == null) {
|
||||
super.handler.logError("EPerson not found: " + currentUserUuid);
|
||||
throw new IllegalArgumentException("Unable to find a user with uuid: " + currentUserUuid);
|
||||
}
|
||||
this.context.setCurrentUser(eperson);
|
||||
} catch (SQLException e) {
|
||||
handler.handleException("Something went wrong trying to fetch eperson for uuid: " + currentUserUuid, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills in some optional command line options.
|
||||
* Checks if there are missing required options or invalid values for options.
|
||||
*/
|
||||
private void initGeneralLineOptionsAndCheckIfValid() {
|
||||
// report file
|
||||
if (this.commandLine.hasOption('r')) {
|
||||
this.reporter = this.commandLine.getOptionValue('r');
|
||||
}
|
||||
|
||||
// parameters
|
||||
this.parameters = new HashMap<>();
|
||||
if (this.commandLine.hasOption('p')) {
|
||||
for (String parameter : this.commandLine.getOptionValues('p')) {
|
||||
String[] parts = parameter.split("=", 2);
|
||||
String name = parts[0].trim();
|
||||
String value;
|
||||
if (parts.length > 1) {
|
||||
value = parts[1].trim();
|
||||
} else {
|
||||
value = "true";
|
||||
}
|
||||
this.parameters.put(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
// verbose
|
||||
verbose = false;
|
||||
if (commandLine.hasOption('v')) {
|
||||
verbose = true;
|
||||
}
|
||||
|
||||
// scope
|
||||
if (this.commandLine.getOptionValue('s') != null) {
|
||||
this.scope = this.commandLine.getOptionValue('s');
|
||||
if (this.scope != null && Curator.TxScope.valueOf(this.scope.toUpperCase()) == null) {
|
||||
this.handler.logError("Bad transaction scope '" + this.scope + "': only 'object', 'curation' or " +
|
||||
"'open' recognized");
|
||||
throw new IllegalArgumentException(
|
||||
"Bad transaction scope '" + this.scope + "': only 'object', 'curation' or " +
|
||||
"'open' recognized");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills in required command line options for the task or taskFile option.
|
||||
* Checks if there are is a missing required -i option and if -i is either 'all' or a valid dso handle.
|
||||
* Checks if -t task has a valid task option.
|
||||
* Checks if -T taskfile is a valid file.
|
||||
*/
|
||||
private void initTaskLineOptionsAndCheckIfValid() {
|
||||
// task or taskFile
|
||||
if (this.commandLine.hasOption('t')) {
|
||||
this.task = this.commandLine.getOptionValue('t');
|
||||
if (!CurationClientOptions.getTaskOptions().contains(this.task)) {
|
||||
super.handler
|
||||
.logError("-t task must be one of: " + CurationClientOptions.getTaskOptions());
|
||||
throw new IllegalArgumentException(
|
||||
"-t task must be one of: " + CurationClientOptions.getTaskOptions());
|
||||
}
|
||||
} else if (this.commandLine.hasOption('T')) {
|
||||
this.taskFile = this.commandLine.getOptionValue('T');
|
||||
if (!(new File(this.taskFile).isFile())) {
|
||||
super.handler
|
||||
.logError("-T taskFile must be valid file: " + this.taskFile);
|
||||
throw new IllegalArgumentException("-T taskFile must be valid file: " + this.taskFile);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.commandLine.hasOption('i')) {
|
||||
this.id = this.commandLine.getOptionValue('i').toLowerCase();
|
||||
if (!this.id.equalsIgnoreCase("all")) {
|
||||
HandleService handleService = HandleServiceFactory.getInstance().getHandleService();
|
||||
DSpaceObject dso;
|
||||
try {
|
||||
dso = handleService.resolveToObject(this.context, id);
|
||||
} catch (SQLException e) {
|
||||
super.handler.logError("SQLException trying to resolve handle " + id + " to a valid dso");
|
||||
throw new IllegalArgumentException(
|
||||
"SQLException trying to resolve handle " + id + " to a valid dso");
|
||||
}
|
||||
if (dso == null) {
|
||||
super.handler.logError("Id must be specified: a valid dso handle or 'all'; " + this.id + " could " +
|
||||
"not be resolved to valid dso handle");
|
||||
throw new IllegalArgumentException(
|
||||
"Id must be specified: a valid dso handle or 'all'; " + this.id + " could " +
|
||||
"not be resolved to valid dso handle");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
super.handler.logError("Id must be specified: a handle, 'all', or no -i and a -q task queue (-h for " +
|
||||
"help)");
|
||||
throw new IllegalArgumentException(
|
||||
"Id must be specified: a handle, 'all', or no -i and a -q task queue (-h for " +
|
||||
"help)");
|
||||
}
|
||||
}
|
||||
}
|
@@ -7,269 +7,42 @@
|
||||
*/
|
||||
package org.dspace.curate;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.FileReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.PrintStream;
|
||||
import java.io.Writer;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.sql.SQLException;
|
||||
|
||||
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.PosixParser;
|
||||
import org.apache.commons.io.output.NullOutputStream;
|
||||
import org.dspace.content.factory.ContentServiceFactory;
|
||||
import org.apache.commons.cli.ParseException;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.core.factory.CoreServiceFactory;
|
||||
import org.dspace.curate.factory.CurateServiceFactory;
|
||||
import org.dspace.eperson.EPerson;
|
||||
import org.dspace.eperson.factory.EPersonServiceFactory;
|
||||
import org.dspace.eperson.service.EPersonService;
|
||||
|
||||
/**
|
||||
* CurationCli provides command-line access to Curation tools and processes.
|
||||
*
|
||||
* @author richardrodgers
|
||||
* This is the CLI version of the {@link Curation} script.
|
||||
* This will only be called when the curate script is called from a commandline instance.
|
||||
*/
|
||||
public class CurationCli {
|
||||
public class CurationCli extends Curation {
|
||||
|
||||
/**
|
||||
* Default constructor
|
||||
* This is the overridden instance of the {@link Curation#assignCurrentUserInContext()} method in the parent class
|
||||
* {@link Curation}.
|
||||
* This is done so that the CLI version of the Script is able to retrieve its currentUser from the -e flag given
|
||||
* with the parameters of the Script.
|
||||
* @throws ParseException If the e flag was not given to the parameters when calling the script
|
||||
*/
|
||||
private CurationCli() { }
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
// create an options object and populate it
|
||||
CommandLineParser parser = new PosixParser();
|
||||
|
||||
Options options = new Options();
|
||||
|
||||
options.addOption("t", "task", true,
|
||||
"curation task name");
|
||||
options.addOption("T", "taskfile", true,
|
||||
"file containing curation task names");
|
||||
options.addOption("i", "id", true,
|
||||
"Id (handle) of object to perform task on, or 'all' to perform on whole repository");
|
||||
options.addOption("p", "parameter", true,
|
||||
"a task parameter 'NAME=VALUE'");
|
||||
options.addOption("q", "queue", true,
|
||||
"name of task queue to process");
|
||||
options.addOption("e", "eperson", true,
|
||||
"email address of curating eperson");
|
||||
options.addOption("r", "reporter", true,
|
||||
"relative or absolute path to the desired report file. "
|
||||
+ "Use '-' to report to console. "
|
||||
+ "If absent, no reporting");
|
||||
options.addOption("s", "scope", true,
|
||||
"transaction scope to impose: use 'object', 'curation', or 'open'. If absent, 'open' " +
|
||||
"applies");
|
||||
options.addOption("v", "verbose", false,
|
||||
"report activity to stdout");
|
||||
options.addOption("h", "help", false, "help");
|
||||
|
||||
CommandLine line = parser.parse(options, args);
|
||||
|
||||
String taskName = null;
|
||||
String taskFileName = null;
|
||||
String idName = null;
|
||||
String taskQueueName = null;
|
||||
String ePersonName = null;
|
||||
String reporterName = null;
|
||||
String scope = null;
|
||||
boolean verbose = false;
|
||||
final Map<String, String> parameters = new HashMap<>();
|
||||
|
||||
if (line.hasOption('h')) {
|
||||
HelpFormatter help = new HelpFormatter();
|
||||
help.printHelp("CurationCli\n", options);
|
||||
System.out
|
||||
.println("\nwhole repo: CurationCli -t estimate -i all");
|
||||
System.out
|
||||
.println("single item: CurationCli -t generate -i itemId");
|
||||
System.out
|
||||
.println("task queue: CurationCli -q monthly");
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
if (line.hasOption('t')) { // task
|
||||
taskName = line.getOptionValue('t');
|
||||
}
|
||||
|
||||
if (line.hasOption('T')) { // task file
|
||||
taskFileName = line.getOptionValue('T');
|
||||
}
|
||||
|
||||
if (line.hasOption('i')) { // id
|
||||
idName = line.getOptionValue('i');
|
||||
}
|
||||
|
||||
if (line.hasOption('q')) { // task queue
|
||||
taskQueueName = line.getOptionValue('q');
|
||||
}
|
||||
|
||||
if (line.hasOption('e')) { // eperson
|
||||
ePersonName = line.getOptionValue('e');
|
||||
}
|
||||
|
||||
if (line.hasOption('p')) { // parameter
|
||||
for (String parameter : line.getOptionValues('p')) {
|
||||
String[] parts = parameter.split("=", 2);
|
||||
String name = parts[0].trim();
|
||||
String value;
|
||||
if (parts.length > 1) {
|
||||
value = parts[1].trim();
|
||||
} else {
|
||||
value = "true";
|
||||
}
|
||||
parameters.put(name, value);
|
||||
}
|
||||
}
|
||||
if (line.hasOption('r')) { // report file
|
||||
reporterName = line.getOptionValue('r');
|
||||
}
|
||||
|
||||
|
||||
if (line.hasOption('s')) { // transaction scope
|
||||
scope = line.getOptionValue('s');
|
||||
}
|
||||
|
||||
if (line.hasOption('v')) { // verbose
|
||||
verbose = true;
|
||||
}
|
||||
|
||||
// now validate the args
|
||||
if (idName == null && taskQueueName == null) {
|
||||
System.out.println("Id must be specified: a handle, 'all', or a task queue (-h for help)");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
if (taskName == null && taskFileName == null && taskQueueName == null) {
|
||||
System.out.println("A curation task or queue must be specified (-h for help)");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
if (scope != null && Curator.TxScope.valueOf(scope.toUpperCase()) == null) {
|
||||
System.out.println("Bad transaction scope '" + scope + "': only 'object', 'curation' or 'open' recognized");
|
||||
System.exit(1);
|
||||
}
|
||||
EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService();
|
||||
|
||||
Context c = new Context(Context.Mode.BATCH_EDIT);
|
||||
if (ePersonName != null) {
|
||||
EPerson ePerson = ePersonService.findByEmail(c, ePersonName);
|
||||
if (ePerson == null) {
|
||||
System.out.println("EPerson not found: " + ePersonName);
|
||||
System.exit(1);
|
||||
}
|
||||
c.setCurrentUser(ePerson);
|
||||
} else {
|
||||
c.turnOffAuthorisationSystem();
|
||||
}
|
||||
|
||||
Curator curator = new Curator();
|
||||
OutputStream reporter;
|
||||
if (null == reporterName) {
|
||||
reporter = new NullOutputStream();
|
||||
} else if ("-".equals(reporterName)) {
|
||||
reporter = System.out;
|
||||
} else {
|
||||
reporter = new PrintStream(reporterName);
|
||||
}
|
||||
Writer reportWriter = new OutputStreamWriter(reporter);
|
||||
curator.setReporter(reportWriter);
|
||||
|
||||
if (scope != null) {
|
||||
Curator.TxScope txScope = Curator.TxScope.valueOf(scope.toUpperCase());
|
||||
curator.setTransactionScope(txScope);
|
||||
}
|
||||
curator.addParameters(parameters);
|
||||
// we are operating in batch mode, if anyone cares.
|
||||
curator.setInvoked(Curator.Invoked.BATCH);
|
||||
// load curation tasks
|
||||
if (taskName != null) {
|
||||
if (verbose) {
|
||||
System.out.println("Adding task: " + taskName);
|
||||
}
|
||||
curator.addTask(taskName);
|
||||
if (verbose && !curator.hasTask(taskName)) {
|
||||
System.out.println("Task: " + taskName + " not resolved");
|
||||
}
|
||||
} else if (taskQueueName == null) {
|
||||
// load taskFile
|
||||
BufferedReader reader = null;
|
||||
@Override
|
||||
protected void assignCurrentUserInContext() throws ParseException {
|
||||
if (this.commandLine.hasOption('e')) {
|
||||
String ePersonEmail = this.commandLine.getOptionValue('e');
|
||||
this.context = new Context(Context.Mode.BATCH_EDIT);
|
||||
try {
|
||||
reader = new BufferedReader(new FileReader(taskFileName));
|
||||
while ((taskName = reader.readLine()) != null) {
|
||||
if (verbose) {
|
||||
System.out.println("Adding task: " + taskName);
|
||||
EPerson ePerson = ePersonService.findByEmail(this.context, ePersonEmail);
|
||||
if (ePerson == null) {
|
||||
super.handler.logError("EPerson not found: " + ePersonEmail);
|
||||
throw new IllegalArgumentException("Unable to find a user with email: " + ePersonEmail);
|
||||
}
|
||||
curator.addTask(taskName);
|
||||
}
|
||||
} finally {
|
||||
if (reader != null) {
|
||||
reader.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
// run tasks against object
|
||||
long start = System.currentTimeMillis();
|
||||
if (verbose) {
|
||||
System.out.println("Starting curation");
|
||||
}
|
||||
if (idName != null) {
|
||||
if (verbose) {
|
||||
System.out.println("Curating id: " + idName);
|
||||
}
|
||||
if ("all".equals(idName)) {
|
||||
// run on whole Site
|
||||
curator.curate(c, ContentServiceFactory.getInstance().getSiteService().findSite(c).getHandle());
|
||||
} else {
|
||||
curator.curate(c, idName);
|
||||
this.context.setCurrentUser(ePerson);
|
||||
} catch (SQLException e) {
|
||||
throw new IllegalArgumentException("SQLException trying to find user with email: " + ePersonEmail);
|
||||
}
|
||||
} else {
|
||||
// process the task queue
|
||||
TaskQueue queue = (TaskQueue) CoreServiceFactory.getInstance().getPluginService()
|
||||
.getSinglePlugin(TaskQueue.class);
|
||||
if (queue == null) {
|
||||
System.out.println("No implementation configured for queue");
|
||||
throw new UnsupportedOperationException("No queue service available");
|
||||
}
|
||||
// use current time as our reader 'ticket'
|
||||
long ticket = System.currentTimeMillis();
|
||||
Iterator<TaskQueueEntry> entryIter = queue.dequeue(taskQueueName, ticket).iterator();
|
||||
while (entryIter.hasNext()) {
|
||||
TaskQueueEntry entry = entryIter.next();
|
||||
if (verbose) {
|
||||
System.out.println("Curating id: " + entry.getObjectId());
|
||||
}
|
||||
curator.clear();
|
||||
// does entry relate to a DSO or workflow object?
|
||||
if (entry.getObjectId().indexOf("/") > 0) {
|
||||
for (String task : entry.getTaskNames()) {
|
||||
curator.addTask(task);
|
||||
}
|
||||
curator.curate(c, entry.getObjectId());
|
||||
} else {
|
||||
// make eperson who queued task the effective user
|
||||
EPerson agent = ePersonService.findByEmail(c, entry.getEpersonId());
|
||||
if (agent != null) {
|
||||
c.setCurrentUser(agent);
|
||||
}
|
||||
CurateServiceFactory.getInstance().getWorkflowCuratorService()
|
||||
.curate(curator, c, entry.getObjectId());
|
||||
}
|
||||
}
|
||||
queue.release(taskQueueName, ticket, true);
|
||||
}
|
||||
c.complete();
|
||||
if (verbose) {
|
||||
long elapsed = System.currentTimeMillis() - start;
|
||||
System.out.println("Ending curation. Elapsed time: " + elapsed);
|
||||
throw new ParseException("Required parameter -e missing!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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.curate;
|
||||
|
||||
import org.apache.commons.cli.Options;
|
||||
|
||||
/**
|
||||
* This is the CLI version of the {@link CurationScriptConfiguration} class that handles the configuration for the
|
||||
* {@link CurationCli} script
|
||||
*/
|
||||
public class CurationCliScriptConfiguration extends CurationScriptConfiguration<Curation> {
|
||||
|
||||
@Override
|
||||
public Options getOptions() {
|
||||
options = super.getOptions();
|
||||
options.addOption("e", "eperson", true, "email address of curating eperson");
|
||||
options.getOption("e").setType(String.class);
|
||||
options.getOption("e").setRequired(true);
|
||||
return options;
|
||||
}
|
||||
}
|
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
package org.dspace.curate;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.cli.CommandLine;
|
||||
import org.apache.commons.cli.Options;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.dspace.services.ConfigurationService;
|
||||
import org.dspace.services.factory.DSpaceServicesFactory;
|
||||
|
||||
/**
|
||||
* This Enum holds all the possible options and combinations for the Curation script
|
||||
*
|
||||
* @author Maria Verdonck (Atmire) on 23/06/2020
|
||||
*/
|
||||
public enum CurationClientOptions {
|
||||
TASK,
|
||||
QUEUE,
|
||||
HELP;
|
||||
|
||||
private static List<String> taskOptions;
|
||||
|
||||
/**
|
||||
* This method resolves the CommandLine parameters to figure out which action the curation script should perform
|
||||
*
|
||||
* @param commandLine The relevant CommandLine for the curation script
|
||||
* @return The curation option to be ran, parsed from the CommandLine
|
||||
*/
|
||||
protected static CurationClientOptions getClientOption(CommandLine commandLine) {
|
||||
if (commandLine.hasOption("h")) {
|
||||
return CurationClientOptions.HELP;
|
||||
} else if (commandLine.hasOption("t") || commandLine.hasOption("T")) {
|
||||
return CurationClientOptions.TASK;
|
||||
} else if (commandLine.hasOption("q")) {
|
||||
return CurationClientOptions.QUEUE;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will create all the possible Options for the {@link Curation} script.
|
||||
* This will be used by {@link CurationScriptConfiguration}
|
||||
* @return The options for the {@link Curation} script
|
||||
*/
|
||||
protected static Options constructOptions() {
|
||||
Options options = new Options();
|
||||
|
||||
options.addOption("t", "task", true, "curation task name; options: " + getTaskOptions());
|
||||
options.addOption("T", "taskfile", true, "file containing curation task names");
|
||||
options.addOption("i", "id", true,
|
||||
"Id (handle) of object to perform task on, or 'all' to perform on whole repository");
|
||||
options.addOption("p", "parameter", true, "a task parameter 'NAME=VALUE'");
|
||||
options.addOption("q", "queue", true, "name of task queue to process");
|
||||
options.addOption("r", "reporter", true,
|
||||
"relative or absolute path to the desired report file. Use '-' to report to console. If absent, no " +
|
||||
"reporting");
|
||||
options.addOption("s", "scope", true,
|
||||
"transaction scope to impose: use 'object', 'curation', or 'open'. If absent, 'open' applies");
|
||||
options.addOption("v", "verbose", false, "report activity to stdout");
|
||||
options.addOption("h", "help", false, "help");
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates list of the taskOptions' keys from the configs of plugin.named.org.dspace.curate.CurationTask
|
||||
*
|
||||
* @return List of the taskOptions' keys from the configs of plugin.named.org.dspace.curate.CurationTask
|
||||
*/
|
||||
public static List<String> getTaskOptions() {
|
||||
if (taskOptions == null) {
|
||||
ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService();
|
||||
String[] taskConfigs = configurationService.getArrayProperty("plugin.named.org.dspace.curate.CurationTask");
|
||||
taskOptions = new ArrayList<>();
|
||||
for (String taskConfig : taskConfigs) {
|
||||
taskOptions.add(StringUtils.substringAfterLast(taskConfig, "=").trim());
|
||||
}
|
||||
}
|
||||
return taskOptions;
|
||||
}
|
||||
}
|
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
package org.dspace.curate;
|
||||
|
||||
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 Curation} script
|
||||
*
|
||||
* @author Maria Verdonck (Atmire) on 23/06/2020
|
||||
*/
|
||||
public class CurationScriptConfiguration<T extends Curation> extends ScriptConfiguration<T> {
|
||||
|
||||
@Autowired
|
||||
private AuthorizeService authorizeService;
|
||||
|
||||
private Class<T> dspaceRunnableClass;
|
||||
|
||||
@Override
|
||||
public Class<T> getDspaceRunnableClass() {
|
||||
return this.dspaceRunnableClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDspaceRunnableClass(Class<T> dspaceRunnableClass) {
|
||||
this.dspaceRunnableClass = dspaceRunnableClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Only admin can run Curation script via the scripts and processes endpoints.
|
||||
* @param context The relevant DSpace context
|
||||
* @return True if currentUser is admin, otherwise false
|
||||
*/
|
||||
@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) {
|
||||
super.options = CurationClientOptions.constructOptions();
|
||||
}
|
||||
return options;
|
||||
}
|
||||
}
|
@@ -98,6 +98,7 @@ public class Curator {
|
||||
communityService = ContentServiceFactory.getInstance().getCommunityService();
|
||||
itemService = ContentServiceFactory.getInstance().getItemService();
|
||||
handleService = HandleServiceFactory.getInstance().getHandleService();
|
||||
resolver = new TaskResolver();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -142,10 +143,10 @@ public class Curator {
|
||||
// performance order currently FIFO - to be revisited
|
||||
perfList.add(taskName);
|
||||
} catch (IOException ioE) {
|
||||
log.error("Task: '" + taskName + "' initialization failure: " + ioE.getMessage());
|
||||
System.out.println("Task: '" + taskName + "' initialization failure: " + ioE.getMessage());
|
||||
}
|
||||
} else {
|
||||
log.error("Task: '" + taskName + "' does not resolve");
|
||||
System.out.println("Task: '" + taskName + "' does not resolve");
|
||||
}
|
||||
return this;
|
||||
}
|
||||
@@ -259,13 +260,6 @@ public class Curator {
|
||||
/**
|
||||
* Performs all configured tasks upon DSpace object
|
||||
* (Community, Collection or Item).
|
||||
* <P>
|
||||
* Note: Site-wide tasks will default to running as
|
||||
* an Anonymous User unless you call the Site-wide task
|
||||
* via the {@link curate(Context,String)} or
|
||||
* {@link #curate(Context, DSpaceObject)} method with an
|
||||
* authenticated Context object.
|
||||
*
|
||||
* @param dso the DSpace object
|
||||
* @throws IOException if IO error
|
||||
*/
|
||||
@@ -325,7 +319,7 @@ public class Curator {
|
||||
taskQ.enqueue(queueId, new TaskQueueEntry(c.getCurrentUser().getName(),
|
||||
System.currentTimeMillis(), perfList, id));
|
||||
} else {
|
||||
log.error("curate - no TaskQueue implemented");
|
||||
System.out.println("curate - no TaskQueue implemented");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -346,7 +340,7 @@ public class Curator {
|
||||
try {
|
||||
reporter.append(message);
|
||||
} catch (IOException ex) {
|
||||
log.error("Task reporting failure", ex);
|
||||
System.out.println("Task reporting failure: " + ex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -552,7 +546,7 @@ public class Curator {
|
||||
return !suspend(statusCode);
|
||||
} catch (IOException ioe) {
|
||||
//log error & pass exception upwards
|
||||
log.error("Error executing curation task '" + task.getName() + "'", ioe);
|
||||
System.out.println("Error executing curation task '" + task.getName() + "'; " + ioe);
|
||||
throw ioe;
|
||||
}
|
||||
}
|
||||
@@ -568,7 +562,7 @@ public class Curator {
|
||||
return !suspend(statusCode);
|
||||
} catch (IOException ioe) {
|
||||
//log error & pass exception upwards
|
||||
log.error("Error executing curation task '" + task.getName() + "'", ioe);
|
||||
System.out.println("Error executing curation task '" + task.getName() + "'; " + ioe);
|
||||
throw ioe;
|
||||
}
|
||||
}
|
||||
|
@@ -7,6 +7,9 @@
|
||||
*/
|
||||
package org.dspace.discovery;
|
||||
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
@@ -31,7 +34,7 @@ public class DiscoverQuery {
|
||||
**/
|
||||
private String query;
|
||||
private List<String> filterQueries;
|
||||
private String DSpaceObjectFilter = null;
|
||||
private List<String> dspaceObjectFilters = new ArrayList<>();
|
||||
private List<String> fieldPresentQueries;
|
||||
private boolean spellCheck;
|
||||
|
||||
@@ -118,20 +121,33 @@ public class DiscoverQuery {
|
||||
* Sets the DSpace object filter, must be an DSpace Object type integer
|
||||
* can be used to only return objects from a certain DSpace Object type
|
||||
*
|
||||
* @param DSpaceObjectFilter the DSpace object filer
|
||||
* @param dspaceObjectFilter the DSpace object filter
|
||||
*/
|
||||
public void setDSpaceObjectFilter(String DSpaceObjectFilter) {
|
||||
this.DSpaceObjectFilter = DSpaceObjectFilter;
|
||||
public void setDSpaceObjectFilter(String dspaceObjectFilter) {
|
||||
this.dspaceObjectFilters = singletonList(dspaceObjectFilter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the DSpace object filter
|
||||
* can be used to only return objects from a certain DSpace Object type
|
||||
* Adds a DSpace object filter, must be an DSpace Object type integer.
|
||||
* Can be used to also return objects from a certain DSpace Object type.
|
||||
*
|
||||
* @return the DSpace object filer
|
||||
* @param dspaceObjectFilter the DSpace object filer
|
||||
*/
|
||||
public String getDSpaceObjectFilter() {
|
||||
return DSpaceObjectFilter;
|
||||
public void addDSpaceObjectFilter(String dspaceObjectFilter) {
|
||||
|
||||
if (isNotBlank(dspaceObjectFilter)) {
|
||||
this.dspaceObjectFilters.add(dspaceObjectFilter);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the DSpace object filters
|
||||
* can be used to only return objects from certain DSpace Object types
|
||||
*
|
||||
* @return the DSpace object filters
|
||||
*/
|
||||
public List<String> getDSpaceObjectFilters() {
|
||||
return dspaceObjectFilters;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -14,7 +14,6 @@ import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.apache.commons.cli.CommandLine;
|
||||
import org.apache.commons.cli.Options;
|
||||
import org.apache.commons.cli.ParseException;
|
||||
import org.dspace.content.Collection;
|
||||
import org.dspace.content.Community;
|
||||
@@ -30,17 +29,18 @@ import org.dspace.discovery.indexobject.factory.IndexFactory;
|
||||
import org.dspace.discovery.indexobject.factory.IndexObjectFactoryFactory;
|
||||
import org.dspace.handle.factory.HandleServiceFactory;
|
||||
import org.dspace.scripts.DSpaceRunnable;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.dspace.services.factory.DSpaceServicesFactory;
|
||||
import org.dspace.utils.DSpace;
|
||||
|
||||
/**
|
||||
* Class used to reindex dspace communities/collections/items into discovery
|
||||
*/
|
||||
public class IndexClient extends DSpaceRunnable {
|
||||
public class IndexClient extends DSpaceRunnable<IndexDiscoveryScriptConfiguration> {
|
||||
|
||||
private Context context;
|
||||
|
||||
@Autowired
|
||||
private IndexingService indexer;
|
||||
private IndexingService indexer = DSpaceServicesFactory.getInstance().getServiceManager()
|
||||
.getServiceByName(IndexingService.class.getName(),
|
||||
IndexingService.class);
|
||||
|
||||
private IndexClientOptions indexClientOptions;
|
||||
|
||||
@@ -144,6 +144,12 @@ public class IndexClient extends DSpaceRunnable {
|
||||
handler.logInfo("Done with indexing");
|
||||
}
|
||||
|
||||
@Override
|
||||
public IndexDiscoveryScriptConfiguration getScriptConfiguration() {
|
||||
return new DSpace().getServiceManager().getServiceByName("index-discovery",
|
||||
IndexDiscoveryScriptConfiguration.class);
|
||||
}
|
||||
|
||||
public void setup() throws ParseException {
|
||||
try {
|
||||
context = new Context(Context.Mode.READ_ONLY);
|
||||
@@ -151,18 +157,8 @@ public class IndexClient extends DSpaceRunnable {
|
||||
} catch (Exception e) {
|
||||
throw new ParseException("Unable to create a new DSpace Context: " + e.getMessage());
|
||||
}
|
||||
|
||||
indexClientOptions = IndexClientOptions.getIndexClientOption(commandLine);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for this class. This will ensure that the Options are created and set appropriately.
|
||||
*/
|
||||
private IndexClient() {
|
||||
Options options = IndexClientOptions.constructOptions();
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indexes the given object and all children, if applicable.
|
||||
*
|
||||
|
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
package org.dspace.discovery;
|
||||
|
||||
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 IndexClient} script
|
||||
*/
|
||||
public class IndexDiscoveryScriptConfiguration<T extends IndexClient> extends ScriptConfiguration<T> {
|
||||
|
||||
@Autowired
|
||||
private AuthorizeService authorizeService;
|
||||
|
||||
private Class<T> dspaceRunnableClass;
|
||||
|
||||
@Override
|
||||
public Class<T> getDspaceRunnableClass() {
|
||||
return 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) {
|
||||
super.options = IndexClientOptions.constructOptions();
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic setter for the dspaceRunnableClass
|
||||
* @param dspaceRunnableClass The dspaceRunnableClass to be set on this IndexDiscoveryScriptConfiguration
|
||||
*/
|
||||
@Override
|
||||
public void setDspaceRunnableClass(Class<T> dspaceRunnableClass) {
|
||||
this.dspaceRunnableClass = dspaceRunnableClass;
|
||||
}
|
||||
}
|
@@ -8,6 +8,7 @@
|
||||
package org.dspace.discovery;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.logging.log4j.Logger;
|
||||
@@ -15,6 +16,7 @@ import org.dspace.content.Bundle;
|
||||
import org.dspace.content.DSpaceObject;
|
||||
import org.dspace.core.Constants;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.discovery.indexobject.factory.IndexFactory;
|
||||
import org.dspace.discovery.indexobject.factory.IndexObjectFactoryFactory;
|
||||
import org.dspace.event.Consumer;
|
||||
import org.dspace.event.Event;
|
||||
@@ -67,7 +69,7 @@ public class IndexEventConsumer implements Consumer {
|
||||
|
||||
int st = event.getSubjectType();
|
||||
if (!(st == Constants.ITEM || st == Constants.BUNDLE
|
||||
|| st == Constants.COLLECTION || st == Constants.COMMUNITY)) {
|
||||
|| st == Constants.COLLECTION || st == Constants.COMMUNITY || st == Constants.SITE)) {
|
||||
log
|
||||
.warn("IndexConsumer should not have been given this kind of Subject in an event, skipping: "
|
||||
+ event.toString());
|
||||
@@ -104,10 +106,28 @@ public class IndexEventConsumer implements Consumer {
|
||||
case Event.MODIFY:
|
||||
case Event.MODIFY_METADATA:
|
||||
if (subject == null) {
|
||||
if (st == Constants.SITE) {
|
||||
// Update the indexable objects of type in event.detail of objects with ids in event.identifiers
|
||||
for (String id : event.getIdentifiers()) {
|
||||
IndexFactory indexableObjectService = IndexObjectFactoryFactory.getInstance().
|
||||
getIndexFactoryByType(event.getDetail());
|
||||
Optional<IndexableObject> indexableObject = Optional.empty();
|
||||
indexableObject = indexableObjectService.findIndexableObject(ctx, id);
|
||||
if (indexableObject.isPresent()) {
|
||||
log.debug("consume() adding event to update queue: " + event.toString());
|
||||
objectsToUpdate
|
||||
.addAll(indexObjectServiceFactory
|
||||
.getIndexableObjects(ctx, indexableObject.get().getIndexedObject()));
|
||||
} else {
|
||||
log.warn("Cannot resolve " + id);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.warn(event.getEventTypeAsString() + " event, could not get object for "
|
||||
+ event.getSubjectTypeAsString() + " id="
|
||||
+ event.getSubjectID()
|
||||
+ ", perhaps it has been deleted.");
|
||||
}
|
||||
} else {
|
||||
log.debug("consume() adding event to update queue: " + event.toString());
|
||||
objectsToUpdate.addAll(indexObjectServiceFactory.getIndexableObjects(ctx, subject));
|
||||
|
@@ -7,6 +7,8 @@
|
||||
*/
|
||||
package org.dspace.discovery;
|
||||
|
||||
import static java.util.stream.Collectors.joining;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
@@ -751,8 +753,13 @@ public class SolrServiceImpl implements SearchService, IndexingService {
|
||||
String filterQuery = discoveryQuery.getFilterQueries().get(i);
|
||||
solrQuery.addFilterQuery(filterQuery);
|
||||
}
|
||||
if (discoveryQuery.getDSpaceObjectFilter() != null) {
|
||||
solrQuery.addFilterQuery(SearchUtils.RESOURCE_TYPE_FIELD + ":" + discoveryQuery.getDSpaceObjectFilter());
|
||||
if (discoveryQuery.getDSpaceObjectFilters() != null) {
|
||||
solrQuery.addFilterQuery(
|
||||
discoveryQuery.getDSpaceObjectFilters()
|
||||
.stream()
|
||||
.map(filter -> SearchUtils.RESOURCE_TYPE_FIELD + ":" + filter)
|
||||
.collect(joining(" OR "))
|
||||
);
|
||||
}
|
||||
|
||||
for (int i = 0; i < discoveryQuery.getFieldPresentQueries().size(); i++) {
|
||||
|
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
package org.dspace.discovery;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.solr.common.SolrInputDocument;
|
||||
import org.dspace.authorize.ResourcePolicy;
|
||||
import org.dspace.authorize.service.AuthorizeService;
|
||||
import org.dspace.content.Collection;
|
||||
import org.dspace.content.Community;
|
||||
import org.dspace.content.factory.ContentServiceFactory;
|
||||
import org.dspace.core.Constants;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.core.LogManager;
|
||||
import org.dspace.discovery.indexobject.IndexableCollection;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
/**
|
||||
* The purpose of this plugin is to index all ADD type resource policies related to collections.
|
||||
*
|
||||
* @author Mykhaylo Boychuk (at 4science.it)
|
||||
*/
|
||||
public class SolrServiceIndexCollectionSubmittersPlugin implements SolrServiceIndexPlugin {
|
||||
|
||||
private static final Logger log = org.apache.logging.log4j.LogManager
|
||||
.getLogger(SolrServiceIndexCollectionSubmittersPlugin.class);
|
||||
|
||||
@Autowired(required = true)
|
||||
protected AuthorizeService authorizeService;
|
||||
|
||||
@Override
|
||||
public void additionalIndex(Context context, IndexableObject idxObj, SolrInputDocument document) {
|
||||
if (idxObj instanceof IndexableCollection) {
|
||||
Collection col = ((IndexableCollection) idxObj).getIndexedObject();
|
||||
if (col != null) {
|
||||
try {
|
||||
String fieldValue = null;
|
||||
Community parent = (Community) ContentServiceFactory.getInstance().getDSpaceObjectService(col)
|
||||
.getParentObject(context, col);
|
||||
while (parent != null) {
|
||||
if (parent.getAdministrators() != null) {
|
||||
fieldValue = "g" + parent.getAdministrators().getID();
|
||||
document.addField("submit", fieldValue);
|
||||
}
|
||||
parent = (Community) ContentServiceFactory.getInstance().getDSpaceObjectService(parent)
|
||||
.getParentObject(context, parent);
|
||||
}
|
||||
List<ResourcePolicy> policies = authorizeService.getPoliciesActionFilter(context, col,
|
||||
Constants.ADD);
|
||||
for (ResourcePolicy resourcePolicy : policies) {
|
||||
if (resourcePolicy.getGroup() != null) {
|
||||
fieldValue = "g" + resourcePolicy.getGroup().getID();
|
||||
} else {
|
||||
fieldValue = "e" + resourcePolicy.getEPerson().getID();
|
||||
|
||||
}
|
||||
document.addField("submit", fieldValue);
|
||||
context.uncacheEntity(resourcePolicy);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
log.error(LogManager.getHeader(context, "Error while indexing resource policies",
|
||||
"Collection: (id " + col.getID() + " type " + col.getName() + ")" ));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -17,6 +17,7 @@ import org.apache.logging.log4j.Logger;
|
||||
import org.apache.solr.common.SolrInputDocument;
|
||||
import org.dspace.browse.BrowseException;
|
||||
import org.dspace.browse.BrowseIndex;
|
||||
import org.dspace.content.Collection;
|
||||
import org.dspace.content.Item;
|
||||
import org.dspace.content.MetadataValue;
|
||||
import org.dspace.content.authority.service.ChoiceAuthorityService;
|
||||
@@ -63,7 +64,7 @@ public class SolrServiceMetadataBrowseIndexingPlugin implements SolrServiceIndex
|
||||
return;
|
||||
}
|
||||
Item item = ((IndexableItem) indexableObject).getIndexedObject();
|
||||
|
||||
Collection collection = item.getOwningCollection();
|
||||
// Get the currently configured browse indexes
|
||||
BrowseIndex[] bis;
|
||||
try {
|
||||
@@ -175,7 +176,7 @@ public class SolrServiceMetadataBrowseIndexingPlugin implements SolrServiceIndex
|
||||
true);
|
||||
if (!ignorePrefered) {
|
||||
preferedLabel = choiceAuthorityService
|
||||
.getLabel(values.get(x), values.get(x).getLanguage());
|
||||
.getLabel(values.get(x), collection, values.get(x).getLanguage());
|
||||
}
|
||||
List<String> variants = null;
|
||||
|
||||
@@ -195,7 +196,7 @@ public class SolrServiceMetadataBrowseIndexingPlugin implements SolrServiceIndex
|
||||
if (!ignoreVariants) {
|
||||
variants = choiceAuthorityService
|
||||
.getVariants(
|
||||
values.get(x));
|
||||
values.get(x), collection);
|
||||
}
|
||||
|
||||
if (StringUtils
|
||||
|
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
package org.dspace.discovery.indexobject;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.dspace.core.ReloadableEntity;
|
||||
import org.dspace.discovery.IndexableObject;
|
||||
|
||||
/**
|
||||
* This class exists in order to provide a default implementation for the equals and hashCode methods.
|
||||
* Since IndexableObjects can be made multiple times for the same underlying object, we needed a more finetuned
|
||||
* equals and hashcode methods. We're simply checking that the underlying objects are equal and generating the hashcode
|
||||
* for the underlying object. This way, we'll always get a proper result when calling equals or hashcode on an
|
||||
* IndexableObject because it'll depend on the underlying object
|
||||
* @param <T> Refers to the underlying entity that is linked to this object
|
||||
* @param <PK> The type of ID that this entity uses
|
||||
*/
|
||||
public abstract class AbstractIndexableObject<T extends ReloadableEntity<PK>, PK extends Serializable>
|
||||
implements IndexableObject<T,PK> {
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
//Two IndexableObjects of the same DSpaceObject are considered equal
|
||||
if (!(obj instanceof AbstractIndexableObject)) {
|
||||
return false;
|
||||
}
|
||||
IndexableDSpaceObject other = (IndexableDSpaceObject) obj;
|
||||
return other.getIndexedObject().equals(getIndexedObject());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
//Two IndexableObjects of the same DSpaceObject are considered equal
|
||||
return getIndexedObject().hashCode();
|
||||
}
|
||||
|
||||
}
|
@@ -12,6 +12,7 @@ import java.sql.SQLException;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.collections4.ListUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.solr.client.solrj.SolrClient;
|
||||
import org.apache.solr.client.solrj.SolrServerException;
|
||||
@@ -56,7 +57,7 @@ public abstract class IndexFactoryImpl<T extends IndexableObject, S> implements
|
||||
doc.addField(SearchUtils.RESOURCE_ID_FIELD, indexableObject.getID().toString());
|
||||
|
||||
//Do any additional indexing, depends on the plugins
|
||||
for (SolrServiceIndexPlugin solrServiceIndexPlugin : solrServiceIndexPlugins) {
|
||||
for (SolrServiceIndexPlugin solrServiceIndexPlugin : ListUtils.emptyIfNull(solrServiceIndexPlugins)) {
|
||||
solrServiceIndexPlugin.additionalIndex(context, indexableObject, doc);
|
||||
}
|
||||
|
||||
|
@@ -7,7 +7,6 @@
|
||||
*/
|
||||
package org.dspace.discovery.indexobject;
|
||||
|
||||
import org.dspace.discovery.IndexableObject;
|
||||
import org.dspace.xmlworkflow.storedcomponents.ClaimedTask;
|
||||
|
||||
/**
|
||||
@@ -15,7 +14,7 @@ import org.dspace.xmlworkflow.storedcomponents.ClaimedTask;
|
||||
*
|
||||
* @author Kevin Van de Velde (kevin at atmire dot com)
|
||||
*/
|
||||
public class IndexableClaimedTask implements IndexableObject<ClaimedTask, Integer> {
|
||||
public class IndexableClaimedTask extends AbstractIndexableObject<ClaimedTask, Integer> {
|
||||
|
||||
private ClaimedTask claimedTask;
|
||||
public static final String TYPE = ClaimedTask.class.getSimpleName();
|
||||
|
@@ -10,7 +10,6 @@ package org.dspace.discovery.indexobject;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.dspace.content.DSpaceObject;
|
||||
import org.dspace.discovery.IndexableObject;
|
||||
|
||||
/**
|
||||
* DSpaceObject implementation for the IndexableObject, contains methods used by all DSpaceObject methods
|
||||
@@ -18,7 +17,7 @@ import org.dspace.discovery.IndexableObject;
|
||||
*
|
||||
* @author Kevin Van de Velde (kevin at atmire dot com)
|
||||
*/
|
||||
public abstract class IndexableDSpaceObject<T extends DSpaceObject> implements IndexableObject<T, UUID> {
|
||||
public abstract class IndexableDSpaceObject<T extends DSpaceObject> extends AbstractIndexableObject<T, UUID> {
|
||||
|
||||
private T dso;
|
||||
|
||||
@@ -40,4 +39,6 @@ public abstract class IndexableDSpaceObject<T extends DSpaceObject> implements I
|
||||
public UUID getID() {
|
||||
return dso.getID();
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -8,14 +8,13 @@
|
||||
package org.dspace.discovery.indexobject;
|
||||
|
||||
import org.dspace.content.InProgressSubmission;
|
||||
import org.dspace.discovery.IndexableObject;
|
||||
|
||||
/**
|
||||
* InProgressSubmission implementation for the IndexableObject
|
||||
* @author Kevin Van de Velde (kevin at atmire dot com)
|
||||
*/
|
||||
public abstract class IndexableInProgressSubmission<T extends InProgressSubmission>
|
||||
implements IndexableObject<T, Integer> {
|
||||
extends AbstractIndexableObject<T, Integer> {
|
||||
|
||||
protected T inProgressSubmission;
|
||||
|
||||
|
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
package org.dspace.discovery.indexobject;
|
||||
|
||||
import org.dspace.content.MetadataField;
|
||||
import org.dspace.discovery.IndexableObject;
|
||||
|
||||
/**
|
||||
* {@link MetadataField} implementation for the {@link IndexableObject}
|
||||
*
|
||||
* @author Maria Verdonck (Atmire) on 14/07/2020
|
||||
*/
|
||||
public class IndexableMetadataField extends AbstractIndexableObject<MetadataField, Integer> {
|
||||
|
||||
private MetadataField metadataField;
|
||||
public static final String TYPE = MetadataField.class.getSimpleName();
|
||||
|
||||
public IndexableMetadataField(MetadataField metadataField) {
|
||||
this.metadataField = metadataField;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType() {
|
||||
return TYPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getID() {
|
||||
return this.metadataField.getID();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MetadataField getIndexedObject() {
|
||||
return this.metadataField;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIndexedObject(MetadataField metadataField) {
|
||||
this.metadataField = metadataField;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTypeText() {
|
||||
return TYPE.toUpperCase();
|
||||
}
|
||||
}
|
@@ -7,14 +7,13 @@
|
||||
*/
|
||||
package org.dspace.discovery.indexobject;
|
||||
|
||||
import org.dspace.discovery.IndexableObject;
|
||||
import org.dspace.xmlworkflow.storedcomponents.PoolTask;
|
||||
|
||||
/**
|
||||
* PoolTask implementation for the IndexableObject
|
||||
* @author Kevin Van de Velde (kevin at atmire dot com)
|
||||
*/
|
||||
public class IndexablePoolTask implements IndexableObject<PoolTask, Integer> {
|
||||
public class IndexablePoolTask extends AbstractIndexableObject<PoolTask, Integer> {
|
||||
|
||||
public static final String TYPE = PoolTask.class.getSimpleName();
|
||||
|
||||
|
@@ -173,6 +173,8 @@ public class ItemIndexFactoryImpl extends DSpaceObjectIndexFactoryImpl<Indexable
|
||||
public void addDiscoveryFields(SolrInputDocument doc, Context context, Item item,
|
||||
List<DiscoveryConfiguration> discoveryConfigurations)
|
||||
throws SQLException, IOException {
|
||||
// use the item service to retrieve the owning collection also for inprogress submission
|
||||
Collection collection = (Collection) itemService.getParentObject(context, item);
|
||||
//Keep a list of our sort values which we added, sort values can only be added once
|
||||
List<String> sortFieldsAdded = new ArrayList<>();
|
||||
Map<String, List<DiscoverySearchFilter>> searchFilters = null;
|
||||
@@ -359,7 +361,7 @@ public class ItemIndexFactoryImpl extends DSpaceObjectIndexFactoryImpl<Indexable
|
||||
if (!ignorePrefered) {
|
||||
|
||||
preferedLabel = choiceAuthorityService
|
||||
.getLabel(meta, meta.getLanguage());
|
||||
.getLabel(meta, collection, meta.getLanguage());
|
||||
}
|
||||
|
||||
boolean ignoreVariants =
|
||||
@@ -375,7 +377,7 @@ public class ItemIndexFactoryImpl extends DSpaceObjectIndexFactoryImpl<Indexable
|
||||
true);
|
||||
if (!ignoreVariants) {
|
||||
variants = choiceAuthorityService
|
||||
.getVariants(meta);
|
||||
.getVariants(meta, collection);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
package org.dspace.discovery.indexobject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.solr.common.SolrInputDocument;
|
||||
import org.dspace.content.MetadataField;
|
||||
import org.dspace.content.service.MetadataFieldService;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.discovery.indexobject.factory.MetadataFieldIndexFactory;
|
||||
import org.dspace.eperson.Group;
|
||||
import org.dspace.eperson.factory.EPersonServiceFactory;
|
||||
import org.dspace.eperson.service.GroupService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
/**
|
||||
* Factory implementation for indexing/retrieving {@link org.dspace.content.MetadataField} items in the search core
|
||||
*
|
||||
* @author Maria Verdonck (Atmire) on 14/07/2020
|
||||
*/
|
||||
public class MetadataFieldIndexFactoryImpl extends IndexFactoryImpl<IndexableMetadataField, MetadataField>
|
||||
implements MetadataFieldIndexFactory {
|
||||
|
||||
public static final String SCHEMA_FIELD_NAME = "schema";
|
||||
public static final String ELEMENT_FIELD_NAME = "element";
|
||||
public static final String QUALIFIER_FIELD_NAME = "qualifier";
|
||||
public static final String FIELD_NAME_VARIATIONS = "fieldName";
|
||||
|
||||
protected GroupService groupService = EPersonServiceFactory.getInstance().getGroupService();
|
||||
|
||||
@Override
|
||||
public SolrInputDocument buildDocument(Context context, IndexableMetadataField indexableObject) throws SQLException,
|
||||
IOException {
|
||||
// Add the ID's, types and call the SolrServiceIndexPlugins
|
||||
final SolrInputDocument doc = super.buildDocument(context, indexableObject);
|
||||
final MetadataField metadataField = indexableObject.getIndexedObject();
|
||||
// add schema, element, qualifier and full fieldName
|
||||
addFacetIndex(doc, SCHEMA_FIELD_NAME, metadataField.getMetadataSchema().getName(),
|
||||
metadataField.getMetadataSchema().getName());
|
||||
addFacetIndex(doc, ELEMENT_FIELD_NAME, metadataField.getElement(), metadataField.getElement());
|
||||
String fieldName = metadataField.toString().replace('_', '.');
|
||||
addFacetIndex(doc, FIELD_NAME_VARIATIONS, fieldName, fieldName);
|
||||
if (StringUtils.isNotBlank(metadataField.getQualifier())) {
|
||||
addFacetIndex(doc, QUALIFIER_FIELD_NAME, metadataField.getQualifier(), metadataField.getQualifier());
|
||||
addFacetIndex(doc, FIELD_NAME_VARIATIONS, fieldName,
|
||||
metadataField.getElement() + "." + metadataField.getQualifier());
|
||||
addFacetIndex(doc, FIELD_NAME_VARIATIONS, metadataField.getQualifier(), metadataField.getQualifier());
|
||||
} else {
|
||||
addFacetIndex(doc, FIELD_NAME_VARIATIONS, metadataField.getElement(), metadataField.getElement());
|
||||
}
|
||||
addNamedResourceTypeIndex(doc, indexableObject.getTypeText());
|
||||
Group anonymousGroup = groupService.findByName(context, Group.ANONYMOUS);
|
||||
// add read permission on doc for anonymous group
|
||||
doc.addField("read", "g" + anonymousGroup.getID());
|
||||
return doc;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
private MetadataFieldService metadataFieldService;
|
||||
|
||||
@Override
|
||||
public Iterator<IndexableMetadataField> findAll(Context context) throws SQLException {
|
||||
final Iterator<MetadataField> metadataFields = metadataFieldService.findAll(context).iterator();
|
||||
return new Iterator<>() {
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return metadataFields.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IndexableMetadataField next() {
|
||||
return new IndexableMetadataField(metadataFields.next());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType() {
|
||||
return IndexableMetadataField.TYPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<IndexableMetadataField> findIndexableObject(Context context, String id) throws SQLException {
|
||||
final MetadataField metadataField = metadataFieldService.find(context, Integer.parseInt(id));
|
||||
return metadataField == null ? Optional.empty() : Optional.of(new IndexableMetadataField(metadataField));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(Object object) {
|
||||
return object instanceof MetadataField;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List getIndexableObjects(Context context, MetadataField object) {
|
||||
return Arrays.asList(new IndexableMetadataField(object));
|
||||
}
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
package org.dspace.discovery.indexobject.factory;
|
||||
|
||||
import org.dspace.content.MetadataField;
|
||||
import org.dspace.discovery.indexobject.IndexableMetadataField;
|
||||
|
||||
/**
|
||||
* Factory interface for indexing/retrieving {@link org.dspace.content.MetadataField} items in the search core
|
||||
*
|
||||
* @author Maria Verdonck (Atmire) on 14/07/2020
|
||||
*/
|
||||
public interface MetadataFieldIndexFactory extends IndexFactory<IndexableMetadataField, MetadataField> {
|
||||
}
|
@@ -38,7 +38,7 @@ public interface CitationDocumentService {
|
||||
* Citation enabled globally (all citable bitstreams will get "watermarked") modules/disseminate-citation:
|
||||
* enable_globally
|
||||
* OR
|
||||
* The container is this object is whitelist enabled.
|
||||
* The container is this object is "allow list" enabled.
|
||||
* - community: modules/disseminate-citation: enabled_communities
|
||||
* - collection: modules/disseminate-citation: enabled_collections
|
||||
* AND
|
||||
|
@@ -12,6 +12,7 @@ import java.sql.SQLException;
|
||||
import java.util.Locale;
|
||||
import javax.mail.MessagingException;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.dspace.authorize.AuthorizeException;
|
||||
import org.dspace.core.ConfigurationManager;
|
||||
@@ -22,6 +23,7 @@ import org.dspace.core.Utils;
|
||||
import org.dspace.eperson.service.AccountService;
|
||||
import org.dspace.eperson.service.EPersonService;
|
||||
import org.dspace.eperson.service.RegistrationDataService;
|
||||
import org.dspace.services.ConfigurationService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
/**
|
||||
@@ -47,6 +49,8 @@ public class AccountServiceImpl implements AccountService {
|
||||
protected EPersonService ePersonService;
|
||||
@Autowired(required = true)
|
||||
protected RegistrationDataService registrationDataService;
|
||||
@Autowired
|
||||
private ConfigurationService configurationService;
|
||||
|
||||
protected AccountServiceImpl() {
|
||||
|
||||
@@ -67,6 +71,9 @@ public class AccountServiceImpl implements AccountService {
|
||||
public void sendRegistrationInfo(Context context, String email)
|
||||
throws SQLException, IOException, MessagingException,
|
||||
AuthorizeException {
|
||||
if (!configurationService.getBooleanProperty("user.registration", true)) {
|
||||
throw new IllegalStateException("The user.registration parameter was set to false");
|
||||
}
|
||||
sendInfo(context, email, true, true);
|
||||
}
|
||||
|
||||
@@ -155,6 +162,14 @@ public class AccountServiceImpl implements AccountService {
|
||||
registrationDataService.deleteByToken(context, token);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verifyPasswordStructure(String password) {
|
||||
if (StringUtils.length(password) < 6) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* THIS IS AN INTERNAL METHOD. THE SEND PARAMETER ALLOWS IT TO BE USED FOR
|
||||
* TESTING PURPOSES.
|
||||
@@ -233,8 +248,8 @@ public class AccountServiceImpl implements AccountService {
|
||||
// Note change from "key=" to "token="
|
||||
String specialLink = new StringBuffer().append(base).append(
|
||||
base.endsWith("/") ? "" : "/").append(
|
||||
isRegister ? "register" : "forgot").append("?")
|
||||
.append("token=").append(rd.getToken())
|
||||
isRegister ? "register" : "forgot").append("/")
|
||||
.append(rd.getToken())
|
||||
.toString();
|
||||
Locale locale = context.getCurrentLocale();
|
||||
Email bean = Email.getEmail(I18nUtil.getEmailFilename(locale, isRegister ? "register"
|
||||
|
@@ -141,7 +141,7 @@ public class EPerson extends DSpaceObject implements DSpaceObjectLegacySupport {
|
||||
return false;
|
||||
}
|
||||
final EPerson other = (EPerson) obj;
|
||||
if (this.getID() != other.getID()) {
|
||||
if (!this.getID().equals(other.getID())) {
|
||||
return false;
|
||||
}
|
||||
if (!StringUtils.equals(this.getEmail(), other.getEmail())) {
|
||||
|
@@ -23,7 +23,9 @@ import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.dspace.authorize.AuthorizeConfiguration;
|
||||
import org.dspace.authorize.AuthorizeException;
|
||||
import org.dspace.authorize.ResourcePolicy;
|
||||
import org.dspace.authorize.service.AuthorizeService;
|
||||
import org.dspace.authorize.service.ResourcePolicyService;
|
||||
import org.dspace.content.Collection;
|
||||
import org.dspace.content.DSpaceObject;
|
||||
import org.dspace.content.DSpaceObjectServiceImpl;
|
||||
@@ -76,6 +78,8 @@ public class GroupServiceImpl extends DSpaceObjectServiceImpl<Group> implements
|
||||
|
||||
@Autowired(required = true)
|
||||
protected AuthorizeService authorizeService;
|
||||
@Autowired(required = true)
|
||||
protected ResourcePolicyService resourcePolicyService;
|
||||
|
||||
protected GroupServiceImpl() {
|
||||
super();
|
||||
@@ -185,7 +189,8 @@ public class GroupServiceImpl extends DSpaceObjectServiceImpl<Group> implements
|
||||
return false;
|
||||
|
||||
// special, everyone is member of group 0 (anonymous)
|
||||
} else if (StringUtils.equals(group.getName(), Group.ANONYMOUS)) {
|
||||
} else if (StringUtils.equals(group.getName(), Group.ANONYMOUS) ||
|
||||
isParentOf(context, group, findByName(context, Group.ANONYMOUS))) {
|
||||
return true;
|
||||
|
||||
} else {
|
||||
@@ -654,6 +659,23 @@ public class GroupServiceImpl extends DSpaceObjectServiceImpl<Group> implements
|
||||
return collectionService.getParentObject(context, collection);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (AuthorizeConfiguration.canCollectionAdminManagePolicies()
|
||||
|| AuthorizeConfiguration.canCommunityAdminManagePolicies()
|
||||
|| AuthorizeConfiguration.canCommunityAdminManageCollectionWorkflows()) {
|
||||
List<Group> groups = new ArrayList<Group>();
|
||||
groups.add(group);
|
||||
List<ResourcePolicy> policies = resourcePolicyService.find(context, null, groups,
|
||||
Constants.DEFAULT_ITEM_READ, Constants.COLLECTION);
|
||||
if (policies.size() > 0) {
|
||||
return policies.get(0).getdSpaceObject();
|
||||
}
|
||||
policies = resourcePolicyService.find(context, null, groups,
|
||||
Constants.DEFAULT_BITSTREAM_READ, Constants.COLLECTION);
|
||||
if (policies.size() > 0) {
|
||||
return policies.get(0).getdSpaceObject();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (AuthorizeConfiguration.canCommunityAdminManageAdminGroup()) {
|
||||
|
@@ -46,4 +46,11 @@ public interface AccountService {
|
||||
|
||||
public void deleteToken(Context context, String token)
|
||||
throws SQLException;
|
||||
|
||||
/**
|
||||
* This method verifies that a certain String adheres to the password rules for DSpace
|
||||
* @param password The String to be checked
|
||||
* @return A boolean indicating whether or not the given String adheres to the password rules
|
||||
*/
|
||||
public boolean verifyPasswordStructure(String password);
|
||||
}
|
||||
|
162
dspace-api/src/main/java/org/dspace/external/provider/impl/LiveImportDataProvider.java
vendored
Normal file
162
dspace-api/src/main/java/org/dspace/external/provider/impl/LiveImportDataProvider.java
vendored
Normal file
@@ -0,0 +1,162 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
package org.dspace.external.provider.impl;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.dspace.content.dto.MetadataValueDTO;
|
||||
import org.dspace.external.model.ExternalDataObject;
|
||||
import org.dspace.external.provider.ExternalDataProvider;
|
||||
import org.dspace.importer.external.datamodel.ImportRecord;
|
||||
import org.dspace.importer.external.exception.MetadataSourceException;
|
||||
import org.dspace.importer.external.metadatamapping.MetadatumDTO;
|
||||
import org.dspace.importer.external.service.components.QuerySource;
|
||||
|
||||
/**
|
||||
* This class allows to configure a Live Import Provider as an External Data Provider
|
||||
*
|
||||
* @author Andrea Bollini (andrea.bollini at 4science.it)
|
||||
*
|
||||
*/
|
||||
public class LiveImportDataProvider implements ExternalDataProvider {
|
||||
/**
|
||||
* The {@link QuerySource} live import provider
|
||||
*/
|
||||
private QuerySource querySource;
|
||||
|
||||
/**
|
||||
* An unique human readable identifier for this provider
|
||||
*/
|
||||
private String sourceIdentifier;
|
||||
|
||||
private String recordIdMetadata;
|
||||
|
||||
private String displayMetadata = "dc.title";
|
||||
|
||||
@Override
|
||||
public String getSourceIdentifier() {
|
||||
return sourceIdentifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method set the SourceIdentifier for the ExternalDataProvider
|
||||
* @param sourceIdentifier The UNIQUE sourceIdentifier to be set on any LiveImport data provider
|
||||
*/
|
||||
public void setSourceIdentifier(String sourceIdentifier) {
|
||||
this.sourceIdentifier = sourceIdentifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method set the MetadataSource for the ExternalDataProvider
|
||||
* @param metadataSource {@link org.dspace.importer.external.service.components.MetadataSource} implementation used to process the input data
|
||||
*/
|
||||
public void setMetadataSource(QuerySource querySource) {
|
||||
this.querySource = querySource;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method set dublin core identifier to use as metadata id
|
||||
* @param recordIdMetadata dublin core identifier to use as metadata id
|
||||
*/
|
||||
public void setRecordIdMetadata(String recordIdMetadata) {
|
||||
this.recordIdMetadata = recordIdMetadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method set the dublin core identifier to display the title
|
||||
* @param displayMetadata metadata to use as title
|
||||
*/
|
||||
public void setDisplayMetadata(String displayMetadata) {
|
||||
this.displayMetadata = displayMetadata;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ExternalDataObject> getExternalDataObject(String id) {
|
||||
try {
|
||||
ExternalDataObject externalDataObject = getExternalDataObject(querySource.getRecord(id));
|
||||
return Optional.of(externalDataObject);
|
||||
} catch (MetadataSourceException e) {
|
||||
throw new RuntimeException(
|
||||
"The live import provider " + querySource.getImportSource() + " throws an exception", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ExternalDataObject> searchExternalDataObjects(String query, int start, int limit) {
|
||||
Collection<ImportRecord> records;
|
||||
try {
|
||||
records = querySource.getRecords(query, start, limit);
|
||||
return records.stream().map(r -> getExternalDataObject(r)).collect(Collectors.toList());
|
||||
} catch (MetadataSourceException e) {
|
||||
throw new RuntimeException(
|
||||
"The live import provider " + querySource.getImportSource() + " throws an exception", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(String source) {
|
||||
return StringUtils.equalsIgnoreCase(sourceIdentifier, source);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNumberOfResults(String query) {
|
||||
try {
|
||||
return querySource.getRecordsCount(query);
|
||||
} catch (MetadataSourceException e) {
|
||||
throw new RuntimeException(
|
||||
"The live import provider " + querySource.getImportSource() + " throws an exception", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method to convert an ImportRecord to an ExternalDataObject
|
||||
*
|
||||
* FIXME it would be useful to remove ImportRecord at all in favor of the
|
||||
* ExternalDataObject
|
||||
*
|
||||
* @param record
|
||||
* @return
|
||||
*/
|
||||
private ExternalDataObject getExternalDataObject(ImportRecord record) {
|
||||
//return 400 if no record were found
|
||||
if (record == null) {
|
||||
throw new IllegalArgumentException("No record found for query or id");
|
||||
}
|
||||
ExternalDataObject externalDataObject = new ExternalDataObject(sourceIdentifier);
|
||||
String id = getFirstValue(record, recordIdMetadata);
|
||||
String display = getFirstValue(record, displayMetadata);
|
||||
externalDataObject.setId(id);
|
||||
externalDataObject.setDisplayValue(display);
|
||||
externalDataObject.setValue(display);
|
||||
for (MetadatumDTO dto : record.getValueList()) {
|
||||
// FIXME it would be useful to remove MetadatumDTO in favor of MetadataValueDTO
|
||||
MetadataValueDTO mvDTO = new MetadataValueDTO();
|
||||
mvDTO.setSchema(dto.getSchema());
|
||||
mvDTO.setElement(dto.getElement());
|
||||
mvDTO.setQualifier(dto.getQualifier());
|
||||
mvDTO.setValue(dto.getValue());
|
||||
externalDataObject.addMetadata(mvDTO);
|
||||
}
|
||||
return externalDataObject;
|
||||
}
|
||||
|
||||
private String getFirstValue(ImportRecord record, String metadata) {
|
||||
String id = null;
|
||||
String[] split = StringUtils.split(metadata, ".", 3);
|
||||
Collection<MetadatumDTO> values = record.getValue(split[0], split[1], split.length == 3 ? split[2] : null);
|
||||
if (!values.isEmpty()) {
|
||||
id = (values.iterator().next().getValue());
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user