* Conflict file: RelationshipRestRepositoryIT
This commit is contained in:
Marie Verdonck
2020-10-28 18:06:17 +01:00
807 changed files with 53805 additions and 10819 deletions

29
.codecov.yml Normal file
View 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
View 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.

View 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.

View 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

View File

@@ -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
View 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

View 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
View 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

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -1,24 +1,24 @@
# DSpace
[![Build Status](https://travis-ci.org/DSpace/DSpace.png?branch=master)](https://travis-ci.org/DSpace/DSpace)
[![Build Status](https://travis-ci.com/DSpace/DSpace.png?branch=main)](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/

View File

@@ -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>

View File

@@ -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: ");

View File

@@ -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);
}
/**

View File

@@ -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();
}
}

View File

@@ -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");
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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) {

View File

@@ -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!");
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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) {

View File

@@ -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;
}

View File

@@ -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) {

View File

@@ -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;
}
}

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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

View File

@@ -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 {

View File

@@ -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);

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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.

View File

@@ -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)

View File

@@ -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.");
}

View File

@@ -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;
}
}

View File

@@ -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:

View File

@@ -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

View File

@@ -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();
}
}

View File

@@ -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.

View File

@@ -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;
}
}

View File

@@ -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())) {

View File

@@ -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)))));
}
/**

View File

@@ -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)) {

View File

@@ -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;

View File

@@ -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()));
}
}

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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("'", "&apos;").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("'", "&apos;"));
}
@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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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 {

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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,

View File

@@ -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
*

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -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);

View 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)");
}
}
}

View File

@@ -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!");
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
/**

View File

@@ -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.
*

View File

@@ -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;
}
}

View File

@@ -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));

View File

@@ -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);
}

View File

@@ -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() + ")" ));
}
}
}
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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