mirror of
https://github.com/DSpace/DSpace.git
synced 2025-10-07 10:04:21 +00:00
Git merge https://github.com/atmire/DSpace.git / master
* Conflict file: RelationshipRestRepositoryIT
This commit is contained in:
29
.codecov.yml
Normal file
29
.codecov.yml
Normal file
@@ -0,0 +1,29 @@
|
||||
# DSpace configuration for Codecov.io coverage reports
|
||||
# These override the default YAML settings at
|
||||
# https://docs.codecov.io/docs/codecov-yaml#section-default-yaml
|
||||
# Can be validated via instructions at:
|
||||
# https://docs.codecov.io/docs/codecov-yaml#validate-your-repository-yaml
|
||||
|
||||
# Settings related to code coverage analysis
|
||||
coverage:
|
||||
status:
|
||||
# Configuration for project-level checks. This checks how the PR changes overall coverage.
|
||||
project:
|
||||
default:
|
||||
# For each PR, auto compare coverage to previous commit.
|
||||
# Require that overall (project) coverage does NOT drop more than 0.5%
|
||||
target: auto
|
||||
threshold: 0.5%
|
||||
# Configuration for patch-level checks. This checks the relative coverage of the new PR code ONLY.
|
||||
patch:
|
||||
default:
|
||||
# For each PR, make sure the coverage of the new code is within 1% of current overall coverage.
|
||||
# We let 'patch' be more lenient as we only require *project* coverage to not drop significantly.
|
||||
target: auto
|
||||
threshold: 1%
|
||||
|
||||
# Turn PR comments "off". This feature adds the code coverage summary as a
|
||||
# comment on each PR. See https://docs.codecov.io/docs/pull-request-comments
|
||||
# However, this same info is available from the Codecov checks in the PR's
|
||||
# "Checks" tab in GitHub. So, the comment is unnecessary.
|
||||
comment: false
|
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
|
16
.github/pull_request_template.md
vendored
16
.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:_
|
||||
* Fixes #[issue-number]
|
||||
* Related to [REST Contract](https://github.com/DSpace/Rest7Contract)
|
||||
|
||||
## Description
|
||||
Short summary of changes (1-2 sentences).
|
||||
@@ -20,11 +19,8 @@ List of changes in this PR:
|
||||
_This checklist provides a reminder of what we are going to look for when reviewing your PR. You need not complete this checklist prior to creating your PR (draft PRs are always welcome). If you are unsure about an item in the checklist, don't hesitate to ask. We're here to help!_
|
||||
|
||||
- [ ] My PR is small in size (e.g. less than 1,000 lines of code, not including comments & integration tests). Exceptions may be made if previously agreed upon.
|
||||
- [ ] My PR passes Checkstyle validation based on the [Code Style Guide](https://wiki.lyrasis.org/display/DSPACE/Code+Style+Guide)
|
||||
- [ ] My PR passes Checkstyle validation based on the [Code Style Guide](https://wiki.lyrasis.org/display/DSPACE/Code+Style+Guide).
|
||||
- [ ] My PR includes Javadoc for _all new (or modified) public methods and classes_. It also includes Javadoc for large or complex private methods.
|
||||
- [ ] My PR passes all tests and includes new/updated Unit or Integration Tests for any bug fixes, improvements or new features. A few reminders about what constitutes good tests:
|
||||
* Include tests for different user types, including: (1) Anonymous user, (2) Logged in user (non-admin), and (3) Administrator.
|
||||
* Include tests for known error scenarios and error codes (e.g. `400 Bad Request`, `401 Unauthorized`, `403 Forbidden`, `404 Not Found`, etc)
|
||||
* For bug fixes, include a test that reproduces the bug and proves it is fixed. For clarity, it may be useful to provide the test in a separate commit from the bug fix.
|
||||
- [ ] If my PR includes new, third-party dependencies (in any `pom.xml`), I've made sure their licenses align with the [DSpace BSD License](https://github.com/DSpace/DSpace/blob/master/LICENSE) based on the [Licensing of Contributions](https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines#CodeContributionGuidelines-LicensingofContributions) documentation.
|
||||
- [ ] My PR passes all tests and includes new/updated Unit or Integration Tests based on the [Code Testing Guide](https://wiki.lyrasis.org/display/DSPACE/Code+Testing+Guide).
|
||||
- [ ] If my PR includes new, third-party dependencies (in any `pom.xml`), I've made sure their licenses align with the [DSpace BSD License](https://github.com/DSpace/DSpace/blob/main/LICENSE) based on the [Licensing of Contributions](https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines#CodeContributionGuidelines-LicensingofContributions) documentation.
|
||||
- [ ] If my PR modifies the REST API, I've linked to the REST Contract page (or open PR) related to this change.
|
||||
|
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
|
77
.travis.yml
77
.travis.yml
@@ -1,46 +1,55 @@
|
||||
# DSpace's Travis CI Configuration
|
||||
# Builds: https://travis-ci.com/github/DSpace/DSpace
|
||||
# Travis configuration guide/validation: https://config.travis-ci.com/explore
|
||||
language: java
|
||||
sudo: false
|
||||
# TODO: Upgrade to Bionic
|
||||
dist: trusty
|
||||
|
||||
env:
|
||||
# Give Maven 1GB of memory to work with
|
||||
- MAVEN_OPTS=-Xmx1024M
|
||||
os: linux
|
||||
|
||||
jdk:
|
||||
# DS-3384 Oracle JDK has DocLint enabled by default.
|
||||
# Let's use this to catch any newly introduced DocLint issues.
|
||||
- oraclejdk11
|
||||
|
||||
## Should we run into any problems with oraclejdk8 on Travis, we may try the following workaround.
|
||||
## https://docs.travis-ci.com/user/languages/java#Testing-Against-Multiple-JDKs
|
||||
## https://github.com/travis-ci/travis-ci/issues/3259#issuecomment-130860338
|
||||
#addons:
|
||||
# apt:
|
||||
# packages:
|
||||
# - oracle-java8-installer
|
||||
# Define global environment variables (shared across all jobs)
|
||||
env:
|
||||
global:
|
||||
# Suppress all Maven "downloading" messages in Travis logs (see https://stackoverflow.com/a/35653426)
|
||||
# This also slightly speeds builds in Travis, as there is less logging
|
||||
- HIDE_MAVEN_DOWNLOADS="-Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn"
|
||||
# Give Maven 1GB of memory to work with
|
||||
- MAVEN_OPTS="-Xmx1024M $HIDE_MAVEN_DOWNLOADS"
|
||||
# Maven options which will skip ALL code validation checks. Includes skipping:
|
||||
# - enforcer.skip => Skip maven-enforcer-plugin rules
|
||||
# - checkstyle.skip => Skip all checkstyle checks by maven-checkstyle-plugin
|
||||
# - license.skip => Skip all license header checks by license-maven-plugin
|
||||
# - xml.skip => Skip all XML/XSLT validation by xml-maven-plugin
|
||||
# (Useful for builds which don't need to repeat code checks)
|
||||
- SKIP_CODE_CHECKS="-Denforcer.skip=true -Dcheckstyle.skip=true -Dlicense.skip=true -Dxml.skip=true"
|
||||
|
||||
before_install:
|
||||
# Remove outdated settings.xml from Travis builds. Workaround for https://github.com/travis-ci/travis-ci/issues/4629
|
||||
- rm ~/.m2/settings.xml
|
||||
# Create two jobs to run Unit & Integration tests in parallel.
|
||||
# These jobs only differ in the TEST_FLAGS defined below,
|
||||
# and otherwise share all the other configs in this file
|
||||
jobs:
|
||||
include:
|
||||
- name: "Run Unit Tests & Check Code"
|
||||
# NOTE: unit tests include deprecated REST API v6 (as it has unit tests)
|
||||
env: TEST_FLAGS="-DskipUnitTests=false -Pdspace-rest"
|
||||
- name: "Run Integration Tests"
|
||||
# NOTE: skips code checks, as they are already done by Unit Test job
|
||||
env: TEST_FLAGS="-DskipIntegrationTests=false $SKIP_CODE_CHECKS"
|
||||
|
||||
# Skip install stage, as we'll do it below
|
||||
install: "echo 'Skipping install stage, dependencies will be downloaded during build and test stages.'"
|
||||
# Skip 'install' process to save time. We build/install/test all at once in "script" below.
|
||||
install: skip
|
||||
|
||||
# Build DSpace and run both Unit and Integration Tests
|
||||
script:
|
||||
# Summary of flags used (below):
|
||||
# license:check => Validate all source code license headers
|
||||
# -Dmaven.test.skip=false => Enable DSpace Unit Tests
|
||||
# -DskipITs=false => Enable DSpace Integration Tests
|
||||
# -Pdspace-rest => Enable optional dspace-rest module as part of build
|
||||
# -P !assembly => Skip assembly of "dspace-installer" directory (as it can be memory intensive)
|
||||
# -B => Maven batch/non-interactive mode (recommended for CI)
|
||||
# -V => Display Maven version info before build
|
||||
# -Dsurefire.rerunFailingTestsCount=2 => try again for flakey tests, and keep track of/report on number of retries
|
||||
- "mvn clean install license:check -Dmaven.test.skip=false -DskipITs=false -Pdspace-rest -P !assembly -B -V -Dsurefire.rerunFailingTestsCount=2"
|
||||
# Build DSpace and run configured tests (see 'jobs' above)
|
||||
# Notes on flags used:
|
||||
# -B => Maven batch/non-interactive mode (recommended for CI)
|
||||
# -V => Display Maven version info before build
|
||||
# -P-assembly => Disable build of dspace-installer in [src]/dspace/, as it can be memory intensive
|
||||
# -Pcoverage-report => Enable aggregate code coverage report (across all modules) via JaCoCo
|
||||
script: mvn install -B -V -P-assembly -Pcoverage-report $TEST_FLAGS
|
||||
|
||||
# After a successful build and test (see 'script'), send code coverage reports to coveralls.io
|
||||
# These code coverage reports are generated by jacoco-maven-plugin (during test process above).
|
||||
after_success:
|
||||
# Run "verify", enabling the "coveralls" profile. This sends our reports to coveralls.io (see coveralls-maven-plugin)
|
||||
- "cd dspace && mvn verify -P coveralls"
|
||||
# After a successful build and test (see 'script'), send aggregate code coverage reports
|
||||
# (generated by -Pcoverage-report above) to CodeCov.io
|
||||
after_success: bash <(curl -s https://codecov.io/bash)
|
||||
|
@@ -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
|
||||
|
69
README.md
69
README.md
@@ -1,24 +1,24 @@
|
||||
|
||||
# DSpace
|
||||
|
||||
[](https://travis-ci.org/DSpace/DSpace)
|
||||
[](https://travis-ci.com/DSpace/DSpace)
|
||||
|
||||
[DSpace Documentation](https://wiki.duraspace.org/display/DSDOC/) |
|
||||
[DSpace Documentation](https://wiki.lyrasis.org/display/DSDOC/) |
|
||||
[DSpace Releases](https://github.com/DSpace/DSpace/releases) |
|
||||
[DSpace Wiki](https://wiki.duraspace.org/display/DSPACE/Home) |
|
||||
[Support](https://wiki.duraspace.org/display/DSPACE/Support)
|
||||
[DSpace Wiki](https://wiki.lyrasis.org/display/DSPACE/Home) |
|
||||
[Support](https://wiki.lyrasis.org/display/DSPACE/Support)
|
||||
|
||||
DSpace open source software is a turnkey repository application used by more than
|
||||
2,000 organizations and institutions worldwide to provide durable access to digital resources.
|
||||
For more information, visit http://www.dspace.org/
|
||||
|
||||
***
|
||||
:warning: **Work on DSpace 7 has begun on our `master` branch.** This means that there is temporarily NO user interface on this `master` branch. DSpace 7 will feature a new, unified [Angular](https://angular.io/) user interface, along with an enhanced, rebuilt REST API. The latest status of this work can be found on the [DSpace 7 UI Working Group](https://wiki.duraspace.org/display/DSPACE/DSpace+7+UI+Working+Group) page. Additionally, the codebases can be found in the following places:
|
||||
* DSpace 7 REST API work is occurring on the [`master` branch](https://github.com/DSpace/DSpace/tree/master/dspace-server-webapp) of this repository.
|
||||
* The REST Contract is being documented at https://github.com/DSpace/Rest7Contract
|
||||
:warning: **Work on DSpace 7 has begun on our `main` branch.** This means that there is NO user interface on this `main` branch. DSpace 7 will feature a new, unified [Angular](https://angular.io/) user interface, along with an enhanced, rebuilt REST API. The latest status of this work can be found on the [DSpace 7 Working Group](https://wiki.lyrasis.org/display/DSPACE/DSpace+7+Working+Group) page. Additionally, the codebases can be found in the following places:
|
||||
* DSpace 7 REST API work is occurring on the [`main` branch](https://github.com/DSpace/DSpace/tree/main/dspace-server-webapp) of this repository.
|
||||
* The REST Contract is at https://github.com/DSpace/Rest7Contract
|
||||
* DSpace 7 Angular UI work is occurring at https://github.com/DSpace/dspace-angular
|
||||
|
||||
**If you would like to get involved in our DSpace 7 development effort, we welcome new contributors.** Just join one of our meetings or get in touch via Slack. See the [DSpace 7 UI Working Group](https://wiki.duraspace.org/display/DSPACE/DSpace+7+UI+Working+Group) wiki page for more info.
|
||||
**If you would like to get involved in our DSpace 7 development effort, we welcome new contributors.** Just join one of our meetings or get in touch via Slack. See the [DSpace 7 Working Group](https://wiki.lyrasis.org/display/DSPACE/DSpace+7+Working+Group) wiki page for more info.
|
||||
|
||||
**If you are looking for the ongoing maintenance work for DSpace 6 (or prior releases)**, you can find that work on the corresponding maintenance branch (e.g. [`dspace-6_x`](https://github.com/DSpace/DSpace/tree/dspace-6_x)) in this repository.
|
||||
***
|
||||
@@ -31,10 +31,10 @@ Past releases are all available via GitHub at https://github.com/DSpace/DSpace/r
|
||||
|
||||
## Documentation / Installation
|
||||
|
||||
Documentation for each release may be viewed online or downloaded via our [Documentation Wiki](https://wiki.duraspace.org/display/DSDOC/).
|
||||
Documentation for each release may be viewed online or downloaded via our [Documentation Wiki](https://wiki.lyrasis.org/display/DSDOC/).
|
||||
|
||||
The latest DSpace Installation instructions are available at:
|
||||
https://wiki.duraspace.org/display/DSDOC6x/Installing+DSpace
|
||||
https://wiki.lyrasis.org/display/DSDOC6x/Installing+DSpace
|
||||
|
||||
Please be aware that, as a Java web application, DSpace requires a database (PostgreSQL or Oracle)
|
||||
and a servlet container (usually Tomcat) in order to function.
|
||||
@@ -49,14 +49,14 @@ DSpace is a community built and supported project. We do not have a centralized
|
||||
but have a dedicated group of volunteers who help us improve the software, documentation, resources, etc.
|
||||
|
||||
We welcome contributions of any type. Here's a few basic guides that provide suggestions for contributing to DSpace:
|
||||
* [How to Contribute to DSpace](https://wiki.duraspace.org/display/DSPACE/How+to+Contribute+to+DSpace): How to contribute in general (via code, documentation, bug reports, expertise, etc)
|
||||
* [Code Contribution Guidelines](https://wiki.duraspace.org/display/DSPACE/Code+Contribution+Guidelines): How to give back code or contribute features, bug fixes, etc.
|
||||
* [DSpace Community Advisory Team (DCAT)](https://wiki.duraspace.org/display/cmtygp/DSpace+Community+Advisory+Team): If you are not a developer, we also have an interest group specifically for repository managers. The DCAT group meets virtually, once a month, and sends open invitations to join their meetings via the [DCAT mailing list](https://groups.google.com/d/forum/DSpaceCommunityAdvisoryTeam).
|
||||
* [How to Contribute to DSpace](https://wiki.lyrasis.org/display/DSPACE/How+to+Contribute+to+DSpace): How to contribute in general (via code, documentation, bug reports, expertise, etc)
|
||||
* [Code Contribution Guidelines](https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines): How to give back code or contribute features, bug fixes, etc.
|
||||
* [DSpace Community Advisory Team (DCAT)](https://wiki.lyrasis.org/display/cmtygp/DSpace+Community+Advisory+Team): If you are not a developer, we also have an interest group specifically for repository managers. The DCAT group meets virtually, once a month, and sends open invitations to join their meetings via the [DCAT mailing list](https://groups.google.com/d/forum/DSpaceCommunityAdvisoryTeam).
|
||||
|
||||
We also encourage GitHub Pull Requests (PRs) at any time. Please see our [Development with Git](https://wiki.duraspace.org/display/DSPACE/Development+with+Git) guide for more info.
|
||||
We also encourage GitHub Pull Requests (PRs) at any time. Please see our [Development with Git](https://wiki.lyrasis.org/display/DSPACE/Development+with+Git) guide for more info.
|
||||
|
||||
In addition, a listing of all known contributors to DSpace software can be
|
||||
found online at: https://wiki.duraspace.org/display/DSPACE/DSpaceContributors
|
||||
found online at: https://wiki.lyrasis.org/display/DSPACE/DSpaceContributors
|
||||
|
||||
## Getting Help
|
||||
|
||||
@@ -64,12 +64,12 @@ DSpace provides public mailing lists where you can post questions or raise topic
|
||||
We welcome everyone to participate in these lists:
|
||||
|
||||
* [dspace-community@googlegroups.com](https://groups.google.com/d/forum/dspace-community) : General discussion about DSpace platform, announcements, sharing of best practices
|
||||
* [dspace-tech@googlegroups.com](https://groups.google.com/d/forum/dspace-tech) : Technical support mailing list. See also our guide for [How to troubleshoot an error](https://wiki.duraspace.org/display/DSPACE/Troubleshoot+an+error).
|
||||
* [dspace-tech@googlegroups.com](https://groups.google.com/d/forum/dspace-tech) : Technical support mailing list. See also our guide for [How to troubleshoot an error](https://wiki.lyrasis.org/display/DSPACE/Troubleshoot+an+error).
|
||||
* [dspace-devel@googlegroups.com](https://groups.google.com/d/forum/dspace-devel) : Developers / Development mailing list
|
||||
|
||||
Great Q&A is also available under the [DSpace tag on Stackoverflow](http://stackoverflow.com/questions/tagged/dspace)
|
||||
|
||||
Additional support options are listed at https://wiki.duraspace.org/display/DSPACE/Support
|
||||
Additional support options are at https://wiki.lyrasis.org/display/DSPACE/Support
|
||||
|
||||
DSpace also has an active service provider network. If you'd rather hire a service provider to
|
||||
install, upgrade, customize or host DSpace, then we recommend getting in touch with one of our
|
||||
@@ -77,47 +77,46 @@ install, upgrade, customize or host DSpace, then we recommend getting in touch w
|
||||
|
||||
## Issue Tracker
|
||||
|
||||
The DSpace Issue Tracker can be found at: https://jira.duraspace.org/projects/DS/summary
|
||||
DSpace uses GitHub to track issues:
|
||||
* Backend (REST API) issues: https://github.com/DSpace/DSpace/issues
|
||||
* Frontend (User Interface) issues: https://github.com/DSpace/dspace-angular/issues
|
||||
|
||||
## Testing
|
||||
|
||||
### Running Tests
|
||||
|
||||
By default, in DSpace, Unit Tests and Integration Tests are disabled. However, they are
|
||||
run automatically by [Travis CI](https://travis-ci.org/DSpace/DSpace/) for all Pull Requests and code commits.
|
||||
run automatically by [Travis CI](https://travis-ci.com/DSpace/DSpace/) for all Pull Requests and code commits.
|
||||
|
||||
* How to run both Unit Tests (via `maven-surefire-plugin`) and Integration Tests (via `maven-failsafe-plugin`):
|
||||
```
|
||||
# NOTE: while "mvn test" runs Unit Tests,
|
||||
# Integration Tests only run for "verify" or "install" phases
|
||||
mvn clean install -Dmaven.test.skip=false -DskipITs=false
|
||||
mvn install -DskipUnitTests=false -DskipIntegrationTests=false
|
||||
```
|
||||
* How to run just Unit Tests:
|
||||
* How to run _only_ Unit Tests:
|
||||
```
|
||||
mvn clean test -Dmaven.test.skip=false
|
||||
mvn test -DskipUnitTests=false
|
||||
```
|
||||
* How to run a *single* Unit Test
|
||||
```
|
||||
# Run all tests in a specific test class
|
||||
# NOTE: testClassName is just the class name, do not include package
|
||||
mvn clean test -Dmaven.test.skip=false -Dtest=[testClassName]
|
||||
# NOTE: failIfNoTests=false is required to skip tests in other modules
|
||||
mvn test -DskipUnitTests=false -Dtest=[full.package.testClassName] -DfailIfNoTests=false
|
||||
|
||||
# Run one test method in a specific test class
|
||||
mvn clean test -Dmaven.test.skip=false -Dtest=[testClassName]#[testMethodName]
|
||||
mvn test -DskipUnitTests=false -Dtest=[full.package.testClassName]#[testMethodName] -DfailIfNoTests=false
|
||||
```
|
||||
* How to run Integration Tests (requires running Unit tests too)
|
||||
* How to run _only_ Integration Tests
|
||||
```
|
||||
mvn clean verify -Dmaven.test.skip=false -DskipITs=false
|
||||
mvn install -DskipIntegrationTests=false
|
||||
```
|
||||
* How to run a *single* Integration Test (requires running Unit tests too)
|
||||
* How to run a *single* Integration Test
|
||||
```
|
||||
# Run all integration tests in a specific test class
|
||||
# NOTE: Integration Tests only run for "verify" or "install" phases
|
||||
# NOTE: testClassName is just the class name, do not include package
|
||||
mvn clean verify -Dmaven.test.skip=false -DskipITs=false -Dit.test=[testClassName]
|
||||
# NOTE: failIfNoTests=false is required to skip tests in other modules
|
||||
mvn install -DskipIntegrationTests=false -Dit.test=[full.package.testClassName] -DfailIfNoTests=false
|
||||
|
||||
# Run one test method in a specific test class
|
||||
mvn clean verify -Dmaven.test.skip=false -DskipITs=false -Dit.test=[testClassName]#[testMethodName]
|
||||
mvn install -DskipIntegrationTests=false -Dit.test=[full.package.testClassName]#[testMethodName] -DfailIfNoTests=false
|
||||
```
|
||||
* How to run only tests of a specific DSpace module
|
||||
```
|
||||
@@ -133,4 +132,4 @@ run automatically by [Travis CI](https://travis-ci.org/DSpace/DSpace/) for all P
|
||||
## License
|
||||
|
||||
DSpace source code is freely available under a standard [BSD 3-Clause license](https://opensource.org/licenses/BSD-3-Clause).
|
||||
The full license is available at http://www.dspace.org/license/
|
||||
The full license is available in the [LICENSE](LICENSE) file or online at http://www.dspace.org/license/
|
||||
|
@@ -12,7 +12,7 @@
|
||||
<parent>
|
||||
<groupId>org.dspace</groupId>
|
||||
<artifactId>dspace-parent</artifactId>
|
||||
<version>7.0-SNAPSHOT</version>
|
||||
<version>7.0-beta5-SNAPSHOT</version>
|
||||
<relativePath>..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -98,20 +98,6 @@
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<!-- Verify OS license headers for all source code files -->
|
||||
<plugin>
|
||||
<groupId>com.mycila</groupId>
|
||||
<artifactId>license-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<excludes>
|
||||
<exclude>**/src/test/resources/**</exclude>
|
||||
<exclude>**/src/test/data/**</exclude>
|
||||
<exclude>**/.gitignore</exclude>
|
||||
<exclude>**/src/main/resources/rebel.xml</exclude>
|
||||
<exclude>src/test/data/dspaceFolder/config/spiders/**</exclude>
|
||||
</excludes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
@@ -141,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)
|
||||
@@ -244,7 +157,81 @@
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<!-- Run Unit Testing! This plugin just kicks off the tests (when enabled). -->
|
||||
<plugin>
|
||||
<groupId>com.mycila</groupId>
|
||||
<artifactId>license-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<excludes>
|
||||
<exclude>src/test/resources/**</exclude>
|
||||
<exclude>src/test/data/**</exclude>
|
||||
<!-- Ignore license header requirements on Flyway upgrade scripts -->
|
||||
<exclude>src/main/resources/org/dspace/storage/rdbms/flywayupgrade/**</exclude>
|
||||
</excludes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
</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>
|
||||
@@ -255,11 +242,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>
|
||||
@@ -269,12 +301,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>
|
||||
|
||||
@@ -305,19 +337,25 @@
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.dspace</groupId>
|
||||
<groupId>net.handle</groupId>
|
||||
<artifactId>handle</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.aggregate</groupId>
|
||||
<artifactId>jetty-all</artifactId>
|
||||
<groupId>net.cnri</groupId>
|
||||
<artifactId>cnri-servlet-container</artifactId>
|
||||
<exclusions>
|
||||
<!-- Newer versions provided in our parent POM -->
|
||||
<exclusion>
|
||||
<artifactId>javax.servlet</artifactId>
|
||||
<groupId>org.eclipse.jetty.orbit</groupId>
|
||||
<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>
|
||||
<artifactId>jetty-server</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.dspace</groupId>
|
||||
<artifactId>jargon</artifactId>
|
||||
@@ -331,6 +369,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>
|
||||
@@ -487,9 +537,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>
|
||||
<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>
|
||||
@@ -547,7 +752,7 @@
|
||||
<dependency>
|
||||
<groupId>org.flywaydb</groupId>
|
||||
<artifactId>flyway-core</artifactId>
|
||||
<version>4.0.3</version>
|
||||
<version>6.5.5</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Google Analytics -->
|
||||
@@ -571,6 +776,7 @@
|
||||
<groupId>com.google.oauth-client</groupId>
|
||||
<artifactId>google-oauth-client</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- FindBugs -->
|
||||
<dependency>
|
||||
<groupId>com.google.code.findbugs</groupId>
|
||||
@@ -580,6 +786,7 @@
|
||||
<groupId>com.google.code.findbugs</groupId>
|
||||
<artifactId>annotations</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>joda-time</groupId>
|
||||
<artifactId>joda-time</artifactId>
|
||||
@@ -670,7 +877,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();
|
||||
if (!change) {
|
||||
validateExpressedRelations(c);
|
||||
}
|
||||
return changes;
|
||||
}
|
||||
@@ -487,7 +628,7 @@ public class MetadataImport {
|
||||
* @throws AuthorizeException if there is an authorization problem with permissions
|
||||
* @throws MetadataImportException custom exception for error handling within metadataimport
|
||||
*/
|
||||
protected void compareAndUpdate(Item item, String[] fromCSV, boolean change,
|
||||
protected void compareAndUpdate(Context c, Item item, String[] fromCSV, boolean change,
|
||||
String md, BulkEditChange changes, DSpaceCSVLine line)
|
||||
throws SQLException, AuthorizeException, MetadataImportException {
|
||||
// Log what metadata element we're looking at
|
||||
@@ -565,7 +706,7 @@ public class MetadataImport {
|
||||
// Compare from current->csv
|
||||
for (int v = 0; v < fromCSV.length; v++) {
|
||||
String value = fromCSV[v];
|
||||
BulkEditMetadataValue dcv = getBulkEditValueFromCSV(language, schema, element, qualifier, value,
|
||||
BulkEditMetadataValue dcv = getBulkEditValueFromCSV(c, language, schema, element, qualifier, value,
|
||||
fromAuthority);
|
||||
if (fromAuthority != null) {
|
||||
value = dcv.getValue() + csv.getAuthoritySeparator() + dcv.getAuthority() + csv
|
||||
@@ -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()) {
|
||||
@@ -1578,7 +1517,7 @@ public class MetadataImport {
|
||||
throw new MetadataImportException("Error in CSV row " + rowCount + ":\n" +
|
||||
"Not a UUID or indirect entity reference: '" + reference + "'");
|
||||
}
|
||||
} else if (!reference.startsWith("rowName:") ) { // Not a rowName ref; so it's a metadata value reference
|
||||
} else if (!reference.startsWith("rowName:")) { // Not a rowName ref; so it's a metadata value reference
|
||||
MetadataValueService metadataValueService = ContentServiceFactory.getInstance().getMetadataValueService();
|
||||
MetadataFieldService metadataFieldService =
|
||||
ContentServiceFactory.getInstance().getMetadataFieldService();
|
||||
@@ -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: "
|
||||
@@ -1762,7 +1706,7 @@ public class MetadataImport {
|
||||
} else {
|
||||
relationValidationErrors.add("Error on CSV row " + originRow + ":" + "\n" +
|
||||
"Cannot resolve Entity type for reference: "
|
||||
+ originRefererUUID + " in row: " + originRow );
|
||||
+ originRefererUUID + " in row: " + originRow);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
@@ -19,6 +19,15 @@ import org.dspace.core.Context;
|
||||
* @author Andrea Bollini
|
||||
*/
|
||||
public interface RequestItemAuthorExtractor {
|
||||
public RequestItemAuthor getRequestItemAuthor(Context context, Item item)
|
||||
throws SQLException;
|
||||
|
||||
/**
|
||||
* Retrieve the auhtor to contact for a request copy of the give item.
|
||||
*
|
||||
* @param context DSpace context object
|
||||
* @param item item to request
|
||||
* @return An object containing name an email address to send the request to
|
||||
* or null if no valid email address was found.
|
||||
* @throws SQLException if database error
|
||||
*/
|
||||
public RequestItemAuthor getRequestItemAuthor(Context context, Item item) throws SQLException;
|
||||
}
|
||||
|
@@ -16,6 +16,7 @@ import org.dspace.content.MetadataValue;
|
||||
import org.dspace.content.service.ItemService;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.core.I18nUtil;
|
||||
import org.dspace.services.factory.DSpaceServicesFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
/**
|
||||
@@ -38,6 +39,7 @@ public class RequestItemMetadataStrategy extends RequestItemSubmitterStrategy {
|
||||
@Override
|
||||
public RequestItemAuthor getRequestItemAuthor(Context context, Item item)
|
||||
throws SQLException {
|
||||
RequestItemAuthor author = null;
|
||||
if (emailMetadata != null) {
|
||||
List<MetadataValue> vals = itemService.getMetadataByMetadataString(item, emailMetadata);
|
||||
if (vals.size() > 0) {
|
||||
@@ -49,19 +51,38 @@ public class RequestItemMetadataStrategy extends RequestItemSubmitterStrategy {
|
||||
fullname = nameVals.iterator().next().getValue();
|
||||
}
|
||||
}
|
||||
|
||||
if (StringUtils.isBlank(fullname)) {
|
||||
fullname = I18nUtil
|
||||
.getMessage(
|
||||
"org.dspace.app.requestitem.RequestItemMetadataStrategy.unnamed",
|
||||
context);
|
||||
}
|
||||
RequestItemAuthor author = new RequestItemAuthor(
|
||||
fullname, email);
|
||||
author = new RequestItemAuthor(fullname, email);
|
||||
return author;
|
||||
}
|
||||
} else {
|
||||
// Uses the basic strategy to look for the original submitter
|
||||
author = super.getRequestItemAuthor(context, item);
|
||||
// Is the author or his email null, so get the help desk or admin name and email
|
||||
if (null == author || null == author.getEmail()) {
|
||||
String email = null;
|
||||
String name = null;
|
||||
//First get help desk name and email
|
||||
email = DSpaceServicesFactory.getInstance()
|
||||
.getConfigurationService().getProperty("mail.helpdesk");
|
||||
name = DSpaceServicesFactory.getInstance()
|
||||
.getConfigurationService().getProperty("mail.helpdesk.name");
|
||||
// If help desk mail is null get the mail and name of admin
|
||||
if (email == null) {
|
||||
email = DSpaceServicesFactory.getInstance()
|
||||
.getConfigurationService().getProperty("mail.admin");
|
||||
name = DSpaceServicesFactory.getInstance()
|
||||
.getConfigurationService().getProperty("mail.admin.name");
|
||||
}
|
||||
return super.getRequestItemAuthor(context, item);
|
||||
author = new RequestItemAuthor(name, email);
|
||||
}
|
||||
}
|
||||
return author;
|
||||
}
|
||||
|
||||
public void setEmailMetadata(String emailMetadata) {
|
||||
|
@@ -23,13 +23,22 @@ public class RequestItemSubmitterStrategy implements RequestItemAuthorExtractor
|
||||
public RequestItemSubmitterStrategy() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the submitter of an Item as RequestItemAuthor or null if the
|
||||
* Submitter is deleted.
|
||||
*
|
||||
* @return The submitter of the item or null if the submitter is deleted
|
||||
* @throws SQLException if database error
|
||||
*/
|
||||
@Override
|
||||
public RequestItemAuthor getRequestItemAuthor(Context context, Item item)
|
||||
throws SQLException {
|
||||
EPerson submitter = item.getSubmitter();
|
||||
RequestItemAuthor author = new RequestItemAuthor(
|
||||
RequestItemAuthor author = null;
|
||||
if (null != submitter) {
|
||||
author = new RequestItemAuthor(
|
||||
submitter.getFullName(), submitter.getEmail());
|
||||
}
|
||||
return author;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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;
|
||||
@@ -19,12 +22,22 @@ import org.dspace.content.Bitstream;
|
||||
import org.dspace.content.Bundle;
|
||||
import org.dspace.content.Collection;
|
||||
import org.dspace.content.Community;
|
||||
import org.dspace.content.DSpaceObject;
|
||||
import org.dspace.content.Item;
|
||||
import org.dspace.content.factory.ContentServiceFactory;
|
||||
import org.dspace.content.service.CollectionService;
|
||||
import org.dspace.content.service.ItemService;
|
||||
import org.dspace.core.Constants;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.eperson.EPerson;
|
||||
import org.dspace.eperson.Group;
|
||||
import org.dspace.eperson.factory.EPersonServiceFactory;
|
||||
import org.dspace.eperson.service.GroupService;
|
||||
import org.dspace.services.factory.DSpaceServicesFactory;
|
||||
import org.dspace.utils.DSpace;
|
||||
import org.dspace.xmlworkflow.factory.XmlWorkflowServiceFactory;
|
||||
import org.dspace.xmlworkflow.storedcomponents.CollectionRole;
|
||||
import org.dspace.xmlworkflow.storedcomponents.service.CollectionRoleService;
|
||||
|
||||
/**
|
||||
* This class is an addition to the AuthorizeManager that perform authorization
|
||||
@@ -34,6 +47,7 @@ import org.dspace.core.Context;
|
||||
*/
|
||||
public class AuthorizeUtil {
|
||||
|
||||
private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(AuthorizeUtil.class);
|
||||
/**
|
||||
* Default constructor
|
||||
*/
|
||||
@@ -525,4 +539,154 @@ public class AuthorizeUtil {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will check whether the current user is authorized to manage the default read group
|
||||
* @param context The relevant DSpace context
|
||||
* @param collection The collection for which this will be checked
|
||||
* @throws AuthorizeException If something goes wrong
|
||||
* @throws SQLException If something goes wrong
|
||||
*/
|
||||
public static void authorizeManageDefaultReadGroup(Context context,
|
||||
Collection collection) throws AuthorizeException, SQLException {
|
||||
AuthorizeService authorizeService = AuthorizeServiceFactory.getInstance().getAuthorizeService();
|
||||
authorizeService.authorizeAction(context, collection, Constants.ADMIN);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method checks whether the current user has sufficient rights to modify the group.
|
||||
* Depending on the kind of group and due to delegated administration, separate checks need to be done to verify
|
||||
* whether the user is allowed to modify the group.
|
||||
*
|
||||
* @param context the context of which the user will be checked
|
||||
* @param group the group to be checked
|
||||
* @throws SQLException
|
||||
* @throws AuthorizeException
|
||||
*/
|
||||
public static void authorizeManageGroup(Context context, Group group) throws SQLException, AuthorizeException {
|
||||
AuthorizeService authorizeService = AuthorizeServiceFactory.getInstance().getAuthorizeService();
|
||||
GroupService groupService = EPersonServiceFactory.getInstance().getGroupService();
|
||||
CollectionRoleService collectionRoleService = XmlWorkflowServiceFactory.getInstance()
|
||||
.getCollectionRoleService();
|
||||
if (authorizeService.isAdmin(context)) {
|
||||
return;
|
||||
}
|
||||
|
||||
DSpaceObject parentObject = groupService.getParentObject(context, group);
|
||||
if (parentObject == null) {
|
||||
throw new AuthorizeException("not authorized to manage this group");
|
||||
}
|
||||
if (parentObject.getType() == Constants.COLLECTION) {
|
||||
Collection collection = (Collection) parentObject;
|
||||
|
||||
if (group.equals(collection.getSubmitters())) {
|
||||
authorizeManageSubmittersGroup(context, collection);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
List<CollectionRole> collectionRoles = collectionRoleService.findByCollection(context, collection);
|
||||
for (CollectionRole role : collectionRoles) {
|
||||
if (group.equals(role.getGroup())) {
|
||||
authorizeManageWorkflowsGroup(context, collection);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (group.equals(collection.getAdministrators())) {
|
||||
authorizeManageAdminGroup(context, collection);
|
||||
return;
|
||||
}
|
||||
// if we reach this point, it means that the group is related
|
||||
// to a collection but as it is not the submitters, nor the administrators,
|
||||
// nor a workflow groups it must be a default item/bitstream groups
|
||||
authorizeManageDefaultReadGroup(context, collection);
|
||||
return;
|
||||
}
|
||||
if (parentObject.getType() == Constants.COMMUNITY) {
|
||||
Community community = (Community) parentObject;
|
||||
authorizeManageAdminGroup(context, community);
|
||||
return;
|
||||
}
|
||||
|
||||
throw new AuthorizeException("not authorized to manage this group");
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will return a boolean indicating whether the current user is allowed to register a new
|
||||
* account or not
|
||||
* @param context The relevant DSpace context
|
||||
* @param request The current request
|
||||
* @return A boolean indicating whether the current user can register a new account or not
|
||||
* @throws SQLException If something goes wrong
|
||||
*/
|
||||
public static boolean authorizeNewAccountRegistration(Context context, HttpServletRequest request)
|
||||
throws SQLException {
|
||||
if (DSpaceServicesFactory.getInstance().getConfigurationService()
|
||||
.getBooleanProperty("user.registration", true)) {
|
||||
// This allowSetPassword is currently the only mthod that would return true only when it's
|
||||
// actually expected to be returning true.
|
||||
// For example the LDAP canSelfRegister will return true due to auto-register, while that
|
||||
// does not imply a new user can register explicitly
|
||||
return AuthenticateServiceFactory.getInstance().getAuthenticationService()
|
||||
.allowSetPassword(context, request, null);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will return a boolean indicating whether it's allowed to update the password for the EPerson
|
||||
* with the given email and canLogin property
|
||||
* @param context The relevant DSpace context
|
||||
* @param email The email to be checked
|
||||
* @return A boolean indicating if the password can be updated or not
|
||||
*/
|
||||
public static boolean authorizeUpdatePassword(Context context, String email) {
|
||||
try {
|
||||
EPerson eperson = EPersonServiceFactory.getInstance().getEPersonService().findByEmail(context, email);
|
||||
if (eperson != null && eperson.canLogIn()) {
|
||||
HttpServletRequest request = new DSpace().getRequestService().getCurrentRequest()
|
||||
.getHttpServletRequest();
|
||||
return AuthenticateServiceFactory.getInstance().getAuthenticationService()
|
||||
.allowSetPassword(context, request, null);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
log.error("Something went wrong trying to retrieve EPerson for email: " + email, e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method checks if the community Admin can manage accounts
|
||||
*
|
||||
* @return true if is able
|
||||
*/
|
||||
public static boolean canCommunityAdminManageAccounts() {
|
||||
boolean isAble = false;
|
||||
if (AuthorizeConfiguration.canCommunityAdminManagePolicies()
|
||||
|| AuthorizeConfiguration.canCommunityAdminManageAdminGroup()
|
||||
|| AuthorizeConfiguration.canCommunityAdminManageCollectionPolicies()
|
||||
|| AuthorizeConfiguration.canCommunityAdminManageCollectionSubmitters()
|
||||
|| AuthorizeConfiguration.canCommunityAdminManageCollectionWorkflows()
|
||||
|| AuthorizeConfiguration.canCommunityAdminManageCollectionAdminGroup()) {
|
||||
isAble = true;
|
||||
}
|
||||
return isAble;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method checks if the Collection Admin can manage accounts
|
||||
*
|
||||
* @return true if is able
|
||||
*/
|
||||
public static boolean canCollectionAdminManageAccounts() {
|
||||
boolean isAble = false;
|
||||
if (AuthorizeConfiguration.canCollectionAdminManagePolicies()
|
||||
|| AuthorizeConfiguration.canCollectionAdminManageSubmitters()
|
||||
|| AuthorizeConfiguration.canCollectionAdminManageWorkflows()
|
||||
|| AuthorizeConfiguration.canCollectionAdminManageAdminGroup()) {
|
||||
isAble = true;
|
||||
}
|
||||
return isAble;
|
||||
}
|
||||
}
|
||||
|
@@ -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),
|
||||
@@ -606,6 +614,12 @@ public class AuthorizeServiceImpl implements AuthorizeService {
|
||||
resourcePolicyService.removeDsoEPersonPolicies(c, o, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAllEPersonPolicies(Context c, EPerson e)
|
||||
throws SQLException, AuthorizeException {
|
||||
resourcePolicyService.removeAllEPersonPolicies(c, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Group> getAuthorizedGroups(Context c, DSpaceObject o,
|
||||
int actionID) throws java.sql.SQLException {
|
||||
|
@@ -114,6 +114,11 @@ public class ResourcePolicyServiceImpl implements ResourcePolicyService {
|
||||
return resourcePolicyDAO.findByEPersonGroupTypeIdAction(c, e, groups, action, type_id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ResourcePolicy> find(Context context, EPerson ePerson) throws SQLException {
|
||||
return resourcePolicyDAO.findByEPerson(context, ePerson);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ResourcePolicy> findByTypeGroupActionExceptId(Context context, DSpaceObject dso, Group group,
|
||||
int action, int notPolicyID)
|
||||
@@ -246,6 +251,11 @@ public class ResourcePolicyServiceImpl implements ResourcePolicyService {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAllEPersonPolicies(Context context, EPerson ePerson) throws SQLException, AuthorizeException {
|
||||
resourcePolicyDAO.deleteByEPerson(context, ePerson);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeGroupPolicies(Context c, Group group) throws SQLException {
|
||||
resourcePolicyDAO.deleteByGroup(c, group);
|
||||
|
@@ -33,6 +33,8 @@ public interface ResourcePolicyDAO extends GenericDAO<ResourcePolicy> {
|
||||
public List<ResourcePolicy> findByDsoAndType(Context context, DSpaceObject dSpaceObject, String type)
|
||||
throws SQLException;
|
||||
|
||||
public List<ResourcePolicy> findByEPerson(Context context, EPerson ePerson) throws SQLException;
|
||||
|
||||
public List<ResourcePolicy> findByGroup(Context context, Group group) throws SQLException;
|
||||
|
||||
public List<ResourcePolicy> findByDSoAndAction(Context context, DSpaceObject dso, int actionId) throws SQLException;
|
||||
@@ -66,6 +68,15 @@ public interface ResourcePolicyDAO extends GenericDAO<ResourcePolicy> {
|
||||
|
||||
public void deleteByDsoEPersonPolicies(Context context, DSpaceObject dso, EPerson ePerson) throws SQLException;
|
||||
|
||||
/**
|
||||
* Deletes all policies that belong to an EPerson
|
||||
*
|
||||
* @param context DSpace context object
|
||||
* @param ePerson ePerson whose policies to delete
|
||||
* @throws SQLException if database error
|
||||
*/
|
||||
public void deleteByEPerson(Context context, EPerson ePerson) throws SQLException;
|
||||
|
||||
public void deleteByDsoAndTypeNotEqualsTo(Context c, DSpaceObject o, String type) throws SQLException;
|
||||
|
||||
/**
|
||||
@@ -101,7 +112,7 @@ public interface ResourcePolicyDAO extends GenericDAO<ResourcePolicy> {
|
||||
* @return total resource policies of the ePerson
|
||||
* @throws SQLException if database error
|
||||
*/
|
||||
public int countByEPerson(Context context, EPerson eperson) throws SQLException;
|
||||
public int countByEPerson(Context context, EPerson ePerson) throws SQLException;
|
||||
|
||||
/**
|
||||
* Return a paginated list of policies related to a resourceUuid belong to an ePerson
|
||||
|
@@ -63,6 +63,16 @@ public class ResourcePolicyDAOImpl extends AbstractHibernateDAO<ResourcePolicy>
|
||||
return list(context, criteriaQuery, false, ResourcePolicy.class, -1, -1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ResourcePolicy> findByEPerson(Context context, EPerson ePerson) throws SQLException {
|
||||
CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context);
|
||||
CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, ResourcePolicy.class);
|
||||
Root<ResourcePolicy> resourcePolicyRoot = criteriaQuery.from(ResourcePolicy.class);
|
||||
criteriaQuery.select(resourcePolicyRoot);
|
||||
criteriaQuery.where(criteriaBuilder.equal(resourcePolicyRoot.get(ResourcePolicy_.eperson), ePerson));
|
||||
return list(context, criteriaQuery, false, ResourcePolicy.class, -1, -1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ResourcePolicy> findByGroup(Context context, Group group) throws SQLException {
|
||||
CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context);
|
||||
@@ -194,6 +204,15 @@ public class ResourcePolicyDAOImpl extends AbstractHibernateDAO<ResourcePolicy>
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteByEPerson(Context context, EPerson ePerson) throws SQLException {
|
||||
String queryString = "delete from ResourcePolicy where eperson= :eperson";
|
||||
Query query = createQuery(context, queryString);
|
||||
query.setParameter("eperson", ePerson);
|
||||
query.executeUpdate();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteByDsoAndTypeNotEqualsTo(Context context, DSpaceObject dso, String type) throws SQLException {
|
||||
|
||||
@@ -247,10 +266,10 @@ public class ResourcePolicyDAOImpl extends AbstractHibernateDAO<ResourcePolicy>
|
||||
}
|
||||
|
||||
@Override
|
||||
public int countByEPerson(Context context, EPerson eperson) throws SQLException {
|
||||
public int countByEPerson(Context context, EPerson ePerson) throws SQLException {
|
||||
Query query = createQuery(context,
|
||||
"SELECT count(*) FROM " + ResourcePolicy.class.getSimpleName() + " WHERE eperson_id = (:epersonUuid) ");
|
||||
query.setParameter("epersonUuid", eperson.getID());
|
||||
query.setParameter("epersonUuid", ePerson.getID());
|
||||
return count(query);
|
||||
}
|
||||
|
||||
|
@@ -213,6 +213,26 @@ public interface AuthorizeService {
|
||||
|
||||
public boolean isCollectionAdmin(Context c) throws SQLException;
|
||||
|
||||
/**
|
||||
* Check to see if a specific user is Community admin
|
||||
*
|
||||
* @param c current context
|
||||
* @param e the user to check
|
||||
* @return true if user is an admin of some community
|
||||
* @throws SQLException
|
||||
*/
|
||||
public boolean isCommunityAdmin(Context c, EPerson e) throws SQLException;
|
||||
|
||||
/**
|
||||
* Check to see if a specific user is Collection admin
|
||||
*
|
||||
* @param c current context
|
||||
* @param e the user to check
|
||||
* @return true if user is an admin of some collection
|
||||
* @throws SQLException if database error
|
||||
*/
|
||||
public boolean isCollectionAdmin(Context c, EPerson e) throws SQLException;
|
||||
|
||||
///////////////////////////////////////////////
|
||||
// policy manipulation methods
|
||||
///////////////////////////////////////////////
|
||||
@@ -429,6 +449,16 @@ public interface AuthorizeService {
|
||||
*/
|
||||
public void removeEPersonPolicies(Context c, DSpaceObject o, EPerson e) throws SQLException, AuthorizeException;
|
||||
|
||||
/**
|
||||
* Removes all policies from an eperson that belong to an EPerson.
|
||||
*
|
||||
* @param c current context
|
||||
* @param e the eperson
|
||||
* @throws SQLException if there's a database problem
|
||||
* @throws AuthorizeException if authorization error
|
||||
*/
|
||||
public void removeAllEPersonPolicies(Context c, EPerson e) throws SQLException, AuthorizeException;
|
||||
|
||||
/**
|
||||
* Returns all groups authorized to perform an action on an object. Returns
|
||||
* empty array if no matches.
|
||||
|
@@ -39,6 +39,16 @@ public interface ResourcePolicyService extends DSpaceCRUDService<ResourcePolicy>
|
||||
|
||||
public List<ResourcePolicy> find(Context context, Group group) throws SQLException;
|
||||
|
||||
/**
|
||||
* Retrieve a list of ResourcePolicies by EPerson
|
||||
*
|
||||
* @param c context
|
||||
* @param ePerson the EPerson for which to look up the resource policies
|
||||
* @return a list of ResourcePolicies for the provided EPerson
|
||||
* @throws SQLException if there's a database problem
|
||||
*/
|
||||
public List<ResourcePolicy> find(Context c, EPerson ePerson) throws SQLException;
|
||||
|
||||
public List<ResourcePolicy> find(Context c, EPerson e, List<Group> groups, int action, int type_id)
|
||||
throws SQLException;
|
||||
|
||||
@@ -72,6 +82,16 @@ public interface ResourcePolicyService extends DSpaceCRUDService<ResourcePolicy>
|
||||
public void removeDsoEPersonPolicies(Context context, DSpaceObject dso, EPerson ePerson)
|
||||
throws SQLException, AuthorizeException;
|
||||
|
||||
/**
|
||||
* Removes all ResourcePolicies related to an EPerson
|
||||
*
|
||||
* @param context context
|
||||
* @param ePerson the EPerson for which the ResourcePolicies will be deleted
|
||||
* @throws SQLException if there's a database problem
|
||||
* @throws AuthorizeException when the current user is not authorized
|
||||
*/
|
||||
public void removeAllEPersonPolicies(Context context, EPerson ePerson) throws SQLException, AuthorizeException;
|
||||
|
||||
public void removeGroupPolicies(Context c, Group group) throws SQLException;
|
||||
|
||||
public void removeDsoAndTypeNotEqualsToPolicies(Context c, DSpaceObject o, String type)
|
||||
|
@@ -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: "
|
||||
@@ -889,4 +900,95 @@ public class CollectionServiceImpl extends DSpaceObjectServiceImpl<Collection> i
|
||||
throws SQLException {
|
||||
return collectionDAO.getCollectionsWithBitstreamSizesTotal(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Group createDefaultReadGroup(Context context, Collection collection, String typeOfGroupString,
|
||||
int defaultRead)
|
||||
throws SQLException, AuthorizeException {
|
||||
Group role = groupService.create(context);
|
||||
groupService.setName(role, "COLLECTION_" + collection.getID().toString() + "_" + typeOfGroupString +
|
||||
"_DEFAULT_READ");
|
||||
|
||||
// Remove existing privileges from the anonymous group.
|
||||
authorizeService.removePoliciesActionFilter(context, collection, defaultRead);
|
||||
|
||||
// Grant our new role the default privileges.
|
||||
authorizeService.addPolicy(context, collection, defaultRead, role);
|
||||
groupService.update(context, role);
|
||||
return role;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<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;
|
||||
}
|
||||
}
|
||||
|
@@ -629,6 +629,10 @@ public class CommunityServiceImpl extends DSpaceObjectServiceImpl<Community> imp
|
||||
case Constants.DELETE:
|
||||
if (AuthorizeConfiguration.canCommunityAdminPerformSubelementDeletion()) {
|
||||
adminObject = getParentObject(context, community);
|
||||
if (adminObject == null) {
|
||||
//top-level community, has to be admin of the current community
|
||||
adminObject = community;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case Constants.ADD:
|
||||
|
@@ -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
|
||||
|
@@ -7,6 +7,7 @@
|
||||
*/
|
||||
package org.dspace.content;
|
||||
|
||||
import java.util.Objects;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
@@ -15,6 +16,8 @@ import javax.persistence.Id;
|
||||
import javax.persistence.SequenceGenerator;
|
||||
import javax.persistence.Table;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.builder.HashCodeBuilder;
|
||||
import org.dspace.core.ReloadableEntity;
|
||||
|
||||
/**
|
||||
@@ -45,6 +48,7 @@ public class EntityType implements ReloadableEntity<Integer> {
|
||||
|
||||
/**
|
||||
* The standard setter for the ID of this EntityType
|
||||
*
|
||||
* @param id The ID that this EntityType's ID will be set to
|
||||
*/
|
||||
public void setId(Integer id) {
|
||||
@@ -53,6 +57,7 @@ public class EntityType implements ReloadableEntity<Integer> {
|
||||
|
||||
/**
|
||||
* The standard getter for the label of this EntityType
|
||||
*
|
||||
* @return The label for this EntityType
|
||||
*/
|
||||
public String getLabel() {
|
||||
@@ -61,6 +66,7 @@ public class EntityType implements ReloadableEntity<Integer> {
|
||||
|
||||
/**
|
||||
* The standard setter for the label of this EntityType
|
||||
*
|
||||
* @param label The label that this EntityType's label will be set to
|
||||
*/
|
||||
public void setLabel(String label) {
|
||||
@@ -69,9 +75,40 @@ public class EntityType implements ReloadableEntity<Integer> {
|
||||
|
||||
/**
|
||||
* The standard getter for the ID of this EntityType
|
||||
*
|
||||
* @return The ID for this EntityType
|
||||
*/
|
||||
public Integer getID() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether two entity types are equal based on the id and the label
|
||||
* @param obj object to be compared
|
||||
* @return
|
||||
*/
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof EntityType)) {
|
||||
return false;
|
||||
}
|
||||
EntityType entityType = (EntityType) obj;
|
||||
|
||||
if (!Objects.equals(this.getID(), entityType.getID())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!StringUtils.equals(this.getLabel(), entityType.getLabel())) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a hash code value for the object.
|
||||
* @return hash code value
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return new HashCodeBuilder().append(getID()).toHashCode();
|
||||
}
|
||||
}
|
||||
|
@@ -230,6 +230,12 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl<Item> implements It
|
||||
return itemDAO.findBySubmitter(context, eperson);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Item> findBySubmitter(Context context, EPerson eperson, boolean retrieveAllItems)
|
||||
throws SQLException {
|
||||
return itemDAO.findBySubmitter(context, eperson, retrieveAllItems);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Item> findBySubmitterDateSorted(Context context, EPerson eperson, Integer limit)
|
||||
throws SQLException {
|
||||
@@ -1100,19 +1106,7 @@ prevent the generation of resource policy entry values with null dspace_object a
|
||||
}
|
||||
break;
|
||||
case Constants.DELETE:
|
||||
if (item.getOwningCollection() != null) {
|
||||
if (AuthorizeConfiguration.canCollectionAdminPerformItemDeletion()) {
|
||||
adminObject = collection;
|
||||
} else if (AuthorizeConfiguration.canCommunityAdminPerformItemDeletion()) {
|
||||
adminObject = community;
|
||||
}
|
||||
} else {
|
||||
if (AuthorizeConfiguration.canCollectionAdminManageTemplateItem()) {
|
||||
adminObject = collection;
|
||||
} else if (AuthorizeConfiguration.canCommunityAdminManageCollectionTemplateItem()) {
|
||||
adminObject = community;
|
||||
}
|
||||
}
|
||||
adminObject = item;
|
||||
break;
|
||||
case Constants.WRITE:
|
||||
// if it is a template item we need to check the
|
||||
@@ -1372,6 +1366,32 @@ prevent the generation of resource policy entry values with null dspace_object a
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Supports moving metadata by adding the metadata value or updating the place of the relationship
|
||||
*/
|
||||
@Override
|
||||
protected void moveSingleMetadataValue(Context context, Item dso, int place, MetadataValue rr) {
|
||||
if (rr instanceof RelationshipMetadataValue) {
|
||||
try {
|
||||
//Retrieve the applicable relationship
|
||||
Relationship rs = relationshipService.find(context,
|
||||
((RelationshipMetadataValue) rr).getRelationshipId());
|
||||
if (rs.getLeftItem() == dso) {
|
||||
rs.setLeftPlace(place);
|
||||
} else {
|
||||
rs.setRightPlace(place);
|
||||
}
|
||||
relationshipService.update(context, rs);
|
||||
} catch (Exception e) {
|
||||
//should not occur, otherwise metadata can't be updated either
|
||||
log.error("An error occurred while moving " + rr.getAuthority() + " for item " + dso.getID(), e);
|
||||
}
|
||||
} else {
|
||||
//just move the metadata
|
||||
rr.setPlace(place);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will sort the List of MetadataValue objects based on the MetadataSchema, MetadataField Element,
|
||||
* MetadataField Qualifier and MetadataField Place in that order.
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
|
@@ -212,9 +212,8 @@ public class WorkspaceItemServiceImpl implements WorkspaceItemService {
|
||||
*/
|
||||
Item item = workspaceItem.getItem();
|
||||
if (!authorizeService.isAdmin(context)
|
||||
&& ((context.getCurrentUser() == null) || (context
|
||||
.getCurrentUser().getID() != item.getSubmitter()
|
||||
.getID()))) {
|
||||
&& (item.getSubmitter() == null || (context.getCurrentUser() == null)
|
||||
|| (context.getCurrentUser().getID() != item.getSubmitter().getID()))) {
|
||||
// Not an admit, not the submitter
|
||||
throw new AuthorizeException("Must be an administrator or the "
|
||||
+ "original submitter to delete a workspace item");
|
||||
@@ -265,7 +264,12 @@ public class WorkspaceItemServiceImpl implements WorkspaceItemService {
|
||||
|
||||
// Need to delete the workspaceitem row first since it refers
|
||||
// to item ID
|
||||
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);
|
||||
}
|
||||
|
@@ -47,6 +47,19 @@ public interface ItemDAO extends DSpaceObjectLegacySupportDAO<Item> {
|
||||
|
||||
public Iterator<Item> findBySubmitter(Context context, EPerson eperson) throws SQLException;
|
||||
|
||||
/**
|
||||
* Find all the items by a given submitter. The order is
|
||||
* indeterminate. All items are included.
|
||||
*
|
||||
* @param context DSpace context object
|
||||
* @param eperson the submitter
|
||||
* @param retrieveAllItems flag to determine if only archive should be returned
|
||||
* @return an iterator over the items submitted by eperson
|
||||
* @throws SQLException if database error
|
||||
*/
|
||||
public Iterator<Item> findBySubmitter(Context context, EPerson eperson, boolean retrieveAllItems)
|
||||
throws SQLException;
|
||||
|
||||
public Iterator<Item> findBySubmitter(Context context, EPerson eperson, MetadataField metadataField, int limit)
|
||||
throws SQLException;
|
||||
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -108,6 +108,17 @@ public class ItemDAOImpl extends AbstractHibernateDSODAO<Item> implements ItemDA
|
||||
return iterate(query);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Item> findBySubmitter(Context context, EPerson eperson, boolean retrieveAllItems)
|
||||
throws SQLException {
|
||||
if (!retrieveAllItems) {
|
||||
return findBySubmitter(context, eperson);
|
||||
}
|
||||
Query query = createQuery(context, "FROM Item WHERE submitter= :submitter");
|
||||
query.setParameter("submitter", eperson);
|
||||
return iterate(query);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Item> findBySubmitter(Context context, EPerson eperson, MetadataField metadataField, int limit)
|
||||
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_;
|
||||
|
||||
/**
|
||||
@@ -56,6 +61,7 @@ public class ProcessDAOImpl extends AbstractHibernateDAO<Process> implements Pro
|
||||
CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Process.class);
|
||||
Root<Process> processRoot = criteriaQuery.from(Process.class);
|
||||
criteriaQuery.select(processRoot);
|
||||
criteriaQuery.orderBy(criteriaBuilder.desc(processRoot.get(Process_.processId)));
|
||||
|
||||
return list(context, criteriaQuery, false, Process.class, limit, offset);
|
||||
}
|
||||
@@ -71,6 +77,76 @@ public class ProcessDAOImpl extends AbstractHibernateDAO<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
|
||||
@@ -339,4 +341,57 @@ public interface CollectionService
|
||||
* @throws SQLException if database error
|
||||
*/
|
||||
List<Map.Entry<Collection, Long>> getCollectionsWithBitstreamSizesTotal(Context context) throws SQLException;
|
||||
|
||||
/**
|
||||
* This method will create a default read group for the given Collection. It'll create either a defaultItemRead or
|
||||
* a defaultBitstreamRead group depending on the given parameters
|
||||
*
|
||||
* @param context The relevant DSpace context
|
||||
* @param collection The collection for which it'll be created
|
||||
* @param typeOfGroupString The type of group to be made, item or bitstream
|
||||
* @param defaultRead The defaultRead int, item or bitstream
|
||||
* @return The created Group
|
||||
* @throws SQLException If something goes wrong
|
||||
* @throws AuthorizeException If something goes wrong
|
||||
*/
|
||||
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,
|
||||
|
@@ -113,6 +113,21 @@ public interface ItemService
|
||||
public Iterator<Item> findBySubmitter(Context context, EPerson eperson)
|
||||
throws SQLException;
|
||||
|
||||
/**
|
||||
* Find all the items by a given submitter. The order is
|
||||
* indeterminate. All items are included.
|
||||
*
|
||||
* @param context DSpace context object
|
||||
* @param eperson the submitter
|
||||
* @param retrieveAllItems flag to determine if all items should be returned or only archived items.
|
||||
* If true, all items (regardless of status) are returned.
|
||||
* If false, only archived items will be returned.
|
||||
* @return an iterator over the items submitted by eperson
|
||||
* @throws SQLException if database error
|
||||
*/
|
||||
public Iterator<Item> findBySubmitter(Context context, EPerson eperson, boolean retrieveAllItems)
|
||||
throws SQLException;
|
||||
|
||||
/**
|
||||
* Retrieve the list of items submitted by eperson, ordered by recently submitted, optionally limitable
|
||||
*
|
||||
|
@@ -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;
|
||||
|
||||
@@ -190,7 +190,15 @@ public class Context implements AutoCloseable {
|
||||
setMode(this.mode);
|
||||
}
|
||||
|
||||
public static boolean updateDatabase() {
|
||||
/**
|
||||
* Update the DSpace database, ensuring that any necessary migrations are run prior to initializing
|
||||
* Hibernate.
|
||||
* <P>
|
||||
* This is synchronized as it only needs to be run successfully *once* (for the first Context initialized).
|
||||
*
|
||||
* @return true/false, based on whether database was successfully updated
|
||||
*/
|
||||
public static synchronized boolean updateDatabase() {
|
||||
//If the database has not been updated yet, update it and remember that.
|
||||
if (databaseUpdated.compareAndSet(false, true)) {
|
||||
|
||||
@@ -200,7 +208,7 @@ public class Context implements AutoCloseable {
|
||||
try {
|
||||
DatabaseUtils.updateDatabase();
|
||||
} catch (SQLException sqle) {
|
||||
log.fatal("Cannot initialize database via Flyway!", sqle);
|
||||
log.fatal("Cannot update or initialize database via Flyway!", sqle);
|
||||
databaseUpdated.set(false);
|
||||
}
|
||||
}
|
||||
@@ -876,4 +884,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;
|
||||
@@ -24,7 +26,6 @@ import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.TimeZone;
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -69,7 +70,6 @@ import org.dspace.discovery.indexobject.IndexableCommunity;
|
||||
import org.dspace.discovery.indexobject.IndexableItem;
|
||||
import org.dspace.discovery.indexobject.factory.IndexFactory;
|
||||
import org.dspace.discovery.indexobject.factory.IndexObjectFactoryFactory;
|
||||
import org.dspace.eperson.EPerson;
|
||||
import org.dspace.eperson.Group;
|
||||
import org.dspace.eperson.factory.EPersonServiceFactory;
|
||||
import org.dspace.eperson.service.GroupService;
|
||||
@@ -100,16 +100,6 @@ import org.springframework.stereotype.Service;
|
||||
@Service
|
||||
public class SolrServiceImpl implements SearchService, IndexingService {
|
||||
|
||||
/**
|
||||
* The name of the discover configuration used to search for workflow tasks in the mydspace
|
||||
*/
|
||||
public static final String DISCOVER_WORKFLOW_CONFIGURATION_NAME = "workflow";
|
||||
|
||||
/**
|
||||
* The name of the discover configuration used to search for inprogress submission in the mydspace
|
||||
*/
|
||||
public static final String DISCOVER_WORKSPACE_CONFIGURATION_NAME = "workspace";
|
||||
|
||||
private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(SolrServiceImpl.class);
|
||||
|
||||
@Autowired
|
||||
@@ -763,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++) {
|
||||
@@ -848,46 +843,9 @@ public class SolrServiceImpl implements SearchService, IndexingService {
|
||||
|
||||
}
|
||||
|
||||
boolean isWorkspace = StringUtils.startsWith(discoveryQuery.getDiscoveryConfigurationName(),
|
||||
DISCOVER_WORKSPACE_CONFIGURATION_NAME);
|
||||
boolean isWorkflow = StringUtils.startsWith(discoveryQuery.getDiscoveryConfigurationName(),
|
||||
DISCOVER_WORKFLOW_CONFIGURATION_NAME);
|
||||
EPerson currentUser = context.getCurrentUser();
|
||||
|
||||
// extra security check to avoid the possibility that an anonymous user
|
||||
// get access to workspace or workflow
|
||||
if (currentUser == null && (isWorkflow || isWorkspace)) {
|
||||
throw new IllegalStateException("An anonymous user cannot perform a workspace or workflow search");
|
||||
}
|
||||
if (isWorkspace) {
|
||||
// insert filter by submitter
|
||||
solrQuery
|
||||
.addFilterQuery("submitter_authority:(" + currentUser.getID() + ")");
|
||||
} else if (isWorkflow) {
|
||||
// Retrieve all the groups the current user is a member of !
|
||||
Set<Group> groups;
|
||||
try {
|
||||
groups = groupService.allMemberGroupsSet(context, currentUser);
|
||||
} catch (SQLException e) {
|
||||
throw new org.dspace.discovery.SearchServiceException(e.getMessage(), e);
|
||||
}
|
||||
|
||||
// insert filter by controllers
|
||||
StringBuilder controllerQuery = new StringBuilder();
|
||||
controllerQuery.append("taskfor:(e" + currentUser.getID());
|
||||
for (Group group : groups) {
|
||||
controllerQuery.append(" OR g").append(group.getID());
|
||||
}
|
||||
controllerQuery.append(")");
|
||||
solrQuery.addFilterQuery(controllerQuery.toString());
|
||||
}
|
||||
|
||||
|
||||
//Add any configured search plugins !
|
||||
List<SolrServiceSearchPlugin> solrServiceSearchPlugins = DSpaceServicesFactory.getInstance().getServiceManager()
|
||||
.getServicesByType(
|
||||
SolrServiceSearchPlugin
|
||||
.class);
|
||||
List<SolrServiceSearchPlugin> solrServiceSearchPlugins = DSpaceServicesFactory.getInstance()
|
||||
.getServiceManager().getServicesByType(SolrServiceSearchPlugin.class);
|
||||
for (SolrServiceSearchPlugin searchPlugin : solrServiceSearchPlugins) {
|
||||
searchPlugin.additionalSearchParameters(context, discoveryQuery, solrQuery);
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -11,7 +11,6 @@ import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.solr.client.solrj.SolrQuery;
|
||||
import org.apache.solr.common.SolrInputDocument;
|
||||
@@ -21,6 +20,7 @@ import org.dspace.authorize.service.ResourcePolicyService;
|
||||
import org.dspace.content.Collection;
|
||||
import org.dspace.content.Community;
|
||||
import org.dspace.content.DSpaceObject;
|
||||
import org.dspace.content.InProgressSubmission;
|
||||
import org.dspace.content.Item;
|
||||
import org.dspace.content.factory.ContentServiceFactory;
|
||||
import org.dspace.content.service.CollectionService;
|
||||
@@ -28,11 +28,16 @@ import org.dspace.content.service.CommunityService;
|
||||
import org.dspace.core.Constants;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.core.LogManager;
|
||||
import org.dspace.discovery.indexobject.IndexableClaimedTask;
|
||||
import org.dspace.discovery.indexobject.IndexableDSpaceObject;
|
||||
import org.dspace.discovery.indexobject.IndexableInProgressSubmission;
|
||||
import org.dspace.discovery.indexobject.IndexablePoolTask;
|
||||
import org.dspace.eperson.EPerson;
|
||||
import org.dspace.eperson.Group;
|
||||
import org.dspace.eperson.service.GroupService;
|
||||
import org.dspace.services.factory.DSpaceServicesFactory;
|
||||
import org.dspace.xmlworkflow.storedcomponents.ClaimedTask;
|
||||
import org.dspace.xmlworkflow.storedcomponents.PoolTask;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
/**
|
||||
@@ -61,8 +66,21 @@ public class SolrServiceResourceRestrictionPlugin implements SolrServiceIndexPlu
|
||||
|
||||
@Override
|
||||
public void additionalIndex(Context context, IndexableObject idxObj, SolrInputDocument document) {
|
||||
DSpaceObject dso = null;
|
||||
if (idxObj instanceof IndexableDSpaceObject) {
|
||||
DSpaceObject dso = ((IndexableDSpaceObject) idxObj).getIndexedObject();
|
||||
dso = ((IndexableDSpaceObject) idxObj).getIndexedObject();
|
||||
} else if (idxObj instanceof IndexableInProgressSubmission) {
|
||||
final InProgressSubmission inProgressSubmission
|
||||
= ((IndexableInProgressSubmission) idxObj).getIndexedObject();
|
||||
dso = inProgressSubmission.getItem();
|
||||
} else if (idxObj instanceof IndexablePoolTask) {
|
||||
final PoolTask poolTask = ((IndexablePoolTask) idxObj).getIndexedObject();
|
||||
dso = poolTask.getWorkflowItem().getItem();
|
||||
} else if (idxObj instanceof IndexableClaimedTask) {
|
||||
final ClaimedTask claimedTask = ((IndexableClaimedTask) idxObj).getIndexedObject();
|
||||
dso = claimedTask.getWorkflowItem().getItem();
|
||||
}
|
||||
if (dso != null) {
|
||||
try {
|
||||
List<ResourcePolicy> policies = authorizeService.getPoliciesActionFilter(context, dso, Constants.READ);
|
||||
for (ResourcePolicy resourcePolicy : policies) {
|
||||
@@ -106,7 +124,8 @@ public class SolrServiceResourceRestrictionPlugin implements SolrServiceIndexPlu
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
log.error(LogManager.getHeader(context, "Error while indexing resource policies",
|
||||
"DSpace object: (id " + dso.getID() + " type " + dso.getType() + ")"));
|
||||
"DSpace object: (id " + dso.getID() + " type " + dso.getType() + ")"
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -114,13 +133,6 @@ public class SolrServiceResourceRestrictionPlugin implements SolrServiceIndexPlu
|
||||
@Override
|
||||
public void additionalSearchParameters(Context context, DiscoverQuery discoveryQuery, SolrQuery solrQuery) {
|
||||
try {
|
||||
// skip workspace and workflow queries as security for it them is builtin in the SolrServiceImpl
|
||||
if (StringUtils.startsWith(discoveryQuery.getDiscoveryConfigurationName(),
|
||||
SolrServiceImpl.DISCOVER_WORKSPACE_CONFIGURATION_NAME)
|
||||
|| StringUtils.startsWith(discoveryQuery.getDiscoveryConfigurationName(),
|
||||
SolrServiceImpl.DISCOVER_WORKFLOW_CONFIGURATION_NAME)) {
|
||||
return;
|
||||
}
|
||||
if (!authorizeService.isAdmin(context)) {
|
||||
StringBuilder resourceQuery = new StringBuilder();
|
||||
//Always add the anonymous group id to the query
|
||||
|
@@ -19,5 +19,14 @@ import org.dspace.core.Context;
|
||||
*/
|
||||
public interface SolrServiceSearchPlugin {
|
||||
|
||||
public void additionalSearchParameters(Context context, DiscoverQuery discoveryQuery, SolrQuery solrQuery);
|
||||
/**
|
||||
* Edits the solr query before it is sent to solr by adding additional parameters to it.
|
||||
*
|
||||
* @param context The DSpace Context object.
|
||||
* @param discoveryQuery The discovery query object on which the solr query is based.
|
||||
* @param solrQuery The query that will be sent to solr and which may be edited by this plugin.
|
||||
* @throws SearchServiceException Any checked exception that might happen in this plugin
|
||||
*/
|
||||
public void additionalSearchParameters(Context context, DiscoverQuery discoveryQuery, SolrQuery solrQuery)
|
||||
throws SearchServiceException;
|
||||
}
|
||||
|
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in 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.Set;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.solr.client.solrj.SolrQuery;
|
||||
import org.dspace.authorize.service.AuthorizeService;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.eperson.EPerson;
|
||||
import org.dspace.eperson.Group;
|
||||
import org.dspace.eperson.service.GroupService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
/**
|
||||
* Plugin to restrict or grant access to workspace and workflow items
|
||||
* based on the discovery configuration used.
|
||||
*/
|
||||
public class SolrServiceWorkspaceWorkflowRestrictionPlugin implements SolrServiceSearchPlugin {
|
||||
|
||||
/**
|
||||
* The name of the discover configuration used to search for inprogress submission in the mydspace
|
||||
*/
|
||||
public static final String DISCOVER_WORKSPACE_CONFIGURATION_NAME = "workspace";
|
||||
|
||||
/**
|
||||
* The name of the discover configuration used to search for workflow tasks in the mydspace
|
||||
*/
|
||||
public static final String DISCOVER_WORKFLOW_CONFIGURATION_NAME = "workflow";
|
||||
|
||||
/**
|
||||
* The name of the discover configuration used by administrators to search for workflow tasks
|
||||
*/
|
||||
public static final String DISCOVER_WORKFLOW_ADMIN_CONFIGURATION_NAME = "workflowAdmin";
|
||||
|
||||
@Autowired(required = true)
|
||||
protected GroupService groupService;
|
||||
|
||||
@Autowired(required = true)
|
||||
protected AuthorizeService authorizeService;
|
||||
|
||||
@Override
|
||||
public void additionalSearchParameters(
|
||||
Context context, DiscoverQuery discoveryQuery, SolrQuery solrQuery
|
||||
) throws SearchServiceException {
|
||||
boolean isWorkspace = StringUtils.startsWith(
|
||||
discoveryQuery.getDiscoveryConfigurationName(),
|
||||
DISCOVER_WORKSPACE_CONFIGURATION_NAME
|
||||
);
|
||||
boolean isWorkflow = StringUtils.startsWith(
|
||||
discoveryQuery.getDiscoveryConfigurationName(),
|
||||
DISCOVER_WORKFLOW_CONFIGURATION_NAME
|
||||
);
|
||||
boolean isWorkflowAdmin = isAdmin(context)
|
||||
&& DISCOVER_WORKFLOW_ADMIN_CONFIGURATION_NAME.equals(discoveryQuery.getDiscoveryConfigurationName());
|
||||
EPerson currentUser = context.getCurrentUser();
|
||||
|
||||
// extra security check to avoid the possibility that an anonymous user
|
||||
// get access to workspace or workflow
|
||||
if (currentUser == null && (isWorkflow || isWorkspace)) {
|
||||
throw new IllegalStateException(
|
||||
"An anonymous user cannot perform a workspace or workflow search");
|
||||
}
|
||||
if (isWorkspace) {
|
||||
// insert filter by submitter
|
||||
solrQuery.addFilterQuery("submitter_authority:(" + currentUser.getID() + ")");
|
||||
} else if (isWorkflow && !isWorkflowAdmin) {
|
||||
// Retrieve all the groups the current user is a member of !
|
||||
Set<Group> groups;
|
||||
try {
|
||||
groups = groupService.allMemberGroupsSet(context, currentUser);
|
||||
} catch (SQLException e) {
|
||||
throw new SearchServiceException(e.getMessage(), e);
|
||||
}
|
||||
|
||||
// insert filter by controllers
|
||||
StringBuilder controllerQuery = new StringBuilder();
|
||||
controllerQuery.append("taskfor:(e").append(currentUser.getID());
|
||||
for (Group group : groups) {
|
||||
controllerQuery.append(" OR g").append(group.getID());
|
||||
}
|
||||
controllerQuery.append(")");
|
||||
solrQuery.addFilterQuery(controllerQuery.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isAdmin(Context context) throws SearchServiceException {
|
||||
try {
|
||||
return authorizeService.isAdmin(context);
|
||||
} catch (SQLException e) {
|
||||
throw new SearchServiceException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user